第1章 高级参数的绑定
1.1 参数的分类
- 默认参数绑定:
request
、response
、session
、model
- 基础数据类型
POJO
- 自定义参数绑定【比如定义转换器
Converter
】 - 包装的
POJO
- 数组:可以绑定到形参上,可以绑定到包装的
POJO
中 - 集合:只能保定在包装的
POJO
上
1.2 数组类型的参数的绑定
场景描述:在商品列表页选中多个商品,然后删除。
需求分析:此功能要求列表页中的每个商品前有一个
checkbox
,选中多个商品点击删除按钮,把此商品的id
传递给Controller
,根据商品的id
删除商品信息。JSP
中的实现
- 程序运行后生成的
html
代码如下:
-
Controller
方法中可以使用String[]
接收,或者pojo
的String[]
属性接收。两种方式任选一种即可。 - 方式一:直接绑定到形参上
@RequestMapping("/queryitem")
public String queryItem(QueryVo queryVo, String[] ids) {
System.out.println(queryVo.getItems().getName());
System.out.println(queryVo.getItems().getPrice());
System.out.println(ids.toString());
return null;
}
- 方式二:绑定到包装的
pojo
中
- 查看结果
1.3 集合类型的参数的绑定
- 场景描述:实现商品数据的批量修改。
- 需求分析:要想实现商品数据的批量删除,需要在商品列表中可以对商品进行修改,并且可以批量提交修改后的商品数据。这时需要将表单的数据绑定到
List
。 -
List
中存放对象,并将定义的List
放在包装类中,使用包装的pojo
对象接收。接收商品列表的pojo
如下:
-
JSP
改造
- Name属性必须是包装pojo的list属性+下标+元素属性。Jsp做如下改造:
varStatus属性常用参数总结下:
${status.index} 输出行号,从0开始。
${status.count} 输出行号,从1开始。
${status.current} 当前这次迭代的(集合中的)项
${status.first} 判断当前项是否为集合中的第一项,返回值为true或false
${status.last} 判断当前项是否为集合中的最后一项,返回值为true或false
begin、end、step分别表示:起始序号,结束序号,跳跃步伐。
Controller
@RequestMapping("/queryitem")
public String queryItem(QueryVo queryVo, String[] ids) {
System.out.println(queryVo.getItems().getName());
System.out.println(queryVo.getItems().getPrice());
System.out.println(ids.toString());
return null;
}
- 接收List类型的数据必须是pojo的属性,方法的形参为List类型无法正确接收到数据。
第2章 @RequestMapping的用法
-
value={"/list","/list2","/itemList"}
多个请求地址可以同时指向同一个方法 -
method=RequestMethod.GET
限制请求方式 -
@RequestMapping
可以写到Controller
上 ,窄化请求映射
2.1 URL路径映射
-
@RequestMapping(value="/item")
或@RequestMapping("/item)
。value
的值是数组,可以将多个url
映射到同一个方法。例如:
2.2 请求方法限定
- 限定
GET
方式请求:只能以GET
方式访问才能通过
@RequestMapping(method = RequestMethod.GET)
//如果通过Post访问则报错:
//HTTP Status 405 - Request method 'POST' not supported
//例如:
@RequestMapping(value="/editItem",method=RequestMethod.GET)
- 限定
POST
方法
@RequestMapping(method = RequestMethod.POST)
-
GET
和POST
都可以都可以访问
@RequestMapping(method={RequestMethod.GET,RequestMethod.POST})
2.3 窄化请求映射
- 在
class上
添加@RequestMapping(url)
指定通用请求前缀, 限制此类下的所有方法请求url
必须以请求前缀开头,通过此方法对url
进行分类管理。 - 例如:
@Controller
@RequestMapping("/items/")
public class ItemsController {
@Autowired
private ItemService itemService ;
@RequestMapping(value={"/list","/list2","/itemList"},method={RequestMethod.GET,RequestMethod.POST})
public ModelAndView showList(QueryVo queryVo,int[] ids){
List<Items> list = itemService.findAll();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("itemList", list);
modelAndView.setViewName("itemList");
return modelAndView;
}
- 访问的地址为:
/items/list
第3章 Controller方法的返回类型
-
Controller
方法的返回值类型有三种:ModelAndView
String
void
3.1 返回ModelAndView
-
controller
方法中定义ModelAndView
对象并返回,对象中可添加model
数据、指定view
。
3.2 返回void
- 当
Controller
方法的返回值类型为void
时,可以在controller
方法形参上定义request
和response
,使用request
和response
指定响应结果。 - 使用
request
转向页面:
request.getRequestDispatcher("页面路径").forward(request, response);
- 使用
response
页面重定向
response.sendRedirect("url")
3.3 返回类型为String
3.3.1 逻辑视图名
-
controller
方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。
//指定逻辑视图名,经过视图解析器解析为jsp物理路径:
/WEB-INF/jsp/item/editItem.jsp
return "item/editItem";
3.3.2 重定向
-
Contrller
方法返回结果重定向到一个url
地址,如下商品修改提交后重定向到商品查询方法,参数无法带到商品查询方法中。
//重定向到list.action地址,request无法带过去
return "redirect:list.action";
redirect
方式相当于response.sendRedirect()
,转发后浏览器的地址栏变为重定向后的地址,因为重定向即执行了一个新的request
和response
。-
由于新发起一个
request
原来的参数在转发时就不能传递到下一个url
,如果要传参数可以/item/list.action
后边加参数,如下:/item/list?...&…..
3.3.3 转发
-
controller
方法执行后继续执行另一个controller
方法,如下商品修改提交后转向到商品修改页面,修改商品的id
参数可以带到商品修改方法中。
//结果转发到editItem.action,request可以带过去
return "forward:editItem.action";
-
forward
方式相当于request.getRequestDispatcher().forward(request,response)
,转发后浏览器地址栏还是原来的地址。转发并没有执行新的request
和response
,而是和转发前的请求共用一个request
和response
。所以转发前请求的参数在转发后仍然可以读取到。
第4章 文件上传【重要】
4.1 文件上传页面三要素
- 表单的提交方式
method
一定是post
- 表单
enctype
的值一定是multipart/form-data
- 文件上传项
input
的类型一定是file
4.2 SpringMVC上传文件注意点
- 需要添加两个
jar
:commons-io.jar
和commons-fileupload.jar
。 - 在
SpringMVC
容器中配置文件解析器 - 绑定参数类型是
MultipartFile
,参数的名称要和页面input
属性值保持一致。
4.3 开始上传
4.3.1 场景设计
- 在商品展示页面单击修改按钮后跳转到修改页面
- 修改页面
4.3.2 导入文件上传的jar包
4.3.3 修改页面的jsp代码
WebContent/jsp/editItem.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>修改商品信息</title>
</head>
<body>
<!-- 上传图片是需要指定属性 enctype="multipart/form-data" -->
<!-- <form id="itemForm" action="" method="post" enctype="multipart/form-data"> -->
<form id="itemForm" action="${pageContext.request.contextPath }/updateitem.action"
enctype="multipart/form-data" method="post">
<input type="hidden" name="id" value="${item.id }" /> 修改商品信息:
<table width="100%" border=1>
<tr>
<td>商品名称</td>
<td><input type="text" name="name" value="${item.name }" /></td>
</tr>
<tr>
<td>商品价格</td>
<td><input type="text" name="price" value="${item.price }" /></td>
</tr>
<%--
<tr>
<td>商品生产日期</td>
<td><input type="text" name="createtime"
value="<fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/>" /></td>
</tr>
--%>
<tr>
<td>商品图片</td>
<td>
<c:if test="${item.pic !=null}">
<img src="/pic/${item.pic}" width=100 height=100/>
<br/>
</c:if>
<input type="file" name="pictureFile"/>
</td>
</tr>
<tr>
<td>商品简介</td>
<td><textarea rows="3" cols="30" name="detail">${item.detail }</textarea>
</td>
</tr>
<tr>
<td colspan="2" align="center"><input type="submit" value="提交" />
</td>
</tr>
</table>
</form>
</body>
</html>
4.3.4 配置文件 解析器
- 在
springmvc.xml
中添加如下配置:
<!-- 文件上传 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置上传文件的最大尺寸为5MB -->
<property name="maxUploadSize">
<value>5242880</value>
</property>
</bean>
4.3.5 编写Controller
修改商品的方法,其中包括文件上传
// 商品修改
@RequestMapping("updateitem")
public String updateitem(Items item, MultipartFile pictureFile, Model model) throws IllegalStateException, IOException {
// 获取上传文件的完整名称(带扩展名)
String originalFilename = pictureFile.getOriginalFilename();
String fileName = UUID.randomUUID().toString(); // 创建一个随机数,作为即将保存图片的名字
// 获取文件的扩展名
String ext = originalFilename.substring(originalFilename.lastIndexOf("."));
pictureFile.transferTo(new File("d:\\upload\\"+fileName+ext)); // 文件保存
item.setPic(fileName+ext);
itemService.update(item);
return "redirect:list.action";
}
4.3.6 测试
- 在修改页面填写修改信息,点击提交后页面跳转到商品展示页。上传的图片保存到了磁盘上,数据库中对应的记录更新。
4.3.7 注意
-
file
的name
与controller
的形参保持一致 -
form
添加enctype="multipart/form-data"
- 文件上传的目录需要提前创建好
- 需要在
springmvc.xml
中配置文件解析器
第5章 异常处理器
5.1 异常处理的思路
-
Web
工程中一般都是三层结构,如果系统中无论那一层发送异常都可以直接向上抛出,而不用处理。如下图所示,系统的dao
、service
、controller
出现异常后都通过throws Exception
向上抛出,最后由springmvc
前端控制器交由异常处理器进行异常处理。
5.2 自定义异常处理器的举例
5.2.1 场景描述
- 在商品展示修改页面,当用户访问到
id
不存在的路径时出现异常。
5.2.2 自定义异常处理器
com.itzhouq.ssm.exception.MyHandlerExceptionResolver
package com.itzhouq.ssm.exception;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
/**
* 自定义异常处理器
* @author itzhouq
*
*/
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object method,
Exception exception) {
// 跳转到错误页面
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("errorMessage", exception.getMessage());
modelAndView.setViewName("error");
return modelAndView;
}
}
5.2.3 编写错误页面
WebContent/jsp/error.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>由于您的操作造成系统异常,错误信息如下,请转告开发工程师:<br/></h1>
${errorMessage }
</body>
</html>
5.2.4 配置异常处理器
- 在
springmvc.xml
中添加以下配置:
<!-- 异常处理器 -->
<bean id="handlerExceptionResolver" class="com.itzhouq.ssm.exception.MyHandlerExceptionResolver"/>
5.2.5 模拟异常
com.itzhouq.ssm.controller.ItemsController
// 展示修改页面
@RequestMapping("/itemEdit")
public String itemEdit(@RequestParam(value="id",required=false,defaultValue="1")int itemId,HttpServletRequest request, HttpServletResponse response,Model model){
Items items = itemService.findById(itemId);
// 模拟异常
if(items == null) {
int i = 1 / 0;
}
model.addAttribute("item", items);
// 逻辑视图:jsp的路径
return "editItem";
}
5.2.6 测试
- 访问
http://localhost/Spring-mybatis-test/itemEdit.action?id=9
,id=9
是不存在的。
第6章 JSON数据交互
6.1 场景设计
- 商品修改页面,修改商品信息后,如果需要使用
Ajax
的异步提交方式,需要和json
交互。
6.2 在修改页面引入JS支持
- 在
WebContent/jsp/editItem.jsp
引入js
<script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery-1.4.4.min.js"></script>
6.3 修改提交方式
<input type="button" onclick="save()" value="提交" />
6.4 编写save函数和ajax代码
-
$("#itemForm").serialize()
可以将表单数据序列化,将数据封装成对象
<script type="text/javascript">
function save(){
// ajax异步提交
// alert($("#itemForm").serialize());
$.ajax({
url:'${pageContext.request.contextPath }/updateitemAjax.action',
type:'post',
data:$("#itemForm").serialize(),
dataType:'json',
success:function(data){
// data :{"success":true|false,"message":"操作成功"|“操作失败”}
if(data.success){
location.href="${pageContext.request.contextPath }/list.action";
}else{
alert(data.message);
}
}
})
}
</script>
6.5 在Controller中编写updateitemAjax方法
// 使用Ajax异步提交方式的修改
@RequestMapping("/updateitemAjax")
@ResponseBody // 作用:把即将返回的对象转化json字符串并且写道浏览器
public Map updateitemAjax(Items item) {
// data :{"success":true|false,"message":"操作成功"|“操作失败”}
Map<String,Object> map = new HashMap<String,Object>();
try {
itemService.update(item);
map.put("success", true);
map.put("message", "操作成功");
} catch (Exception e) {
map.put("success", false);
map.put("message", "操作失败");
// e.printStackTrace();
}
return map;
}
-
@ResponseBody // 作用:把即将返回的对象转化json字符串并且写道浏览器
,这个注解需要引入jar
包
6.6 测试
- 访问
http://localhost/Spring-mybatis-test/list.action
点击修改按钮后,修改信息,提交后页面跳转,后台数据map
中保存了信息{success=true, message=操作成功}
。
第7章 RESTful支持
7.1 什么是restful风格
-
Restful
就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格,是对hhtp
协议的诠释。 - 可以简单理解为精简的
url
地址:比如http://localhost/Spring-mybatis-test/itemEdit.action?id=2
可以访问商品修改页,我现在想让访问的路径精简为http://localhost/Spring-mybatis-test/itemEdit/2
。
7.2 案例:实现对商品访问页的restful风格支持
7.2.1 实现思路
- 修改
web.xml
中前端控制器<url-pattern>/</url-pattern>
的配置 - 修改方法的
@RequestMapping
- 处理静态资源
7.2.2 修改前端控制器配置
- 之前的前端控制器的配置如下:
<!-- 前端控制器 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
- 也就说请求的
url
必须是*.action
这样的格式,才会进入前端控制器。需要修改为:
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!-- / 不包含jsp -->
<!-- /* 包含jsp -->
<url-pattern>/</url-pattern>
</servlet-mapping>
7.2.3 修改Controller方法的@RequestMapping:URL模板映射
com.itzhouq.ssm.controller.ItemsController
// 展示修改页面
@RequestMapping("/itemEdit/{id}")
public String itemEdit(@PathVariable("id")int itemId,HttpServletRequest request, HttpServletResponse response,Model model){
Items items = itemService.findById(itemId);
model.addAttribute("item", items);
// 逻辑视图:jsp的路径
return "editItem";
}
- 到了这一步就可以通过精简的路径访问了。
- 但是控制台提示找不到
js
。这是由于我们配置前端控制的时候<url-pattern>/</url-pattern>
使用的是/
,也就是除jsp
外的所有请求都会进入springmvc
容器中,进而去控制器中寻找响应的方法,但是没有方法映射静态资源,所以就报404。 - 所以现在要处理静态资源,让静态资源去
Webcontent
下寻找资源而不是进入springmvc
容器中。
7.2.4 处理静态资源访问
- 在
springmvc.xml
中配置
<!-- 配置静态资源 -->
<!-- location:请求地址 mapping:映射的位置 -->
<mvc:resources location="/js/" mapping="/js/**"></mvc:resources>
第8章 拦截器
8.1 SpringMVC的拦截器
-
SpringMVC
的处理器拦截器类似于Servlet
开发中的过滤器Filter
,用于对处理器进行预处理和后处理。
8.2 拦截器的定义
定义拦截器要实现接口
HandlerInterceptor
com.itzhouq.ssm.interceptor.MyHandlerInterceptor1
public class MyHandlerInterceptor1 implements HandlerInterceptor {
/**
* controller执行前调用此方法
* 返回true表示继续执行,返回false中止执行
* 这里可以加入登录校验、权限拦截等
*/
@Override
public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
System.out.println("执行了MyHandlerInterceptor1的前置方法");
return true;
}
/**
* controller执行后但未返回视图前调用此方法
* 这里可在返回用户前对模型数据进行加工处理,比如这里加入公用信息以便页面显示
*/
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
throws Exception {
System.out.println("执行了MyHandlerInterceptor1的执行方法");
}
/**
* controller执行后且视图返回后调用此方法
* 这里可得到执行controller时的异常信息
* 这里可记录操作日志,资源清理等
*/
@Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
System.out.println("执行了MyHandlerInterceptor1的后置方法");
}
}
com.itzhouq.ssm.interceptor.MyHandlerInterceptor2
public class MyHandlerInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
System.out.println("执行了MyHandlerInterceptor2的前置方法");
return true;
}
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
throws Exception {
System.out.println("执行了MyHandlerInterceptor2的执行方法");
}
@Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
System.out.println("执行了MyHandlerInterceptor2的后置方法");
}
}
8.3 拦截器配置
8.3.1 针对所有mapping配置全局拦截器
- 在
springmvc.xml
中添加配置
<!-- 针对所有mapping配置全局拦截器 /**表示所有的拦截器都可以进入 -->
<mvc:interceptors>
<!-- 多个拦截器按照顺序执行 -->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.itzhouq.ssm.interceptor.MyHandlerInterceptor1"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.itzhouq.ssm.interceptor.MyHandlerInterceptor2"/>
</mvc:interceptor>
</mvc:interceptors>
8.3.2 针对某种mapping配置拦截器
- 参考代码
<bean
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="handlerInterceptor1"/>
<ref bean="handlerInterceptor2"/>
</list>
</property>
</bean>
<bean id="handlerInterceptor1" class="springmvc.intercapter.HandlerInterceptor1"/>
<bean id="handlerInterceptor2" class="springmvc.intercapter.HandlerInterceptor2"/>
8.4 测试
- 访问
http://localhost/Spring-mybatis-test/itemEdit/2
,查看后台拦截器的执行顺序
执行了MyHandlerInterceptor1的前置方法
执行了MyHandlerInterceptor2的前置方法
执行了MyHandlerInterceptor2的执行方法
执行了MyHandlerInterceptor1的执行方法
执行了MyHandlerInterceptor2的后置方法
执行了MyHandlerInterceptor1的后置方法
总结:
preHandle按拦截器定义顺序调用
postHandler按拦截器定义逆序调用
afterCompletion按拦截器定义逆序调用
postHandler在拦截器链内所有拦截器返成功调用
afterCompletion只有preHandle返回true才调用
8.5 拦截器的应用
8.5.1 用户身份认证
Public class LoginInterceptor implements HandlerInterceptor{
@Override
Public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//如果是登录页面则放行
if(request.getRequestURI().indexOf("login.action")>=0){
return true;
}
HttpSession session = request.getSession();
//如果用户已登录也放行
if(session.getAttribute("user")!=null){
return true;
}
//用户没有登录挑战到登录页面
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
return false;
}
}
8.5.2 用户登录Controller
//登陆页面
@RequestMapping("/login")
public String login(Model model)throws Exception{
return "login";
}
//登陆提交
//userid:用户账号,pwd:密码
@RequestMapping("/loginsubmit")
public String loginsubmit(HttpSession session,String userid,String pwd)throws Exception{
//向session记录用户身份信息
session.setAttribute("activeUser", userid);
return "redirect:item/queryItem.action";
}
//退出
@RequestMapping("/logout")
public String logout(HttpSession session)throws Exception{
//session过期
session.invalidate();
return "redirect:item/queryItem.action";
}