• Spring Boot 2.x(十一):AOP实战--打印接口日志


    接口日志有啥用

    在我们日常的开发过程中,我们可以通过接口日志去查看这个接口的一些详细信息。比如客户端的IP,客户端的类型,响应的时间,请求的类型,请求的接口方法等等,我们可以对这些数据进行统计分析,提取出我们想要的信息。

    怎么拿到接口日志

    这里,我们使用的是Spring的两大杀器之AOP,通过在Controller层定义切点,然后对请求对象进行分析获取接口信息,同时开启一个ThreadLocal来记录响应时间。

    关于AOP的注解

    • @Aspect:将一个类定义为切面类。
    • @Pointcut:定义一个切入点。
    • @Before:在切入点开始处切入内容。
    • @After:在切入点结尾处切入内容。
    • @AfterReturning:在切入点返回内容之后切入内容(可以用来对处理返回值做一些加工处理。
    • @Around:在切入点前后切入内容,并自己控制何时执行切入点自身的内容
    • @AfterThrowing:用来处理当切入内容部分抛出异常之后的处理逻辑。
    • @Order:在切入点前的操作,按order的值由小到大执行;在切入点后的操作,按order的值由大到小执行。

    实战应用

    一:引入依赖

    首先,我们需要新增引入aop的依赖,以及用于分析客户端信息的UserAgentUtils包,还有用于@Slf4j打印日志的Lombok的包:

    		<dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <dependency>
                <groupId>eu.bitwalker</groupId>
                <artifactId>UserAgentUtils</artifactId>
                <version>1.20</version>
            </dependency>
    

    二:定义一个ResponseAop切面类

    在之前的统一返回值和异常处理中我们已经定义过这个类,这里是对其进行完善。这里我再把代码再写一下:

    @Aspect
    @Order(5)
    @Component
    @Slf4j
    public class ResponseAop
    

    三:定义一个ThreadLocal变量

    直接在这里定义基本类型会有同步问题,所以我们定义一个ThreadLocal对象来记录消耗的时间。

    ThreadLocal<Long> startTime = new ThreadLocal<>();
    

    四:定义切点

    这里需要注意的是切点的写法,一定要正确才能保证AOP生效!这里附上一些简单的写法,后续会单独开一章讲解execution表达式的书写。

    • 任意公共方法:
      execution(public * *(..))
    • 任何一个以“set”开始的方法的执行:
      execution(* set*(..))
    • Service接口的任意方法的执行:
      execution(* com.xyz.service.Service.*(..))
    • 定义在service包里的任意方法的执行:
      execution(* com.xyz.service.*.*(..))
    • 定义在service包和所有子包里的任意类的任意方法的执行:
      execution(* com.xyz.service..*.*(..))
    	/**
         * 切点
         */
        @Pointcut("execution(public * indi.viyoung.viboot.*.controller..*(..))")
        public void httpResponse() {
        }
    

    五:在@Before中获取请求信息

    @Before("httpResponse()")
        public void doBefore(JoinPoint joinPoint){
        	//开始计时
            startTime.set(System.currentTimeMillis());
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            //打印请求的内容
            UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));//获取请求头中的User-Agent
            log.info("接口路径:{}" , request.getRequestURL().toString());
            log.info("浏览器:{}", userAgent.getBrowser().toString());
            log.info("浏览器版本:{}",userAgent.getBrowserVersion());
            log.info("操作系统: {}", userAgent.getOperatingSystem().toString());
            log.info("IP : {}" , request.getRemoteAddr());
            log.info("请求类型:{}", request.getMethod());
            log.info("类方法 : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
            log.info("请求参数 : {} " + Arrays.toString(joinPoint.getArgs()));
        }
    

    六:在@AfterReturning中获取方法的返回值和执行时间

    	@AfterReturning(returning = "ret" , pointcut = "httpResponse()")
        public void doAfterReturning(Object ret){
            //处理完请求后,返回内容
            log.info("方法返回值:{}" , ret);
            log.info("方法执行时间:{}毫秒", (System.currentTimeMillis() - startTime.get()));
        }
    

    七:测试结果

    下面,我们对一个接口进行访问:

    2019-02-21 21:03:31.358  INFO 11788 --- [nio-8090-exec-5] indi.viyoung.viboot.aop.ResponseAop      : 接口路径:http://localhost:8090/users
    2019-02-21 21:03:31.359  INFO 11788 --- [nio-8090-exec-5] indi.viyoung.viboot.aop.ResponseAop      : 浏览器:CHROME
    2019-02-21 21:03:31.359  INFO 11788 --- [nio-8090-exec-5] indi.viyoung.viboot.aop.ResponseAop      : 浏览器版本:72.0.3626.109
    2019-02-21 21:03:31.360  INFO 11788 --- [nio-8090-exec-5] indi.viyoung.viboot.aop.ResponseAop      : 操作系统: MAC_OS_X
    2019-02-21 21:03:31.360  INFO 11788 --- [nio-8090-exec-5] indi.viyoung.viboot.aop.ResponseAop      : IP : 0:0:0:0:0:0:0:1
    2019-02-21 21:03:31.360  INFO 11788 --- [nio-8090-exec-5] indi.viyoung.viboot.aop.ResponseAop      : 请求类型:GET
    2019-02-21 21:03:31.360  INFO 11788 --- [nio-8090-exec-5] indi.viyoung.viboot.aop.ResponseAop      : 类方法 : indi.viyoung.viboot.apilog.controller.UserController.findAll
    2019-02-21 21:03:31.360  INFO 11788 --- [nio-8090-exec-5] indi.viyoung.viboot.aop.ResponseAop      : 请求参数 : {} []
    ...
    2019-02-21 21:03:31.393  INFO 11788 --- [nio-8090-exec-5] indi.viyoung.viboot.aop.ResponseAop      : 方法返回值:ReturnVO{code='2000', message='操作成功', data=[User(id=10000001, password=123456, userName=vi-young), User(id=10000002, password=123456, userName=vi-young), User(id=10000003, password=123123, userName=lxt), User(id=10000004, password=123456, userName=yangwei)]}
    2019-02-21 21:03:31.393  INFO 11788 --- [nio-8090-exec-5] indi.viyoung.viboot.aop.ResponseAop      : 方法执行时间:36毫秒
    

    可以看出,我们已经获取到我们想要的信息~

    在后面的应用实战中,我们会将这些信息保存到数据库中,并且使用一些数据分析工具进行分析。

    公众号

    您的推荐是对我最大的帮助!

  • 相关阅读:
    Java代理(静态/动态 JDK,cglib)
    Java数据库基础(JDBC)
    Servlet基础(工作原理、生命周期)
    Java XML DOM解析(xPath)
    java 文件操作
    从源码看集合ArrayList
    全面理解java异常机制
    python3 利用pip安装ipython notebook
    Centos的一个find命令配合rm删除某天前的文件
    在Pandas中直接加载MongoDB的数据
  • 原文地址:https://www.cnblogs.com/viyoung/p/10416230.html
Copyright © 2020-2023  润新知