Spring Boot使用方法小札(5):小议Spring的拦截器

在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的范围大小也没关系。

相关文章:

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值