一、异常处理器
springmvc在处理请求过程中出现异常信息交由异常处理器进行处理,自定义异常处理器可以实现一个系统的异常处理逻辑。(它负责捕获,将异常放到我们自己编写的处理类中)
Spring3.0中对异常的处理方法一共提供了两种:
① 实现HandlerExceptionResolver接口:可以实现全局异常控制,并且Spring已经提供了一个默认的实现类SimpleMappingExceptionResolver;
② 使用@ExceptionHandler注解:可以在Controller内部实现更个性化点异常处理方式,灵活性更高。
1、异常处理器思路
系统中异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。
系统的dao、service、controller出现都通过throws Exception向上抛出,最后由springmvc前端控制器交由异常处理器进行异常处理,SpringMVC提供全局异常处理器(一个系统只有一个异常处理器)进行统一异常处理。如下图:
2、异常处理器使用
● 测试准备
- 错误页面:用于显示异常信息
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <h1>系统发生异常了!</h1> <br /> <h1>异常信息</h1> <br /> <h2>${error }</h2> </body> </html>
- 自定义异常类:
为了区别不同的异常,通常根据异常类型进行区分,这里我们创建一个自定义系统异常。
如果controller、service、dao抛出此类异常说明是系统预期处理的异常信息。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.sikiedu.exception; public class MyException extends Exception { // 错误信息 private String msg; public MyException(String msg) { super(); this.msg = msg; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
- Service异常:发生异常,将异常向上抛出到控制器中
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.sikiedu.service; import 略... /** * @author 青 */ @Service public class ItemServiceImpl implements ItemService { @Autowired() private ItemMapper itemMapper; @Override public List<ItemInfo> selectByVo(ItemInfoVo vo) throws MyException { // 随机抛出一个异常 int x = (int) (Math.random() * 10 - 1); if (x % 2 == 0) { // 制造运行时异常 int i = 1 / 0; } else { // 抛出自定义异常 throw new MyException("【操作没错 - 但我就是不给你通过】"); } return itemMapper.selectByVo(vo); } }
(1) 通过实现HandlerExceptionResolver接口完成异常处理
- 编写异常处理器
package com.sikiedu.exception; import 略... /** * 自定义异常处理器实现 * @author 青 */ public class MyHandlerExceptionResolver implements HandlerExceptionResolver { /** * @param request、response:请求信息 * @param obj:异常对象的(全包名+类名+方法名) * @param e:异常信息 * @return mav:模型视图 */ @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object obj,Exception e) { ModelAndView mav = new ModelAndView(); // 获取异常信息 String message = ""; // 判断异常信息类型 if (e instanceof MyException) { // 执行自定义异常处理 - 需要强制类型转换 message = "HandlerExceptionResolver - 自定义异常 <br/>" + ((MyException) e).getMsg() + "<br/>" + e.getStackTrace()[0]; } else { // 运行时异常 message = "HandlerExceptionResolver - 运行时异常 <br/>" + e.getMessage() + "<br/>" + e.getStackTrace()[0]; } // 将异常信息输出到错误页面 mav.addObject("error", message); // 设置要跳转的视图名称 mav.setViewName("error"); return mav; } }
- springmvc.xml配置异常处理器:所有的Controller出现的异常, 都会在这个异常处理器中查找对应的处理方法
<!-- 配置异常处理器 --> <bean class="com.sikiedu.exception.MyHandlerExceptionResolver" />
- Controller控制器:捕获异常 交给异常处理器
package com.sikiedu.controller; import 略... /** * @author 青 */ @Controller @RequestMapping(value = "/item/") public class ItemController { @Autowired private ItemService itemService; @RequestMapping(value = "selectByVo.do") public String selectByVo(ItemInfoVo vo, Model model) throws MyException { // 异常发生,抛出
List<ItemInfo> itemList = itemService.selectByVo(vo); model.addAttribute("itemList", itemList); return "item_list"; } }
- 测试结果
(2) 通过@ExceptionHandler注解实现异常处理
- 开启注解:SpringMVC中默认是没有加装载HandlerExceptionResolver
<mvc:annotation-driven />
- Controller控制器:捕获异常 交给异常处理器
如果RequestMapping所注解的方法出现了异常,则自动寻找当前类中是否存在被@ExceptionHandler注解的方法,如果存在,则执行。
使用@ExceptionHandler处理,该注解只能处理当前控制器中的异常。
这个方法中可以加入Exception类型的参数,该参数即对于发生的异常对象。
package com.sikiedu.controller; import 略... /** * @author 青 */ @Controller @RequestMapping(value = "/item/") public class ItemController { @Autowired private ItemService itemService; @RequestMapping(value = "selectByVo.do") public String selectByVo(ItemInfoVo vo, Model model) throws MyException { // 发生异常,抛出
List<ItemInfo> itemList = itemService.selectByVo(vo); model.addAttribute("itemList", itemList); return "item_list"; } // 处理运行时异常、自定义异常在,这个方法中可以加入Exception类型的参数,该参数即对于发生的异常对象
@ExceptionHandler(value = { MyException.class, RuntimeException.class }) public String selectException(Exception e, Model model) { // 获取异常信息 String message = ""; // 判断异常信息类型 if (e instanceof MyException) { // 执行自定义异常处理 - 需要强制类型转换 message = "@ExceptionHandler处理 - 自定义异常 <br/>" + ((MyException) e).getMsg() + "<br/>" + e.getStackTrace()[0]; } else { // 运行时异常 message = "@ExceptionHandler处理 - 运行时异常 <br/>" + e.getMessage() + "<br/>" + e.getStackTrace()[0]; } // 将异常信息输出到错误页面 model.addAttribute("error", message); // 视图名 return "error"; } }
- 测试结果:
(3) 通过(@ControllerAdvice + @ ExceptionHandler)注解配置全局异常 - 推荐使用★
@ControllerAdvice:相当于是AOP,面向切面,给每一个controller都横插了该处理类。
通过@ControllerAdvice注解使@ExceptionHandler异常处理注解应用到所有使用@RequestMapping的方法上;
- 开启注解:SpringMVC中默认是没有加装载HandlerExceptionResolver
<mvc:annotation-driven />
- 全局异常处理类:
package com.sikiedu.exception; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; /** * 自定义全局异常类 * @author 青 */ @ControllerAdvice public class MyGlobalException { // 处理运行时异常 @ExceptionHandler(value = RuntimeException.class) String runtime(RuntimeException e, Model model) { // 将异常信息输出 model.addAttribute("error", "@ControllerAdvice+@ExceptionHandler处理 - 运行时异常 </br>" + e.getMessage() + "</br>" + e.getStackTrace()[0]); // 视图名 return "error"; } // 处理自定义异常 @ExceptionHandler(value = MyException.class) String myHandler(MyException e, Model model) { // 将异常信息输出 model.addAttribute("error","@ControllerAdvice+@ExceptionHandler处理 - 自定义异常 </br>" + e.getMsg() + "</br>" + e.getStackTrace()[0]); // 视图名 return "error"; } }
- Controller控制器:捕获异常 交给异常处理器
package com.sikiedu.controller; import 略... /** * @author 青 */ @Controller @RequestMapping(value = "/item/") public class ItemController { @Autowired private ItemService itemService; @RequestMapping(value = "selectByVo.do") public String selectByVo(ItemInfoVo vo, Model model) throws MyException { // 发生异常,抛出 List<ItemInfo> itemList = itemService.selectByVo(vo); model.addAttribute("itemList", itemList); return "item_list"; } }
- 测试结果
二、拦截器Interceptor
SpringMVC中的拦截器可以是运行在控制器(Controller)之前的组件,可以设置拦截器应用于哪些请求路径,当发生这些请求时,拦截器会自动执行,在执行过程中,可以对请求相关数据进行判断,选择阻止继续向后执行,或选择放行。
注意:拦截器是一个若干种请求都会经历的执行过程,但是,并不一定需要阻止继续运行,只要是若干种请求都需要做相同的事情,也许每种请求的处理过程都是选择放行,也可以使用拦截器。
- 作用:权限检查,日志记录,性能检测等;
用户可以自己定义一些拦截器来实现特定的功能。例:访问特定页面前验证用户是否登陆等;
- 拦截器链:HandlerExecutionChain
拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
- 拦截器与过滤器的区别:
① 过滤器:是 servlet 规范中的一部分, 任何 java web 工程都可以使用。 拦截器:是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
② 过滤器:在 url-pattern 中配置了/*之后,可以对所有要访问的资源拦截。
拦截器:它是只会拦截访问的控制器方法,如果访问的是 jsp、html、css、image 或者 js 是不会进行拦截的。
1、主要相关类和方法
● HandlerExecutionChain:该类主要由 handler 和 handler interceptors 组成
![](https://img2018.cnblogs.com/i-beta/1427429/202001/1427429-20200105110648380-1525368017.png)
HandlerMapping类通过getHandler方法会调用到该类
● HandlerInterceptor
Spring MVC中对于一个请求可以添加多个拦截器,而这个拦截器集合中会链式调用这些拦截器。每个拦截器会执行固定顺序的方法,而这些方法就定义在HandlerInterceptor类中。
这是拦截器的一个基础接口,里面有三个方法
- boolean preHandle ( HttpServletRequest request,HttpServletResponse response,Object handler ) throws Exception
使用时机:预处理,在拦截方法前执行
应用场景:可以在该方法中放入一些初始化的操作,比如权限验证,日志管理等
注意:该方法的返回值是boolean类型,若返回值为true,则继续调用后面的拦截器和目标方法,若返回为false,则不会调用后面的拦截器和目标方法,表示请求结束
- void postHandle ( HttpServletRequest request,HttpServletResponse response,Object handler,ModelAndView modelAndView )throws Exception
使用时机:后处理,在调用目标方法之后,渲染视图之前被调用。具体来说,是在调用了Controller中定义的方法之后,但在DispatcherServlet 处理视图返回渲染结果之前被调用。
应用场景:根据使用的时机就可以知道,该方法可以对Controller处理之后ModelAndView进行操作
注意:当有多个interceptor的时候,对于preHandler的调用顺序和postHandler的调用顺序是恰恰相反的。
- void afterCompletion ( HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex ) throws Exception
使用时机:渲染后处理,在页面渲染后执行;
应用场景:释放资源
2、拦截器的执行流程
3、开发拦截器
(1) 自定义拦截器
自定义拦截器必须要实现HandlerInterceptor接口。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("LoginInterceptor.preHandle()"); return false; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("LoginInterceptor.postHandle()"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("LoginInterceptor.afterCompletion()"); } }
接口方法:
① preHandle方法:在调用处理器之前调用该方法,如果该方法返回true则请求继续向下进行,否则请求不会继续向下进行,处理器也不会调用;
② postHandle方法:在调用完处理器后调用该方法;
③ afterCompletion方法:只要该拦截器中的preHandle方法返回true,该方法就会被调用;
在拦截器的3个方法中,只有preHandle()方法是运行在控制器(Controller)之前的,另2个方法是运行在控制器之后的,所以,只有preHandle()具有真正意义的“拦截”功能,
该方法的返回值是boolean类型的,当返回true时表示放行,返回false时将阻止继续向后执行,即控制器并不会被执行;
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String URL = request.getRequestURI(); if (!URL.contains("login")) { // 不是登录请求 - 拦截 Object user = request.getSession().getAttribute("user"); if (user == null) { // 没用登录 - 重定向到登录页面/请求 response.sendRedirect(request.getContextPath() + "/login/login.do"); return false; } } return true; } }
!!!注意:即使已经决定了重定向,还是需要return false;否则处理流程会继续向执行,控制器中的方法还是会被调用,达不到阻止运行的效果!
(2) 在SpringMVC核心配置文件中注册自定义拦截器
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**" /> <!-- 不拦截静态资源 --> <mvc:exclude-mapping path="/admin/**/*.*" /> <bean class="com.sikiedu.interceptor.LoginInterceptor" /> </mvc:interceptor> </mvc:interceptors>
- 配置若干个拦截器:
允许使用若干个拦截器,形成拦截器链,即某个请求可能需要经过多个拦截器,仅当每个拦截器都放行时,才会执行控制器中的方法!
在配置文件中,配置的先后顺序决定了多个拦截器的执行顺序
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<mvc:interceptors> <!-- 拦截器1 --> <mvc:interceptor> <mvc:mapping path="/**" /> <bean class="com.sikiedu.interceptor.MyInterceptor1" /> </mvc:interceptor> <!-- 拦截器2 --> <mvc:interceptor> <mvc:mapping path="/**" /> <bean class="com.sikiedu.interceptor.MyInterceptor2" /> </mvc:interceptor> </mvc:interceptors>
- 配置若干个拦截路径:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<mvc:interceptor> <!-- 拦截路径 --> <mvc:mapping path="/main/index.do" /> <mvc:mapping path="/user/password.do" /> <mvc:mapping path="/user/info.do" /> <mvc:mapping path="/user/handle_password.do" /> <mvc:mapping path="/user/handle_info.do" /> <!-- 拦截器类 --> <bean class="com.sikiedu.interceptor.LoginInterceptor"></bean> </mvc:interceptor>
- 使用通配符 * 拦截:一个星号 * 只能匹配一层路径;
例如:/main/*可以匹配上/main/index.do,也可以匹配/main/hello.do,但是,不可以匹配上/main/a/index.do!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<mvc:interceptor> <!-- 拦截路径 --> <mvc:mapping path="/main/*" /> <!-- 拦截器类 --> <bean class="com.sikiedu.interceptor.LoginInterceptor" /> </mvc:interceptor>
如果一定要匹配若干层路径,必须使用两个星号 **,
例如:配置为/main/**,可以匹配上/main/index.do,也可以匹配/main/a/hello.do,甚至可以匹配/main/a/b/c/d/hello.do,
即无视路径中后续的层级。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<mvc:interceptor> <!-- 拦截路径 --> <mvc:mapping path="/main/**" /> <mvc:mapping path="/user/**" /> <!-- 拦截器类 --> <bean class="com.sikiedu.interceptor.LoginInterceptor" /> </mvc:interceptor>
- 排除指定的路径:如果通配符匹配的路径过多,还可以排除某些请求路径
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<mvc:interceptor> <!-- 拦截路径:黑名单 --> <mvc:mapping path="/main/**" /> <mvc:mapping path="/user/**" /> <!-- 例外路径:白名单 --> <mvc:exclude-mapping path="/user/reg.do" /> <mvc:exclude-mapping path="/user/handle_reg.do" /> <mvc:exclude-mapping path="/user/login.do" /> <mvc:exclude-mapping path="/user/handle_login.do" /> <!-- 拦截器类 --> <bean class="com.sikiedu.interceptor.LoginInterceptor"></bean> </mvc:interceptor>
也就是说,凡/user/下的路径都会经过该拦截器,但是,/user/login.do是不被处理的!添加到“例外”中的路径,在请求时,并不是拦截器直接放行,而是拦截器根本就不执行!
三、总结 - 拦截器规则
① preHandle预处理:--------------根据拦截器定义的顺序,正向执行;
② postHandle后处理:-------------根据拦截器定义的顺序,逆向执行;
③ afterCompletion渲染后处理:---根据拦截器定义的顺序,逆向执行;
④ postHandle预处理:-------------所有拦截器都返回成功调用;
⑤ atterCompletion渲染后处理:---preHandle返回true调用;