.

先说办法,如果看官觉得合适再往下看原理吧

解决办法

步骤:

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实现与区别 -第二篇中的顺序图:

implements Filter无效_AOP


可以看到,Spring在将请求交给Controller的接口处理前、后分别调用Filter链中Filter的方法对处理进行增强。当preHandle中将异常抛出时,并没有到Controller,故而@ControllerAdvice未能拦截该异常。

笔者是在集成Shiro时,需要保留原有项目功能:在身份验证失败或越权时返回JSON格式错误信息而遇到了这个问题,因为Shiro是基于Filter做的拦截,故而需要将Filter中的错误信息抛出。
项目原先的登陆验证是放在AOP做的,而AOP也在Filter之后进行,关于Filter、Interceptor、AOP及其异常抛出顺序,