1. Spring MVC拦截器应用
1.1. Spring MVC拦截器概述
拦截器是SpringMVC中的一个核心应用组件,主要用于处理多个
Controller的共性问题.当我们的请求由DispatcherServlet派发
到具体Controller之前首先要执行拦截器中一些相关方法,在这些
方法中可以对请求进行相应预处理(例如权限检测,参数验证),这些方法可以决定对这个请求进行拦截还是放行.
通过spring mvc 架构图分析,拦截器在Spring MVC中处理流程中的一个位置
回顾Spring MVC详细架构图
各组件在企业架构中定位
思考:
1)假如对请求数据进行编码,是应在过滤器还是拦截器?
推荐使用过滤器.
2)拦截器有哪些有些应用场景呢?(处理后台控制业务的共性)
a)进行身份认证(判定用户是否是合法用户)
b)进行系统监控
c)进行日志记录
d)..........
1.2. Spring MVC拦截器编写及基本配置
拦截器如何编写?
我们自己编写Spring MVC拦截器需要实现HandlerInterceptor接口或者继承此接口的实现类 HandlerInterceptorAdapter(继承这个类时可根据需求重写必要的方法)
例如定义一个拦截器,计算controller方法的执行时间
public class TimeInterceptor implements HandlerInterceptor { @Override public boolean preHandle( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("time.preHandle"); long startTime=System.nanoTime(); request.setAttribute("startTime", startTime); return true;//true表示放行,false表示拦截 } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("time.postHandle"); } @Override public void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("time.afterCompletion"); long endTime=System.nanoTime(); long startTime=(Long)request.getAttribute("startTime"); System.out.println("time:"+(endTime-startTime)); } }
这些方法的简易执行流程点如下图所示:
这些方法的详细的执行时间点如下:
我们自己的拦截器编写完成以后需要在spring配置文件中进行配置,,
例如:
<mvc:interceptors> <mvc:interceptor> <!-- 指定拦截哪些请求路径 --> <mvc:mapping path="/**"/> <!-- 排除要拦截的路径 --> <mvc:exclude-mapping path="/user/doLogin.do"/> <!— 拦截器(这个bean也可以通过ref方式引用,前提是在外面要通过xml或注解方式定义) --> <bean class="com.spring.TimeInterceptor"/> </mvc:interceptor> </mvc:interceptors>
1.3. Spring MVC拦截器链及配置实现
当我们系统中有多个拦截器时,这些拦截器可以构成一个拦截器链.其原理类似过滤器中的过滤链。在多个拦截器应用中仅当所有匹配的拦截器的preHandle()都执行之后,才会调用Controller中处理请求的方法,然后再执行所有匹配的拦截器的postHandler(),再执行所有匹配的拦截器的afterCompletion()。
在拦截器链中,各拦截器的执行先后顺序取决于配置文件中配置的节点的先后顺序!
例如:再定义一个日志拦截器
@Component public class LogInterceptor extends HandlerInterceptorAdapter{ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("log.preHandler"); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("log.afterCompletion"); } }
配置日志拦截器
<!-- 配置拦截器 (要注意标签的编写顺序)--> <mvc:interceptors> <!-- 当存在多个拦截器时,哪个拦截器的方法先执行,要看拦截器配置的顺序 --> <mvc:interceptor> <mvc:mapping path="/user/*"/> <ref bean="logInterceptor"/> </mvc:interceptor> <mvc:interceptor> <!-- 拦截所有请求(这些请求要交给拦截器处理) --> <mvc:mapping path="/**"/> <!-- 定制对哪些请求不拦截 (这些请求不需要拦截处理)--> <mvc:exclude-mapping path="/user/doLogin.do"/> <!-- 拦截器对象 --> <bean class="com.spring.TimeInterceptor"/> </mvc:interceptor> </mvc:interceptors>
具体执行顺序跟配置顺序有关,例如现有两个拦截器A,B,按顺序配置,其执行顺序为
1)A.preHandle
2)B.preHandle
3)执行controller中具体方法
4)B.postHandle
5)A.postHandle
6)B.afterCompletion
7)A.afterCompletion
执行顺序总结:先执行的后结束(参考架构图)
思考:
1) 多个拦截器时,其执行顺序有什么决定?(配置顺序)
2) 拦截器对象何时创建?(启动时创建)
3) 拦截器对象在内存中有几份?(一份)
4) 拦截器中存在变量共享时可能会有线程安全问题吗?(有)
5) Spring管理的Bean对象中存在变量共享时可能会有线程安全吗?(有)
6) 多个拦截器对象如何共享数据?
(通过request,session,ServletContext,)
7) 拦截器对象的生命周期何时结束?(容器销毁时)
8) 拦截器可以解决什么问题?(Controller中共性问题,例如日志处理,系统监控,...)
9) ......
2. Spring MVC异常处理
2.1. Spring MVC 异常概述
实际项目中我们经常会采用分层架构设计程序,每一层都可能会有异常,假如异常信息没有处理,可能会选择抛出,假如这些被抛出的异常与具体业务相关,那到控制层以后我们一般都进行相应的处理(处理方式应该相对友好)
在Spring mvc 项目中,边界出现异常以后我们通常会在Controller中进行处理,常用分层架构中的异常分析:
在分层架构中,异常可能会从DAO层逐步抛出到控制层,可以在控制层对异常进行相关处理。
2.2. Spring MVC 异常处理
在spring中处理异常时,通常会在Controller中定义具体的异常处理方法,这个方法上使用@HandlerException注解进行描述.例如在指定Controller中定义异常处理方法:
@ExceptionHandler(value=Exception.class) @ResponseBody public String handleException(Exception e){ System.out.println("局部异常处理"); return e.getMessage(); }
当多个异常处理类中很多异常处理的方式是相同的,还可以将这个异常处理方法提取到父类,以实现方法的重用。
假如不希望将共性的异常处理方法提取到父类,还可以在外部定义一个全局的异常处理类,这个类使用@ControllerAdvice注解进行修饰.然后在这个类中定义具体的异常处理方法,这些方法再使用@HandlerExcpeiton进行修饰,例如
@ControllerAdvice public class AdviceExceptionHandler { @ExceptionHandler(Throwable.class) @ResponseBody public String handlerException(Throwable e){ System.out.println("全局的异常处理"); return e.getMessage(); } }
Spring MVC中异常处理机制总结:
1) 为什么要进行异常处理?(提高系统的容错能力,让系统表现的更加友好)
2) Spring MVC中异常处理的方式?(局部的,全局的)
3) 局部方式异常处理如何实现?(Controller类中直接定义异常处理方法,使用@ExceptionHandler进行修饰)
4) 全局方式异常处理如何实现?(通过继承将共性提取到父类或者
通过@ControllerAdvice注解修饰类,然后类中定义异常处理方法)
5) 出现异常后,异常处理的具体机制是怎样的(首先会在当前Controller(包含父类)中查找有没有定义异常处理方法,假如有则查看注解中定义的异常类型是否与当前异常类型相匹配,假如匹配则直接处理,假如不匹配则查看当前类中有没有父类类型的异常处理方法与当前异常匹配,假如有则直接处理,没有则找全局异常处理。全局也没有则直接不友好的输出错误信息)
说明:实际项目中在controller的方法中除了要对正确的业务数据进行封装之外,还会对异常信息进行封装(例如封装到JsonResult),然后转换为json格式的字符串,输出到客户端。
3. Spring MVC 文件上传
3.1. 文件上传业务场景
在电子商务系统中对具体商品采用图片方式进行描述,这个图片需要上传到服务器等等。
3.2. Spring MVC 文件上传基本步骤
Step01: 添加文件上传相关依赖(jar包:commons-fileupload)
Step02: 配置对上传文件对象的解析(spring的核心配置文件)
Step03: 编写文件上传表单(请求方式为post,enctype必须为multipart/form-data)
Step04: 编写对应的控制层对象处理文件上传(通过mutilpartFile参数接收文件)
3.2.1. 添加依赖
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency>
3.2.2. 配置对象解析
在spring的核心配置文件中添加对象解析
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 配置文件编码处理 --> <property name="defaultEncoding" value="utf-8"/> <!-- 配置文件大小限制 --> <property name="maxUploadSize" value="4096000000"/> <!-- 配置内存缓冲区大小 --> <property name="maxInMemorySize" value="1024000"/> </bean>
说明:配置对象解析时,bean的id一定要指定,而且官方规定必须为multipartResolver
3.2.3. 定义upload.jsp
在WEB-INF/pages目录下创建一个upload.jsp,然后创建上传表单
<body> <h1>文件上传</h1> <h1>${msg}</h1> <form action="doUpload.do" method="post" enctype="multipart/form-data"> <input type="file" name="upfile"/> <br/> <input type="submit" value="upload"> </form> </body>
说明:定义表单时,提交方式必须为post,enctype必须为multipart/form-data
3.2.4. 定义UploadController
@RequestMapping("/upload/") @Controller public class UploadController { @RequestMapping("uploadUI") public String uploadUI(){ return "upload"; } @RequestMapping("doUpload") public ModelAndView doUpload(MultipartFile upfile) throws IOException{ //获取文件名以及文件大小,检测是否获得文件相关数据 String fileName=upfile.getOriginalFilename(); long size=upfile.getSize(); System.out.println(fileName+"/"+size); //构建文件目标对象,这个对象对应的文件路径必须是存在的或者通过file对象自己创建 File dest=new File("D:/SSMWORK/"+fileName); //transferto实现文件上传 upfile.transferTo(dest); //封装数据返回 ModelAndView mv=new ModelAndView("upload"); mv.addObject("msg", "upload ok"); return mv; } }
说明:在控制层方法接收请求中file类型的数据时需要借助MultipartFile 类型的参数对象,其参数名要求与表单中type为file的参数名相同。