SpringBoot开发Restful API请求的拦截

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a2011102394/article/details/80669119

需求背景:记录所有Restful API 的处理时间

过滤器(Filter)

编写一个Fileter并注入到Spring容器中

@Component
public class TimeFIlter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("timeFilter init !");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("timeFilter start");
        long start = System.currentTimeMillis();
        chain.doFilter(request,response);
        System.out.println("timeFilter耗时:"+(System.currentTimeMillis()-start));
        System.out.println("timeFilter stop");
    }

    @Override
    public void destroy() {
        System.out.println("timeFilter destory!");
    }
}

测试结果如下:

配置第三方的过滤器(即将一个普通的类加载到过滤器链上)

新建一个配置类

@Configuration
public class WebConfig {

    /**
     * 模拟配置第三方的Filter
     * 和在Spring项目的xml中配置filter类似(自行脑补)
     * @return
     */
    @Bean
    public FilterRegistrationBean timeFilter(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        TimeFIlter timeFIlter = new TimeFIlter();
        registrationBean.setFilter(timeFIlter);
        List<String> urls = new ArrayList<>();
        //所有的url都拦截
        urls.add("/*");
        registrationBean.setUrlPatterns(urls);
        return registrationBean;
    }
}

filter控制器的弊端:

filter只能获取到HttpServletRequest和HttpSevletResponse对象,至于具体操作是由那一个控制器的哪一个方法执行的,这个是获取不到的。因为Filter接口是由J2EE规范来制定,的J2EE规范是不知道关于Spring框架的具体实现的。Controller控制器是由Spring框架定义的一套组件。如果要获取这些信息的话,需要用到Spring框架提供的组件才行。

拦截器(Interceptor)

拦截器是由Spring框架提供的一套组件。

  1. 编写拦截器

    @Component
    public class TimeInterceptor implements HandlerInterceptor {
    
        /**
         * 拦截器调用之前执行
         * @param httpServletRequest
         * @param httpServletResponse
         * @param handler
         * @return
         * @throws Exception
         */
        @Override
        public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, 
            Object handler) throws Exception {
            System.out.println("preHandle");
            httpServletRequest.setAttribute("startTime",System.currentTimeMillis());
            //获取类名
            System.out.println(((HandlerMethod)handler).getBean().getClass().getName());
            //获取方法名
            System.out.println(((HandlerMethod)handler).getMethod().getName());
            return false;
        }
    
        /**
         * 拦截器调用之后执行
         * @param httpServletRequest
         * @param httpServletResponse
         * @param o
         * @param modelAndView
         * @throws Exception
         */
        @Override
        public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, 
            Object o, ModelAndView modelAndView) throws Exception {
            System.out.println("postHandler");
            Long time = (Long)httpServletRequest.getAttribute("startTime");
    
            System.out.println("time interceptor 耗时:"+(System.currentTimeMillis()-time));
        }
    
        /**
         * 一定会执行的方法
         * @param httpServletRequest
         * @param httpServletResponse
         * @param o
         * @param e
         * @throws Exception
         */
        @Override
        public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, 
            Object o, Exception e) throws Exception {
            System.out.println("afterCompletion");
            Long time = (Long)httpServletRequest.getAttribute("startTime");
            System.out.println("time interceptor 耗时:"+(System.currentTimeMillis()-time));
            System.out.println("e is"+e);
    
        }
    }
    
  2. 注册拦截器

    1)配置类继承自WebMvcConfigurerAdapter

    2)重新WebMvcConfigurerAdapter类的addInterceptors()方法

    @Autowired
    private TimeInterceptor timeInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册拦截器
        registry.addInterceptor(timeInterceptor);
    }
    

    运行测试结果:

    修改getInfo()方法,让其抛出异常

    运行测试结果:

    可见,postHandle中的方法未执行,afterCompletion依旧执行,注意在afterCompletion方法中未捕获到抛出的异常,是因为异常已经被带有@ControllerAdvice注解的Controller处理。

    抛出一个未被处理的异常,再次进行测试,如下:

    结果如下:

    拦截器会被拦截所有控制器中的方法调用,不光是自己写的,Spring提供的控制器也会被拦截

    interceptor的不足

    无法获取到具体执行的控制器中的方法的参数的值,这与Spring本身的设计有关,可以查看DispatcherServlet的源码

    在DispatcherServlet的doDispatch()方法中,有如下的描述

    如果applyPreHandle()方法,返回false,直接返回,否则继续执行ha.handle()方法。

    打开applyPreHandle()方法的源码,如下

    如此可见,如果存在interceptor拦截器,dispatcherServlet是不会调用处理器,而Controller中方法参数的拼装是在处理器中完成的(即ha.handle())。所谓参数的拼装就是把请求中的数据,封装成具体Controller中的参数的过程。

    综上,如果除了要知道具体是哪个Controller的哪个方法被调用,还要知道具体的方法的参数值,interceptor是做不到的。

切片(Aspect)

Spring AOP简介

  1. 编写切片组件

切片组件的代码如下:

@Aspect
@Component
public class TimeAspect {

    /**
     * 切片方法
     * @param pjp 包含已拦截方法中的所用信息
     * @return
     */
    @Around("execution(* com.crsri.secutity.demo.web.controller.UserController.*(..))")
    public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("time aspect start");
        long start = System.currentTimeMillis();
        Object[] args = pjp.getArgs();
        for(Object arg : args){
            System.out.println("arg is "+arg);
        }
        Object obj = pjp.proceed();
        System.out.println("time aspect 耗时:"+(System.currentTimeMillis()-start));
        System.out.println("time aspect stop");
        return obj;
    }
}

运行结果如下:

小结

综上:要对Restful API的请求进行拦截,不同的业务需求使用不同的组件

  1. filter只能获取原始的http请求和响应信息
  2. interceptor能拿到原始的http请求和响应信息,也可以获取具体处理请求的Controller和具体的方法信息,但是拿不到方法真正被调用的时候,参数的值
  3. Aspect可以获取方法被调用的参数的值,但是获取不到原始的Http请求和响应信息

请求的拦截及异常的处理层次

  • 请求处理

    Filter –> Interceptor –> Aspect —-> Controller

  • 异常处理

    Controller –> Aspect –> ControllerAdvice –> Interceptor –> Filter –> tomcat等Web容器

参考资料

http://qa.blog.163.com/blog/static/190147002201312872938439/

猜你喜欢

转载自blog.csdn.net/a2011102394/article/details/80669119