在Spring Boot中我们可以使用Java Web中原生的Filter API,但是这样很是别扭,尤其对于我这种有精神洁癖的人,既然使用了Spring的技术,就想完全使用它的,不想混杂别的,在Spring中确实是存在这种技术的,这就是拦截器Interceptor,从字面意思上来看跟Filter的作用相同,但是从它的API来看对请求的处理过程更加精细,而且对于存在多个拦截器时,它可以通过在一个拦截器的方法中抛出异常或者返回false来停止对请求的处理,而且不像doFilter中只是通过不调用FilterChain来实现,这样的方式更加精细,关于Filter的细节可以看Java Web基础知识之Filter:过滤一切你不想看到的事情,里面说的很详细!
1. HandlerInterceptor—拦截器的祖宗
首先我们要了解的是Spring中拦截器的部分,首先要了解它,才能使用它和熟悉它。HandlerInterceptor
是所有自定义或者Spring提供的拦截器的祖宗,所以要使用拦截器,这个接口必须了解。
这个接口中有三个方法:
- preHandle(…):这个方法的执行时机是,当通过url已经匹配到对应的请求处理器,也可以理解为就是
@Controller
注解标识的类中的处理器,但是对应的处理器执行之前,所以这个方法也可以决定是否将请求交给下一个Interceptor或者handler进行处理,这是通过返回值来决定的,返回true则将拦截器交给下面的Interceptor或者handler,如果false则不会向后传递请求,并且根据通常的做法此时需要在自定义响应,并标出对应的http错误; - postHandle(…):该方法是在handler执行之后,但是在
DispatcherServlet
渲染视图之前执行的,在这个方法中还有ModelAndView
参数,可以在handler执行完之后再加入视图的参数,这些参数也可以在渲染视图时使用,另外由于这个方法是在handler执行完之后执行,所以在整个Interceptor链中第一个Interceptor中的该方法是会被最后一个执行的; - afterCompletion(…):这个方法是在请求处理完成后执行,这个完成可以理解为渲染视图完成,这时做一些资源的清理工作,这个方法只有在preHandle(…)被成功执行后并且返回true之后才会执行。
But,通常我不会直接实现该接口,因为有时候我并不想重写所有上面的三个方法,可能只需要其中的一个或者两个,这是我们可以实现抽象类org.springframework.web.servlet.handler.HandlerInterceptorAdapter
,这个类完成HandlerInterceptor中所有方法的空实现,所以通过这个接口我们可以只实现一部分需要的方法,其实这就是适配器模式的应用。关系如下:
具体的实例如下:
LoggingInterceptor类:
public class LoggingInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.info("LoggingInterceptor preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.info("LoggingInterceptor postHandle");
}
}
之后在我们的配置类中将该Interceptor声明为Bean,but,这里和Java Web中的Filter是不同的,只是声明为Bean是不够的,还需要在我们的配置类中实现org.springframework.web.servlet.config.annotation.WebMvcConfigurer
,但是这里我们也不会直接实现这个接口,而是通过继承它的适配器实现类org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
,并且实现其中的addInterceptors(InterceptorRegistry registry)方法,如下:
WebConfig配置类:
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loggingInterceptor())
.addPathPatterns("/test");
}
@Bean
public HandlerInterceptor loggingInterceptor(){
return new LoggingInterceptor();
}
}
其中在addInterceptors(…)方法中会将我们的Interceptor添加到注册中,当在MVC自动配置的阶段时会自动检测WebMvcConfigurer
类的实例然后会自动执行Interceptor中的回调方法,在org.springframework.web.servlet.config.annotation.InterceptorRegistration
中还有一些常用的方法,比如说上面使用的addPathPatterns(…)方法,还有一些其他的方法可以使用。
2. Interceptor之间的执行顺序
让人沮丧的是,在org.springframework.web.servlet.config.annotation.InterceptorRegistration
并没有类似于Filter中声明Interceptor之间执行顺序的API,但是又好想弄清其中的缘由(并不是典型的星座),只有自己来做实验了。
另加一个Interceptor,AuthenticationInterceptor类如下:
public class AuthenticationInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = LoggerFactory.getLogger(AuthenticationInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.info("AuthenticationInterceptor preHandle");
return true;
}
}
更改WebConfig配置类如下:
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loggingInterceptor())
.addPathPatterns("/test");
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/test/**");
}
@Bean
public HandlerInterceptor loggingInterceptor(){
return new LoggingInterceptor();
}
@Bean
public HandlerInterceptor authenticationInterceptor(){
return new AuthenticationInterceptor();
}
}
对应测试的请求handler如下:
@RestController
public class DataController {
@RequestMapping(value="/test", method=RequestMethod.GET)
public String test(){
return "test";
}
@RequestMapping(value="/test/1", method=RequestMethod.GET)
public String test1(){
return "test1";
}
}
首先测试对应test()handler的url,即/test,返回结果如下:
首先测试对应test1()handler的url,即/test/1,返回结果如下:
为了测试Interceptor执行的顺序是否和被注册时的顺序有关,我们更改一下WebConfig中Interceptor的注册顺序,如下:
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/test/**");
registry.addInterceptor(loggingInterceptor())
.addPathPatterns("/test");
}
@Bean
public HandlerInterceptor loggingInterceptor(){
return new LoggingInterceptor();
}
@Bean
public HandlerInterceptor authenticationInterceptor(){
return new AuthenticationInterceptor();
}
}
这时重新测试,首先测试对应test()handler的url,即/test,返回结果如下:
首先测试对应test1()handler的url,即/test/1,返回结果如下:
从测试的结果来看:针对同样的url起作用的Interceptor之间的执行顺序是和被注册的顺序有关的,首先注册的Interceptor中的preHandle(…)会先执行,postHandle(…)则相反会最后执行,而且这个顺序和Interceptor所覆盖的url的范围大小也没关系。
相关文章: