1、包装类型pojo参数绑定
2.1、需求
批量修改数据
集合类型的参数绑定
第一步:在包装类ItemsVo中定义一个List<>属性用来批量保存商品信息
第二步:在controller中编写批量修改的方法
@RequestMapping("/editItems")
public String edtiItems(ItemsVo itemsVo) {
// TODO Auto-generated method stub
//修改操作暂不实现 。。。
System.out.println(itemsVo);
return "redirect:itemsList.action";
}
第三步:修改jsp页面中 的name属性
注意这里的命名方式
<c:forEach items="${itemsList}" var="item" varStatus="index">
<tr>
<td><input style="border: 0px;text-align: center;" type="text" name="itemsCustomes[${index.index}].id" value="${item.id}" readonly="readonly"> </td>
<td><input type="text" style="border: 0px;text-align: center;" name="itemsCustomes[${index.index}].name" value="${item.name}"></td>
<td style="width: 150px;height: 150px;"> </td>
<td><input type="text" style="border: 0px;text-align: center;" name="itemsCustomes[${index.index}].createtime"
value="<fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd mm:HH:ss"/>"></td>
<td><input type="text" style="border: 0px;text-align: center;" name="itemsCustomes[${index.index}].price" value="${item.price}"></td>
<td><input type="text" style="border: 0px;text-align: center;" name="itemsCustomes[${index.index}].detail" value="${item.detail}"></td>
<td><a href="${pageContext.request.contextPath}/items/selectItemsById.action?id=${item.id}">修改</a></td>
</tr>
</c:forEach>
测试结果:
2.2、Map类型的参数绑定
3、服务端校验
3.1、校验理解
项目中使用较多的是前端的校验,如页面js校验,对于安全要求要求较高建议在服务端进行数据校验。
服务端校验:
控制层controller:校验页面请求的参数的合法性。在服务端控制层controller校验,不区分客户端类型(浏览器、手机客户端、远程调用)
业务层service(使用较多 ):主要校验关键业务参数,仅限于service接口中使用的参数。
持久层:一般不校验
3.2、springmvc校验
springmvc使用的是hibernate的校验框架validation(和hiberanate没有任何关系)
校验思路:
页面提交请求的参数,提交到controller方法中,使用validation进行校验,如果校验出错,将错误信息展示到页面显示中。
3.2.1、需求:商品修改(商品名的长度和生产日期非空),添加校验,如果信息出错,在商品页面显示错误信息。
3.2.2、实现
1)导入jar包
2)在springmvc.xml中配置校验器,将校验器注入到处理器适配器中
<!-- 校验器 -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<!-- hibernate校验器 -->
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
<!-- 指定校验使用的资源文件,如果不指定默认使用classpath下的ValidationMessages.properties -->
<property name="validationMessageSource" ref="messageSource"/>
</bean>
<!-- 校验错误信息配置文件 -->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<!-- 资源文件名 -->
<property name="basenames">
<list>
<value>classpath:CustomValidationMessages</value>
</list>
</property>
<!-- 资源文件编码格式 -->
<property name="fileEncodings" value="utf-8"/>
<!-- 对资源文件缓存时间,单位秒 -->
<peoperty name="cacheSeconds" valie="120"/>
</bean>
3)将校验器注入到适配器中(注解驱动方式)
3.1)将校验器注入到适配器中(非注解驱动方式)
4)创建错误信息属性文件
5)在pojo中添加校验规则
在ItemsCustome.java(pojo)中添加校验规则
6)在Controller中捕捉错误信息
测试:
3.3、校验分组
3.3.1、问题:在pojo中定义校验规则,而pojo被多个controller公用,但是每个controller的校验需求是不同的
3.3.2、解决方法:定义校验分组(其实就是一个接口),分组中定义有不同的规则,每个controller方法根据需求使用不同的校验分组
根据不同需求指定校验规则
1)定义接口
2)在校验规则中添加分组
3)在controller中只用校验分组
4、数据回显
当从当前页面离开后再回到当前页面时,当前页面中的表单数据还存在;
例:当修改信息时,修改出错,回到修改页面,修改页面原本的数据应该还在
4.1、数据回显的实现
数据回显实际上就是将需要会显得数据保存到Request域中
4.1.1、springmvc默认对pojo数据进行回显,
pojo传入controller方法后,springmvc自动及那个pojo数据到request域而key就等于pojo的类名首字母小写
使用@ModelAttribute(“key”)指定pojo数据回显时数据在request域的key,
@ModelAttribute(“key”)还可以将方法的返回值传回页面保存到Request域
4.2、使用Model回显
4.3、简单类型的数据回显只能使用model
5、异常处理思路
5.1、系统中异常包括两类,预期异常和运行时异常RuntimeException,前者通过异常捕捉从而获得异常信息,后者主要通过规范代码开发、测试手段减少云心是异常的发生。
系统的dao、service、controller都通过throws Exception向上抛出,最后由springmvc前端控制器交由异常处理器进行异常处理。如下图:
springmvc提供全局异常处理器(一个系统只有一个异常处理器)进行统一异常处理。
5.2、自定义异常类
对不同的异常类型定义异常类型
5.2.1、自定义异常类
/**
* 自定义异常类
* 针对预期的异常,需要在程序中抛出异常
* @author guai
*
*/
public class CustomizeException extends Exception{
/**
*
*/
private static final long serialVersionUID = 1L;
//异常信息
private String message;
public CustomizeException(String message) {
super();
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
5.3、全局异常处理器
思路:当系统遇到异常,controller会将全局异常抛给前端控制器,前端控制器调用全局异常处理器。
全局异常处理器处理思路:
解析出异常类型
如果该异常类型是系统自定义的异常,直接取出异常处理,在错误页面展示。
否则:异常类型不是系统自定义的异常,构造一个自定义的异常类型(信息为“未知错误,请联系管理员”)
5.3.1、全局异常处理器需要继承HandlerExceptionResolver接口
/**
* 全局异常处理类
* @author guai
*
*/
public class GlobalExceptionProcess implements HandlerExceptionResolver{
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
//handler就是处理器适配器要执行的Handler对象(只有method)
//ex就是系统抛出的异常
// TODO Auto-generated method stub
CustomizeException ce=null;
if(ex instanceof CustomizeException) {
ce=(CustomizeException)ex;
}else {
//如果不是系统自定义异常(则属于未知异常),构造一个自定义的异常类型
ce=new CustomizeException("未知异常!!");
}
ModelAndView modelAndView=new ModelAndView();
modelAndView.addObject("errorMessage",ce.getMessage());
modelAndView.setViewName("error");
return modelAndView;
}
}
5.3.2、搞个错误页面
5.4、在springmvc.xml中配置全局异常处理器
5.5、测试
如果是程序中手动抛出的异常,在错误页面中显示自定义的异常信息,如果不是手动抛出的异常,说明是一个运行时异常,在错误页面显示未知错误
5.5.1、根据id查询商品信息的controller中抛出异常
测试结果:
6、springmvc中对多部件类型解析
6.1、需求
在修改信息页面,添加上传商品图片功能
6.2、springmvc对多部件类型解析
在页面form中提交enctype=“multipart/form-data”的数据时,需要springmvc对multipart类型的数据进行解析。
要在springmvc中配置multipart类型解析器
6.3、实现:
在springmvc.xml中配置multipart类型解析器
<!-- 文件上传 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置文件的最大尺寸 -->
<property name="maxUploadSize">
<value>5252880</value>
</property>
</bean>
注意:加入上传图片的jar包
6.4、创建图片虚拟目录 存储图片
配置虚拟目录
注意:在图片虚拟目录中,一定将图片分级创建(提高i/o性能),一般采用按日期分级创建。
6.5、上传图片代码
编辑 修改商品信息的controller
if(items_pic!=null) {
//存储图片的物理路径
String pic_path="D:\\Software\\Tomcat9.0\\images";
//获取原始名称
String imgFilename=items_pic.getOriginalFilename();
//新的图片名称
String newfilename=UUID.randomUUID()+imgFilename.substring(imgFilename.lastIndexOf("."));
//新图片
File newFile=new File(pic_path+newfilename);
//将内存中的数据写入磁盘
items_pic.transferTo(newFile);
//将新图片名称存到itemsCustome中
itemsCustome.setPic(newfilename);
}
6.6、jsp页面
测试: 是 iu 呀
7、Json数据交互
7.1、为什么要进行json数据交互
json数据格式再接口调用中,html而页面中较常用,json格式比较简单,解析还比较方便。
比如:webservice接口,传输的就是json数据
7.2、springmvc进行json交互的两种方式
1)请求json输出json要求请求的时json串,所以在前端页面中需要将请求的内容转化成jsong,不常用
2)请求key/value值,输出json,此方法较为常用
7.3、环境准备
需要导入的jar包
7.4、在springmvc.xml中配置
若使用注解驱动,可以不用配置
7.5、创建jsp页面
使用jquery的ajax
<%@ 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>json交互测试</title>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery.min.js"></script>
<script type="text/javascript">
//请求json,输出json
function requestJson(){
$.ajax({
type:'post',
url:'${pageContext.request.contextPath}/items/requestJson.action',
//设置数据格式
contentType:'application/json',
data:'{"name":"手机","price":9999}',
//请求成功 返回的data数据
success:function(data){
alert(data.name+data.price);
}
});
}
//请求key/value,输出时json
function responseJson(){
$.ajax({
type:'post',
url:'${pageContext.request.contextPath}/items/responseJson.action',
//默认格式为key/value请求
data:"name=手机&price=9999",
//请求成功 返回的data数据
success:function(data){
alert(data.name+data.price);
}
});
}
</script>
</head>
<body>
<input type="button"onclick="requestJson()" value="请求json"/>
<input type="button"onclick="responseJson()" value="响应json"/>
</body>
</html>
7.6、创建controller方法
// 请求json
//@ResponseBody 指定响应数据为json
//@RequestBody 指定请求数据为json
@ResponseBody
@RequestMapping(value = "/requestJson", method = { RequestMethod.POST })
public ItemsCustome requestJson(@RequestBody ItemsCustome itemsCustome) {
System.out.println(itemsCustome);
return itemsCustome;
}
// 响应json
@ResponseBody
@RequestMapping(value = "/responseJson", method = { RequestMethod.POST })
public ItemsCustome responseJson(ItemsCustome itemsCustome) {
System.out.println(itemsCustome);
return itemsCustome;
}
4.7、测试:
5、RESTful Representational State Trensfer
5.1、什么是RESTful
5.2、RESTful是一种开发理念
5.2.1、对url进行规范,写RESTful格式的url
非REST的url:http://queryItems.action?id=001&type=T01
REST的url:http://queryItems.action/001/T01
特点:url简洁
不管是删除、添加、更新,,,使用的url是一致的,如果要删除,设置http方法设置为delete,添加时甚至成为put
缺点:需要在controller判断到底进行哪种操作,当需求复杂时,controller中的逻辑判断会十分麻烦。
5.3、状态转换
5.4、REST简单应用
需求,查询商品信息,返回json数据
定义controller方法,进行REST风格的url映射,将查询商品信息的id传入controller
输出json
5.6、controller方法:
@ResponseBody
@RequestMapping("/restFulTest/{id}/{name}")
//{id} 表示占位符与@PathVariable中的参数对应
public ItemsCustome selectItemsCustome(@PathVariable("id") int id,@PathVariable("name") String name) {
ItemsCustome ic=new ItemsCustome();
ic.setId(id);
ic.setName(name);
return ic;
}
5.7、在web.xml中配置RESTful专用前端控制器
<!-- 配置rest前端控制器 -->
<servlet>
<servlet-name>springmvc-rest</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置要扫描的springmvc配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc/springmvc.xml</param-value>
</init-param>
</servlet>
<!-- <url-pattern>:指定相对于Servlet的URL的路径。该路径相对于web应用程序上下文的根路径。<servlet-mapping>将URL模式映射到某个Servlet,即该Servlet处理的URL。! -->
<servlet-mapping>
<servlet-name>springmvc-rest</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
测试:
5.8、问题
使用/配置前端控制器的url-partten时,对静态资源的解析出现问题。无法找到静态资源
即下面这种情况
此时需要在springmvc.xml中配置对静态资源的解析
配置后:
6、拦截器
6.1、定义拦截器
需要继承HandlerInterceptor接口
实现接口的三个方法:
//进入Handler前执行
//用于身份认证和身份授权 若认证不通过当前用户没有成功登录,需要此方法拦截不向下执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// TODO Auto-generated method stub
//return false 表示拦截,不向下执行
//return true表示放行
return false;
}
//进入Handler方法之后返回modelAndView之前
//应用场景从modelAndView出发,填充公用的模型数据在这里传到视图例如:菜单栏,也可以指定统一视图
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
//执行完Handler方法之后
//应用场景:统一的异常处理,日志处理
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
6.2、拦截器配置
springmvc不是全局的而是针对HandlerMapping进行拦截的。
1、如果在某个HandlerMapping中配置拦截器,经过该HandlerMapping映射成功的Handler时才拦截。
2、springmvc也可以配置类似全局的拦截器,springmvc框架会将配置的类似全局的拦截器注入到每个HandllerMapping中。
<!-- 拦截器 -->
<mvc:interceptors>
<!-- 多个拦截器顺序执行 -->
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.shuai.interceptor.ControllerInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.shuai.interceptor.ControllerInterceptor2" />
</mvc:interceptor>
</mvc:interceptors>
3、测试多个拦截器在执行时的执行顺序
在拦截方法中加入
System.out.println(“HandlerInterceptor1/2—after/pre/postHandle”);
。。。。。。
两个拦截器都放行时的测试结果:
拦截器一放行,拦截器二拦截时测试结果:
拦截器一不放行,拦截器二不放行,
测试结果:
拦截器二不执行
6.3、小结:
根据测试结果,拦截器在应用时的注意事项:
对于统一日志处理的拦截器,需要拦截器preHandle一定要放行,且将它放在拦截器链接中第一个位置
对于登录认证的拦截器,放在拦截器链接中第一个位置。
对于权限校验的拦截器应该放在登录认证的拦截器之后。
6.4、登录认证
6.4.1、用户请求url
6.4.2、拦截器进行拦截校验
如果请求的url是不需要登录即可访问的url,允许放行
如果用户session不存在跳转到登录页面
如果用户session存在放行,继续操作
6.4.3、登录controller方法
//登录
@RequestMapping("login")
public String login(HttpSession session,String username,String password)throws Exception{
//调用service运行用户身份认证
session.setAttribute("username", username);
//重定向到用户界面
return "redirect:/items/selectItems.action";
}
6.4.4、登录认证拦截实现
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// TODO Auto-generated method stub
//获取请求的url
String url=request.getRequestURI();
//判断url是否是公开地址,实际应用中将url配在配置文件中
if(url.indexOf("login.action")>=0) {
//如果登录放行
return true;
}
//判断session
HttpSession session=request.getSession();
//从session中取出身份信息
String username=(String)session.getAttribute("username");
if(username!=null) {
return true;
}
request.getRequestDispatcher("/login.jsp ").forward(request, response);
return false;
}
6.4.5、配置拦截器
测试:当没有登陆时想要进入查询页面会直接跳到登录页面
登陆成功后: