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
     
  • 相关阅读:
    Asp.net2.0 中自定义过滤器对Response内容进行处理 dodo
    自动化测试工具 dodo
    TestDriven.NET 2.0——单元测试的好助手(转) dodo
    JS弹出窗口的运用与技巧 dodo
    ElasticSearch 简介 规格严格
    修改PostgreSQL字段长度导致cached plan must not change result type错误 规格严格
    Linux系统更改时区(转) 规格严格
    mvn编译“Cannot find matching toolchain definitions for the following toolchain types“报错解决方法 规格严格
    ElasticSearch 集群 & 数据备份 & 优化 规格严格
    Elasticsearch黑鸟教程22:索引模板的详细介绍 规格严格
  • 原文地址:https://www.cnblogs.com/niceyoo/p/10162077.html
Copyright © 2011-2022 走看看