.
先说办法,如果看官觉得合适再往下看原理吧
解决办法
步骤:
1、创建一个专门抛出Filter中异常的Controller及接口方法,例如该接口地址为:/filter/login_auth_fail
1 @RequestMapping("/filter")
2 @RestController
3 public class FilterController {
4 @RequestMapping("/login_auth_fail")
5 public void loginAuthFail(HttpServletRequest request) {
6 // 此处构造一个合适的异常并抛出即可
7 String code = request.getAttribute("code");
8 throw new CustomException(code);
9 }
10 }
2、在Filter中,需要抛出异常的地方,将异常信息存起来(例如:可以存在HttpServletRequest中),并将请求转发给上面创建的接口地址:
1 request.setAttribute("code", "xxx");
2 request.getRequestDispatcher("/filter/login_auth_fail").forward(request, response);
原理
可能大家尝试过一种拦截办法:使用@ControllerAdvice和@ExceptionHanlder组合拦截,但并没有成功拦截Filter中的异常
1 @ControllerAdvice
2 public class ExpHanlder {
3 @ExceptionHandler(Exception.class)
4 public void handle() {
5 // do somthing...
6 }
7 }
那为什么没有成功呢?关键在于@ControllerAdvice只是对Controller做了加强,而Filter在Controller之前进行,故而异常就这样逃出了咱们的“掌心”。
本文中介绍的办法,恰是利用了这样的运行顺序,让异常乖乖地抛出去:既然Filter中不能抛出,那我先把错误信息记录下来,把请求转发到一个特定的接口(可认为是一个“陷阱”),然后在这个接口中利用记录的错误信息复原一个异常抛出即可。
到这,解决办法的原理已经介绍完了了,后面内容按需观看,将和大家一起回顾Filter与Controller的业务流程。
借用Spring 梳理 - filter、interceptor、aop实现与区别 -第二篇中的顺序图:
可以看到,Spring在将请求交给Controller的接口处理前、后分别调用Filter链中Filter的方法对处理进行增强。当preHandle中将异常抛出时,并没有到Controller,故而@ControllerAdvice未能拦截该异常。
笔者是在集成Shiro时,需要保留原有项目功能:在身份验证失败或越权时返回JSON格式错误信息而遇到了这个问题,因为Shiro是基于Filter做的拦截,故而需要将Filter中的错误信息抛出。
项目原先的登陆验证是放在AOP做的,而AOP也在Filter之后进行,关于Filter、Interceptor、AOP及其异常抛出顺序,