zoukankan      html  css  js  c++  java
  • SpringBoot AspectJ做AOP日志管理

    AspectJ原理:

    • AspectJ是一个代码生成工具(Code Generator)。
    • AspectJ语法就是用来定义代码生成规则的语法。您如果使用过Java Compiler Compiler (JavaCC),您会发现,两者的代码生成规则的理念惊人相似。
    • AspectJ有自己的语法编译工具,编译的结果是Java Class文件,运行的时候,classpath需要包含AspectJ的一个jar文件(Runtime lib)。

    1:引入maven依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    2:Service层(被切面的业务逻辑层)
    这里简单写一个业务逻辑,内容不是重点,重点是怎么切面。
    BusinessService:

    @Service
    public class BusinessService {
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
        public User getIpInfo(HttpServletRequest request, int type) {
            logger.info("进入getIpInfo方法。");
            User user = new User();
            user.setName(request.getContextPath());
            user.setId(request.getContentLengthLong());
            return user;
        }
    
        public Integer getTestInfo(int type) {
            logger.info("进入getTestInfo方法。");
            return type;
        }
    }

    Aspect切面
    有了上面的Service,可以直接使用@Component@Aspect来写切面逻辑。
    OperateLogAspect:

    package com.springboot.study.aspjectj;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    /**
     * @Author: guodong
     * @Date: 2021/6/29 16:13
     * @Version: 1.0
     * @Description:
     */
    @Component
    @Aspect
    public class OperateLogAspect {
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
        @Pointcut("execution(* com.springboot.study.service.BusinessService.*(..)) && !execution(* com.springboot.study.service.BusinessService.getTest*(..)) ")
        private void log() {
        }
    
        @Before("log()")
        public void beforeMethod(JoinPoint joinPoint) {
            String methodName = joinPoint.getSignature().getName();
            joinPoint.getArgs();
            logger.info("方法为:" + methodName + ", 这是一个前置测试 ");
        }
    
        @After("execution(* com.springboot.study.service.BusinessService.*(..))")
        public void afterMethod(JoinPoint joinPoint) {
            String methodName = joinPoint.getSignature().getName();
            logger.info("方法为:" + methodName + ", 这是一个后置测试 ");
        }
    
        @Around("execution(* com.springboot.study.service.BusinessService.getTestInfo(..))")
        public void test(JoinPoint joinPoint) {
            String methodName = joinPoint.getSignature().getName();
            logger.info("方法为:" + methodName + ", 测水水水水水水水水水水 ");
        }
    
        @AfterReturning(value = "log()", returning = "result")
        public void afterReturning(JoinPoint point, Object result) {
            String methodName = point.getSignature().getName();
            logger.info("方法为:" + methodName + ",目标方法执行结果为:" + result);
        }
    
        @Around("log()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            logger.info("请求参数:{}", joinPoint.getArgs());
            Object[] arr = joinPoint.getArgs();
            Integer type = (Integer) arr[1];
            logger.info("请求类型:{}", type);
            long startTime = System.currentTimeMillis();
            Object obj = joinPoint.proceed();
            long timeTaken = System.currentTimeMillis() - startTime;
            logger.info("执行时间:{}", timeTaken);
            return obj;
        }
    }

    参数详解如下:

    • @Pointcut可以定义切点,定义之后,其他地方使用同样的切点就不需要再写execution,直接写切点的方法名即可。
    • @Before是在被切方法调用前执行,可以获取到被切方法的参数。
    • @After是在被切方法执行后执行,无法获取到被切方法的返回值。
    • @AfterReturning是被被切方法执行后执行,可以获取到被切方法的返回值。
    • @Around是环绕被切方法,记住需要调用joinPoint.proceed()执行被切方法,如果有返回值,注意return 返回值。
    • @AfterThrowing: 异常通知, 在方法抛出异常之后执行。
    • 如果切面方法外有事务,切面执行的方法也是会回滚的。

    运行结果
    测试中,我调用BusinessService的getIpInfo方法。
    切面测试结果:

    2021-06-29 16:17:07.450  INFO 13176 --- [nio-8080-exec-1] c.s.study.aspjectj.OperateLogAspect      : 请求参数:org.apache.catalina.connector.RequestFacade@71268e04
    2021-06-29 16:17:07.452  INFO 13176 --- [nio-8080-exec-1] c.s.study.aspjectj.OperateLogAspect      : 请求类型:0
    2021-06-29 16:17:07.454  INFO 13176 --- [nio-8080-exec-1] c.s.study.aspjectj.OperateLogAspect      : 方法为:getIpInfo, 这是一个前置测试 
    2021-06-29 16:17:07.462  INFO 13176 --- [nio-8080-exec-1] c.s.study.service.BusinessService        : 进入getIpInfo方法。
    2021-06-29 16:17:07.463  INFO 13176 --- [nio-8080-exec-1] c.s.study.aspjectj.OperateLogAspect      : 执行时间:11
    2021-06-29 16:17:07.463  INFO 13176 --- [nio-8080-exec-1] c.s.study.aspjectj.OperateLogAspect      : 方法为:getIpInfo, 这是一个后置测试 
    2021-06-29 16:17:07.463  INFO 13176 --- [nio-8080-exec-1] c.s.study.aspjectj.OperateLogAspect      : 方法为:getIpInfo,目标方法执行结果为:User(a=0, id=-1, name=, age=null, result=null)

    切面表达式浅谈
    execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?) 除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。
    ?表示可省略,
    修饰符模式如public、protected等;
    返回类型模式表示方法返回类型;
    方法名模式表示类+方法;
    参数模式表示参数;
    异常模式表示抛出的异常;
    表达式中,可以使用*来代表任意字符,用..来表示任意个参数。
    示例:

    execution(* com.cff.springbootwork.aspectj.service.BusinessService.*(..))

    • 切面表达式是从execution开始的,()内是表达式主体。
    • 第一个号:表示返回类型,号表示所有的类型。
    • com.cff.springbootwork.aspectj.service.BusinessService.*表示匹配该类的所有方法。
    • (..)表示参数个数任意。
    • 多个execution可以合并,可以使用||、&&等进行连接。

    最后补充一点小知识:
    AspectJ支持5种类型的通知注解
    1)@Before:前置通知:在方法执行之前执行的通知。
    2)@After:后置通知,在方法执行之后执行,即方法返回结果或者抛出异常的时候, 下面的后置通知记录了方法的终止。
    3)@AfterRunning:返回通知,在方法返回结果之后执行。ps:无论方法是正常返回还是抛出异常,后置通知都会执行。如果只想在方法返回的时候记录日志,应使用返回通知代替后置通知。
    4)@AfterThrowing:异常通知,在方法抛出异常之后。
    5)@Around:环绕通知,围绕着方法执行(即方法前后都有执行)。环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点,甚至可以控制是否执行连接点。

    参考博客:
    https://blog.csdn.net/zhao9tian/article/details/37762389
    https://blog.csdn.net/u014338530/article/details/88778158
    https://cloud.tencent.com/developer/article/1690944

    郭慕荣博客园
  • 相关阅读:
    【cocos2d-x 024】 LINK : fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏
    【cocos2d-x 024】 LINK : fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏
    socket 通信代码,单线程
    socket 通信代码,单线程
    socket 通信代码,单线程
    使用Cocos Studio 创建帧动画
    使用Cocos Studio 创建帧动画
    使用Cocos Studio 创建帧动画
    sscanf函数用法详解
    (OK) firefox
  • 原文地址:https://www.cnblogs.com/jelly12345/p/14950731.html
Copyright © 2011-2022 走看看