1. 前言
Interceptor
拦截器,是SpringMVC
提供用来拦截发送给Controller
层方法的请求的拦截器,类似于filter主要进行记录日志,判断用户是否登录,过滤权限等。拦截器和我们所学的过滤器是很相似的,只是范围不一样。
- 过滤器filter:是JavaEE提供用来拦截所有的请求,进行过滤,它主要实现编码过滤,进行统一编码,防止乱码。
- 拦截器Interceptor:主要用来拦截
Controller
控制器的方法,一般用于拦截Controller
层,满足条件才放行,主要用于实现权限分配,不满足条件不能访问一些界面(比如登录才能进入)。
注意:
一般请求都是先通过过滤器过滤,才会被拦截器Interceptor处理,决定是否放行,两个过程有任何一个不放行,都不能访问到Controller层方法。
1.1 过滤器和拦截器的大概执行流程
2. 拦截器
2.1 如何实现拦截器
拦截器它是链式调用,一个应用中可以同时存在多个拦截器Interceptor
,一个请求也可以触发多个拦截器,而每个拦截器的调用会依据它的声明顺序
依次执行。
首先编写一个简单的拦截器类,请求的拦截是通过HandlerInterceptor
来实现,看到HandlerInterceptor
接口中也定义了三个方法。
- preHandle():这个方法将在请求处理之前调用(在访问
controller
方法之前执行),返回为true
才会去执行Controller
方法,注意:如果该方法的返回值为false
,将视为当前请求结束
,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。一般用来做权限控制。 - postHandle():只有在preHandle()方法返回值为true时才会执行,会在
Controller
中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用,需要注意的是,postHandle()
方法被调用的顺序跟preHandle()
是相反的,先声明的拦截器preHandle()
方法先执行,而postHandle()
方法反而会后执行。 - afterCompletion():只有在 preHandle() 方法返回值为true 时才会执行。在整个请求结束之后, DispatcherServlet 渲染了对应的视图之后执行。一般用于记录日志,资源释放
注意:如果preHadle()
返回true,但是没有找到对应的Controller
,是不会执行postHandle()方法
的。
@Component
public class MyInterceptor implements HandlerInterceptor {
//在访问Controller方法之前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//preHandle():访问Controller方法之前执行,返回为true,继续访问Controller
System.out.println("MyInterceptor preHandle方法在执行");
return true;//返回false就不再继续执行Controller方法
}
//如果没有Controller就不执行postHandle()方法
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
/**
* posthandle:在执行controller方法之后, 执行jsp之前执行该方法,
* 可以向作用域中放入数据,影响jsp展示效果,(可以执行jsp之前做渲染)
*/
System.out.println("MyInterceptor postHandle方法在执行");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//afterCompletion:在jsp渲染之后执行,用于记录日志,资源释放
System.out.println("MyInterceptor afterCompletion方法在执行");
}
}
SpringBoot中将自定义好的拦截器处理类进行注册,并通过addPathPatterns、excludePathPatterns等属性设置需要拦截或需要排除的 URL。
/**
* 1、编写一个拦截器实现HandlerInterceptor接口
* 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
* 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
*/
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**")//所有请求都被拦截包括静态资源
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
}
}
使用xml配置多个拦截器
<!-- /** 是拦截所有的请求 path="/interceptor"只拦截interceptor路径-->
<mvc:interceptors>
<mvc:interceptor>
<!-- /**拦截所有请求,配置全局拦截器 -->
<mvc:mapping path="/**"/>
<bean class="com.example.config.interceptor.MyInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<!-- /interceptor 之拦截interceptor该路径 -->
<mvc:mapping path="/interceptor"/>
<bean class="com.example.config.interceptor.MyInterceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
2.2 多个拦截器的执行顺序
如果所有拦截器都通过(都不拦截)执行顺序是这样的:
- 都执行的话,preHandle顺序执行,postHandler逆序执行,最后再afterCompletion逆序执行。
- 如果拦截器1拦截(也就是preHandle1返回false),那么后面的拦截器也不执行,直接原路打回。
- 如果拦截器3拦截,那么也不执行controller方法,大概是这样的。
3. 过滤器
3.1 如何实现过滤器
过滤器实现比较简单,直接实现Filter
接口即可,也可以通过@WebFilter
注解实现对特定URL
拦截,Filter
接口中定义了三个方法。
- init():该方法在容器启动初始化过滤器时被调用,它在
Filter
的整个生命周期只会被调用一次,注意
:这个方法必须执行成功,否则过滤器会不起作用。 - doFilter():容器中的每一次请求都会调用该方法,
FilterChain
用来调用下一个过滤器Filter
。 - destroy():当容器销毁过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器
Filter
的整个生命周期也只会被调用一次
@WebFilter
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("MyFilter init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("MyFilter doFilter");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("MyFilter destroy");
}
}
注册到到容器中,配置映射路径。
@Configuration
public class MyRegistConfig {
@Bean
public FilterRegistrationBean myFilter(){
MyFilter myFilter=new MyFilter();
FilterRegistrationBean filterRegistrationBean=new FilterRegistrationBean(myFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello","/css/*"));
return filterRegistrationBean;
}
}
3.2 过滤器先后顺序问题
- 注解配置:按照
类名的字符串比较规则
比较,值小的先执行如: MyFilter和 MyFilter2,MyFilter就先执行了。 - web.xml配置: 谁定义在上边,谁先执行。
- JavaConfig配置:谁定义在上边,谁先执行。
4. 区别
过滤器 和 拦截器 均体现了AOP的编程思想,都可以实现诸如日志记录、登录鉴权等功能,但二者的不同点也是比较多的,接下来一一说明。
4.1 实现原理不同
过滤器和拦截器底层实现方式大不相同,过滤器
是基于函数回调
的,拦截器则是基于Java的反射机制(动态代理)实现的。
4.1.1 过滤器
在我们自定义的过滤器中都会实现一个doFilter()
方法,这个方法有一个FilterChain
参数。而实际上它是一个回调接口,ApplicationFIlterChain
是它的实现类,这个实现类内部有一个doFilter()
方法就是回调方法。
/**
* FilterChain是servlet容器提供给开发人员的一个对象,它向开发人员提供了对资源的过滤请求的调用链的视图.
* 过滤器使用过滤器链调用链中的下一个过滤器,或者如果调用过滤器是链中的最后一个过滤器,则调用链末端的资源.
*/
public interface FilterChain {
/**
* 调用链中的下一个筛选器,或者如果调用筛选器是链中的最后一个筛选器,则会导致调用链末尾的资源.
* @param request ServletRequest对象.
* @param response ServletResponse对象.
* @throws IOException .
* @throws ServletException .
*/
void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}
ApplicationFilterChain
里面能拿到我们自定义的xxxFilter
类,在其内部回调方法doFIlter()里调用各个自定义的xxxFilter
过滤器,并执行 doFilter() 方法。
public final class ApplicationFilterChain implements FilterChain {
/**
* Filters. 执行目标Servlet.service()方法前需要经历的过滤器Filter,初始化为0个元素的数组对象
*/
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
//用于记录过滤器链中当前所执行的过滤器的位置,是当前过滤器在filters数组的下标,初始化为0
private int pos = 0;
/**
* The int which gives the current number of filters in the chain.
* 过滤器链中过滤器的个数(注意:并不是数组filters的长度),初始化为0,和filters数组的初始长度一致
*/
private int n = 0;
/**
* The servlet instance to be executed by this chain.
* 该过滤器链执行完过滤器后最终要执行的目标Servlet
*/
private Servlet servlet = null;
/**
* Does the associated servlet instance support async processing?
* 所关联的Servlet实例是否支持异步处理 ? 缺省为 false,表示缺省情况下不支持异步处理。
*/
private boolean servletSupportsAsync = false;
/**
*执行过滤器链中的下一个过滤器Filter。如果链中所有过滤器都执行过,
* 则调用servlet的service()方法。
*/
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
// 下面的if-else分支主要是根据Globals.IS_SECURITY_ENABLED是true还是false决定
// 如何调用目标逻辑,但两种情况下,目标逻辑最终都是 internalDoFilter(req,res)
if (Globals.IS_SECURITY_ENABLED) {
ServletRequest req = request;
ServletResponse res = response;
try {
AccessController.doPrivileged(() -> {
this.internalDoFilter(req, res);
return null;
});
} catch (PrivilegedActionException var7) {
Exception e = var7.getException();
if (e instanceof ServletException) {
throw (ServletException)e;
}
if (e instanceof IOException) {
throw (IOException)e;
}
if (e instanceof RuntimeException) {
throw (RuntimeException)e;
}
throw new ServletException(e.getMessage(), e);
}
} else {
this.internalDoFilter(request, response);
}
}
//实际的Filter方法
private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
//如果filter没有执行完毕,且filter中一直保持了filterChain的链式调用
if (this.pos < this.n) {
//根据pos定位找到ApplicationFilterConfig
ApplicationFilterConfig filterConfig = this.filters[this.pos++];
try {
//拆包取得Filter对象
Filter filter = filterConfig.getFilter();
//异步判断
if (request.isAsyncSupported() && "false".equalsIgnoreCase(filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
}
/**
* 执行目标Filter对象的doFilter()方法
* 注意,这里当前的ApplicationFilterChain对象
* 被递到了目标Filter对象的doFilter方法
* 而目标Filter对象的doFilter()方法在执行完
* 自己被指定的逻辑之后会反过来调用
* 这个ApplicationFilterChain对象的doFilter方法
* 只是pos向前推进了一个过滤器。
* 这个ApplicationFilterChain和Filter之间
* 反复调用彼此doFilter方法的过程一直持续直到当前链发现所有的
* Filter都已经被执行
*/
if (Globals.IS_SECURITY_ENABLED) {
Principal principal = ((HttpServletRequest)request).getUserPrincipal();
Object[] args = new Object[]{request, response, this};
SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
} else {
//调用用户编写的Filter中的方法进行过滤
filter.doFilter(request, response, this);
}
} catch (ServletException | RuntimeException | IOException var15) {
throw var15;
} catch (Throwable var16) {
Throwable e = ExceptionUtils.unwrapInvocationTargetException(var16);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
} else {
// 这里是过滤器链中所有的过滤器都已经被执行的情况,现在需要调用servlet实例本身了。
// !!! 注意 : 虽然这里开始调用servlet实例了,但是从当前方法执行堆栈可以看出,过滤器链
// 和链中过滤器的doFilter方法的执行帧还在堆栈中并未退出,他们会在servlet实例的逻辑
// 执行完后,分别执行完自己剩余的的逻辑才会逐一结束。
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
//保存Servlet执行前的request与response
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
if (request.isAsyncSupported() && !this.servletSupportsAsync) {
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
}
//安全判断、特殊处理
if (request instanceof HttpServletRequest && response instanceof HttpServletResponse && Globals.IS_SECURITY_ENABLED) {
Principal principal = ((HttpServletRequest)request).getUserPrincipal();
Object[] args = new Object[]{request, response};
SecurityUtil.doAsPrivilege("service", this.servlet, classTypeUsedInService, args, principal);
} else {
//Servlet调用
this.servlet.service(request, response);
}
} catch (ServletException | RuntimeException | IOException var17) {
throw var17;
} catch (Throwable var18) {
Throwable e = ExceptionUtils.unwrapInvocationTargetException(var18);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set((Object)null);
lastServicedResponse.set((Object)null);
}
}
}
}
/**
* 往当前要执行的过滤器链的过滤器集合filters中增加一个过滤器
*/
void addFilter(ApplicationFilterConfig filterConfig) {
// Prevent the same filter being added multiple times
// 去重处理,如果已经添加进来则避免二次添加
for(ApplicationFilterConfig filter:filters)
if(filter==filterConfig)
return;
if (n == filters.length) {
// !!! 请注意:每次需要扩容时并不是增加一个元素空间,而是增加INCREMENT个,
// 这个行为的结果是filters数组的长度和数组中过滤器的个数n并不相等
ApplicationFilterConfig[] newFilters =
new ApplicationFilterConfig[n + INCREMENT];
System.arraycopy(filters, 0, newFilters, 0, n);
filters = newFilters;
}
filters[n++] = filterConfig;
}
}
从源码中我们可以看到,首先从零开始遍历符合的filter
,每个xxxFilter
会先执行自身的 doFilter()
过滤逻辑。最后在执行结束前会执行filterChain.doFilter(servletRequest, servletResponse)
,也就是回调ApplicationFilterChain的doFilter()
方法,以此循环执行实现函数回调。当所有的过滤器执行完毕后再执行service
()方法。
4.1.1 拦截器
4.2 使用范围不同
我们看到过滤器 实现的是 javax.servlet.Filter
接口,而这个接口是在Servlet
规范中定义的,也就是说过滤器Filter
的使用要依赖于Tomcat
等容器,导致它只能在web
程序中使用。
而拦截器(Interceptor
) 它是一个Spring
组件,并由Spring
容器管理,并不依赖Tomcat
等容器,是可以单独使用的。不仅能应用在web
程序中,也可以用于Application
、Swing
等程序中。
4.3 触发时机不同
过滤器 和 拦截器的触发时机也不同,我们看下边这张图。
- 过滤器
Filter
是在请求进入容器后,但在进入servlet
之前进行预处理,请求结束是在servlet
处理完以后。 - 拦截器
Interceptor
是在请求进入servlet
后,在进入Controller
之前进行预处理的,Controller
中渲染了对应的视图之后请求结束。
4.4 拦截的请求范围不同
在上边我们已经同时配置了过滤器和拦截器,再建一个Controller
接收请求测试一下。
@RestController
public class HelloController {
@Autowired
AccountMapper accountMapper;
@GetMapping("/hello")
public String hello(){
List<Account> accountList = accountMapper.findAccountList();
System.out.println("查询多条数据"+accountList);
return accountList.toString();
}
}
项目启动过程中发现,过滤器的init()
方法,随着容器的启动进行了初始化。
此时浏览器发送请求
看到控制台的打印日志如下:
4.5 注入Bean情况不同
在实际的业务场景中,应用到过滤器或拦截器,为处理业务逻辑难免会引入一些service服务。
下边我们分别在过滤器和拦截器中都注入service,看看有什么不同?
@Component
public class TestServiceimpl implements TestService {
@Override
public void test() {
System.out.println("我是test方法");
}
}
过滤器中注入service,发起请求测试一下 ,日志正常打印出“我是test方法”。
@Autowired
TestService testService;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("MyFilter doFilter");
testService.test();
filterChain.doFilter(servletRequest, servletResponse);
}
结果发现报空指针问题,过滤器不能访问容器里面的Bean
MyFilter doFilter
2022-04-07 21:40:51.713 ERROR 653008 --- [nio-9080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
java.lang.NullPointerException: null
at com.example.config.MyFIlter.MyFilter.doFilter(MyFilter.java:30) ~[classes/:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.15.jar:5.3.15]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.15.jar:5.3.15]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.15.jar:5.3.15]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.15.jar:5.3.15]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) [tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540) [tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) [tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) [tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) [tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382) [tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895) [tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1732) [tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) [tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) [tomcat-embed-core-9.0.56.jar:9.0.56]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.56.jar:9.0.56]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_144]
MyInterceptor preHandle方法在执行
MyInterceptor postHandle方法在执行
MyInterceptor afterCompletion方法在执行
在拦截器中注入service,发起请求测试一下 ,竟然TM的也报错了,debug跟一下发现注入的service怎么是Null啊?
- 这是因为加载顺序导致的问题,拦截器加载的时间点在springcontext之前,而Bean又是由spring进行管理。
- 解决方案也很简单,我们在注册拦截器之前,先将Interceptor 手动进行注入。注意:在registry.addInterceptor()注册的是getMyInterceptor() 实例。
@Bean
public MyInterceptor getMyInterceptor(){
System.out.println("注入了MyInterceptor");
return new MyInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getMyInterceptor())
.addPathPatterns("/**")//所有请求都被拦截包括静态资源
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
}
4.6 控制执行顺序不同
实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序。
4.6.1 过滤器执行顺序
过滤器用@Order
注解控制执行顺序,通过@Order
控制过滤器的级别,值越小级别越高越先执行。
@Order(Ordered.HIGHEST_PRECEDENCE)
@WebFilter
public class MyFilter2 implements Filter {
但是在我测试之后,发现并没有成功,过滤器依旧是按照名称的顺序先后执行,那么我们就只能通过编写MyRegistConfig 文件来决定执行顺序
@Configuration
public class MyRegistConfig {
//根据定义Filter的bean顺序执行
@Bean
public FilterRegistrationBean myFilter2(){
MyFilter2 myFilter2=new MyFilter2();
FilterRegistrationBean filterRegistrationBean=new FilterRegistrationBean(myFilter2);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello","/css/*"));
return filterRegistrationBean;
}
@Bean
public FilterRegistrationBean myFilter(){
MyFilter myFilter=new MyFilter();
FilterRegistrationBean filterRegistrationBean=new FilterRegistrationBean(myFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello","/css/*"));
return filterRegistrationBean;
}
}
4.6.2 拦截器执行顺序
拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order
手动设置控制,值越小越先执行。
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**").order(2);
registry.addInterceptor(new MyInterceptor2())
.addPathPatterns("/**").order(1);
}
- 看到输出结果发现,先声明的拦截器
preHandle()
方法先执行,而postHandle()
方法反而会后执行。 postHandle()
方法被调用的顺序跟preHandle()
居然是相反的!如果实际开发中严格要求执行顺序,那就需要特别注意这一点。
那为什么会这样呢? 得到答案就只能看源码了,我们要知道controller
中所有的请求都要经过核心组件DispatcherServlet
路由,都会执行它的doDispatch()
方法,而拦截器postHandle()
、preHandle()
方法便是在其中调用的。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
...........
try {
// 获取可以执行当前Handler的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 注意: 执行Interceptor中PreHandle()方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 注意:执行Handle【包括我们的业务逻辑,当抛出异常时会被Try、catch到】
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 注意:执行Interceptor中PostHandle 方法【抛出异常时无法执行】
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
}
...........
}
看看两个方法applyPreHandle()、applyPostHandle()具体是如何被调用的,就明白为什么postHandle()、preHandle() 执行顺序是相反的了。
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if(!ObjectUtils.isEmpty(interceptors)) {
for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
HandlerInterceptor interceptor = interceptors[i];
if(!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
}
return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if(!ObjectUtils.isEmpty(interceptors)) {
for(int i = interceptors.length - 1; i >= 0; --i) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
发现两个方法中在调用拦截器数组 HandlerInterceptor[] 时,循环的顺序竟然是相反的。。。,导致postHandle()、preHandle() 方法执行的顺序相反。
5. 区别
- 过滤器是基于函数的回调,而拦截器是基于Java的反射机制。
- 过滤器依赖于servlet容器,而拦截器不依赖于servlet容器。
- 过滤器则可以对所有的请求起作用,而拦截器只对action请求起作用。
- 拦截器可以获取IOC容器中的各个bean,而过滤器不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。
- 拦截器可以访问action上下文、值、栈里面的对象,而过滤器不可以。
- 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。