zoukankan      html  css  js  c++  java
  • 记录请求的耗时(拦截器、过滤器、aspect)

    文章前言

    记录控制器请求的耗时处理通常有三种实现方式,分别是:过滤器、拦截器、aspect;下文将逐一实现。

    1、Filter 过滤器

    1.1、方法说明

    需要实现 Filter 类,主要涉及三个方法:
    1. destory:销毁
    2. doFilter:处理过滤器逻辑
    3. init:filter 初始化时调用

    1.2、代码部分

    @Component //表明作为spring的一个bean
    public class TimeFilter implements Filter {
    
        @Override
        public void destroy() {
            System.out.println("time filter destroy");
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            System.out.println("time filter start");
            long start = new Date().getTime();
            //过滤器主要逻辑,整个处理流程
            chain.doFilter(request, response);
            System.out.println("time filter 耗时:"+ (new Date().getTime() - start));
            System.out.println("time filter finish");
        }
    
        @Override
        public void init(FilterConfig arg0) throws ServletException {
            System.out.println("time filter init");
        }
    
    }
     

    1.3、补充说明

    通过过滤器拦截请求的方式,有一个问题,只能拿到 http 的请求和响应(request、response),意味着只能在请求或者响应里获取一些参数;
    但是当前请求到底是哪个控制器的哪个方法处理的,在filter里是无法获取的,因为实现的 Filter 这个接口是由javax(j2e)定义的,
    而我们要拦截的控制器(Controller)中的请求是springmvc定义的,所以 Filter 是无法获取 spring 相关信息。 
     
    带出下一个主角,Interceptor 拦截器,springmvc 提供。
     

    2、Interceptor 拦截器

    2.1、方法说明

    需要实现 HandlerInterceptor 类,主要涉及三个方法:
    1. preHandle:请求方法之前被调用;
    2. postHandle:控制器处理方法之后会被调用,前提是没有抛出异常,抛出异常不会调用;
    3. afterCompletion:请求方法之后被调用,该方法总会被调用,如果出现了异常,将被封装到Exception对象中。

    2.2、代码部分

    @Component
    public class TimeInterceptor implements HandlerInterceptor {
    
       
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {
            System.out.println("preHandle");
            
            System.out.println(((HandlerMethod)handler).getBean().getClass().getName());
            System.out.println(((HandlerMethod)handler).getMethod().getName());
            
            request.setAttribute("startTime", new Date().getTime());
            return true;
        }
    
       
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                ModelAndView modelAndView) throws Exception {
            System.out.println("postHandle");
            Long start = (Long) request.getAttribute("startTime");
            System.out.println("time interceptor 耗时:"+ (new Date().getTime() - start));
    
        }
    
      
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
                throws Exception {
            System.out.println("afterCompletion");
            Long start = (Long) request.getAttribute("startTime");
            System.out.println("time interceptor 耗时:"+ (new Date().getTime() - start));
            System.out.println("ex is "+ex);
    
        }
    
    }

    2.3、补充部分

    interceptor 其实等价于将 filter  doFilter() 方法中的 chain.doFilter(request, response) 分成两部分:preHandle() 、 postHandle() 
    interceptor比 filter 的优势在于方法上多了另外一个参数,Object handler 该参数是用来处理当前 request 请求的控制器方法的声明;
    意味着,你可以通过该参数获取当前控制器相关的信息,如控制器名称、请求的方法名。
     

    2.4、注意部分

      interceptor 跟 Filter 不一样,光声明一个 @Component 是无法达到拦截器起作用,还需要一些额外的配置。
    @Configuration
    public class WebConfig extends WebMvcConfigurerAdapter {
        
        @SuppressWarnings("unused")
        @Autowired
        private TimeInterceptor timeInterceptor;
        
        // 拦截器的一个注册器
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
           registry.addInterceptor(timeInterceptor);
        }
    
    }
    这个配置类需要继承 WebMvcConfigurerAdapter,并重写添加拦截器的方法 addInterceptors,将自定义拦截器添加到应用中。
    这时候拦截器就生效了。 
     

    2.5、继续补充

    拦截器会拦截所有控制器里的方法调用。但是却有一个缺陷,其无法获取前端访问方法的时候所携带的参数的。
    为什么会这么说?
    从Spring MVC 的 DispatcherServlet 的源代码中可以发现,找到 doDispatch() 方法,也就是请求分发的方法,有一段代码如下:
     
    如果我们自定的 Interceptor 的 preHandler 方法返回的是 false,分发任务就会截止,不再继续执行下面的代码,
    而下面的一行代码正是将前端携带的参数进行映射的逻辑,也就是说,preHandler 方法不会接触到前端携带来的参数,也就是说拦截器无法处理参数。
    所以这里引进 AOP 进行拦截。
     

    3、Aspect

    描述AOP常用的一些术语有:通知(Adivce)、连接点(Join point)、切点(Pointcut)、切面(Aspect)、引入(Introduction)、织入(Weaving)
    首先明确的核心概念:切面 = 切点 + 通知。
     

    3.1、通知(Adivce)

    通知分为五种类型:
    Before(前置通知):在目标方法被调用之前调用通知功能
    After(后置通知):在目标方法完成后调用通知,无论方法是否执行成功,不会关心方法的输出是什么
    After-returning(返回通知):在目标方法成功执行之后调用通知
    After-throwing(异常通知):在目标方法抛出异常后调用通知
    Around(通知环绕):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为

    3.2、连接点(Join point)

    连接点是一个应用执行过程中能够插入一个切面的点。
    比如:方法调用、方法执行、字段设置/获取、异常处理执行、类初始化、甚至是for循环中的某个点。
    理论上, 程序执行过程中的任何时点都可以作为作为织入点, 而所有这些执行时点都是Joint point,
    但 Spring AOP 目前仅支持方法执行 (method execution)。

    3.3、切点(Pointcut)

    通知(advice)定义了切面何时,那么切点就是定义切面“何处” 描述某一类 Joint points, 
    比如定义了很多 Joint point, 对于 Spring AOP 来说就是匹配哪些方法的执行。

    3.4、切面(Aspect)

    切面是切点和通知的结合。通知和切点共同定义了关于切面的全部内容 —— 它是什么时候,在何时和何处完成功能。

    3.5、引入(Introduction)

    引用允许我们向现有的类添加新的方法或者属性

    3.6、织入(Weaving)

    组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。
    Spring和其他纯Java AOP框架一样,在运行时完成织入。

    来看一下 Aspect 怎么写:
    @Aspect
    @Component
    public class TimeAspect {
    
        @Around("execution(* club.sscai.security.web.controller.UserController.*(..))")
        public Object handleTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            System.out.println("time aspect is start.");
            for (Object object : proceedingJoinPoint.getArgs()) {
                System.out.println(object);
            }
            long startTime = System.currentTimeMillis();
            Object obj = proceedingJoinPoint.proceed();
            System.out.println("time aspect 耗时:" + (System.currentTimeMillis() - startTime));
            System.out.println("time aspect finish.");
            return obj;
        }
    }
    @Around 定义了环绕通知,也就是定义了何时使用切面,表达式"execution(* club.sscai.security.web.controller.UserController.*(..))"定义了再哪里使用。
    ProceedingJoinPoint 对象的 proceed() 方法表示执行被拦截的方法,它有一个 Object 类型的返回值,是原有方法的返回值,后期使用的时候往往需要强转。
     
    对于上面三种拦截方式,他们的执行有一个基本的顺序,进入的顺序是:
    Filter-->Interceptor-->Aspect-->Controller-->Aspect-->Interceptor-->Filter(不考虑异常的发生)。
    如下所示:
     
    博客地址:http://www.cnblogs.com/niceyoo
     
  • 相关阅读:
    TVM量化代码解析
    如何在 GPU 上优化卷积
    全文翻译(全文合集):TVM: An Automated End-to-End Optimizing Compiler for Deep Learning
    全文翻译(四) TVM An Automated End-to-End Optimizing Compiler
    全文翻译(三) TVM An Automated End-to-End Optimizing Compiler
    全文翻译(二): TVM: An Automated End-to-End Optimizing Compiler for Deep Learning
    全文翻译(一):TVM: An Automated End-to-End Optimizing Compiler for Deep Learning
    pytorch使用gpu加速的方法
    springboot的实践1
    带你玩转stackOverflow-3
  • 原文地址:https://www.cnblogs.com/niceyoo/p/10162077.html
Copyright © 2011-2022 走看看