SpringBoot使用Aspect AOP注解实现日志管理(一)

SpringBoot使用Aspect AOP注解实现日志管理(一)

回顾AOP

aop,又叫面向切面编程,通俗理解就是,将那些与业务无关,却为业务模块所共同调用的逻辑代码封装起来,形成一个切面,减少重复代码,降低模块间的耦合度,方便后期操作和维护。

Spring AOP和Aspect AOP

Spring AOP属于运行时的增强,而Aspect AOP属于编译时的增强。SpringAOP是基于代理(Proxying),而AspectAOP是基于字节码操作。如果切面比较少,两者差不多,如果切面太多,最好使用AspectAOP,它比SpringAOP快很多。

使用spring-boot-starter-aop启动器默认帮我们引入了对Aspectj的实现
SpringBoot使用Aspect AOP注解实现日志管理(一)

常用注解使用

 @Aspect -- 作用是把当前类标识为一个切面供容器读取
 @Pointcut -- (切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
 @Before -- 标识一个前置增强方法,相当于BeforeAdvice的功能
 @AfterReturning -- 后置增强,相当于AfterReturningAdvice,方法退出时执行
 @AfterThrowing -- 异常抛出增强,相当于ThrowsAdvice
 @After -- final增强,不管是抛出异常或者正常退出都会执行
 @Around -- 环绕增强,相当于MethodInterceptor

@Aspect

@Aspect– 作用是把当前类标识为一个切面供容器读取

@Aspect
@Component
@Slf4j
public class LogAspect {
}

@Pointcut

@Pointcut -- (切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式或者基于注解的切入点

基于注解的切入点

@Pointcut("@annotation(cn.zysheep.annotation.Log)")
//@annotation(cn.zysheep.annotation.Log),为自定义注解
public void Pointcut() {}

基于切入点表达式
execution()是最常用的切点函数

@Pointcut("execution(* cn.zysheep.springaop.service.impl..*.*(..))")
public void Pointcut() {}

整个表达式可以分为四个部分:
1. 第一个*号:表示返回类型, *号表示所有的类型
2. 包名: 表示需要拦截的包名,后面的..表示当前包和当前包的所有子包,cn.zysheep.springaop.service.impl包、子孙包下所有类的方法。
3. 第二个*号: 表示类名,*号表示所有的类。
4. *(..):最后这个星号表示方法名, *号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数

// cn.zysheep.controller包中所有的类的所有方法切面
// @Pointcut("execution(public * cn.zysheep.controller.*.*(..))")

// 只针对 UserController 类切面
// @Pointcut("execution(public * cn.zysheep.controller.UserController.*(..))")

// 统一切点,对cn.zysheep及其子包中所有的类的所有方法切面
// @Pointcut("execution(* cn.zysheep.controller.*.*(..))")

个人认为基于注解的方式可以实现更加细粒度的操作,比如日志管理,只要自己声明一个自定义的注解@Log,可以在任意三层中定义到不同的方法中

@Before

标识一个前置增强方法,相当于BeforeAdvice的功能

//@Before: 前置通知 ,Pointcut()定义的切入点函数
 @Before("Pointcut()")
 public void beforeMethod() {
     log.info("调用了前置通知");
 }

@AfterReturning

后置增强,相当于AfterReturningAdvice,方法退出时执行

 //@AfterRunning: 返回通知    result为切入点返回内容
@AfterReturning(value="Pointcut()",returning="result")
public void afterReturningMethod(JoinPoint joinPoint,Object result){
    log.info("调用了返回通知,result :{}",result);
}

@AfterThrowing

异常抛出增强,相当于ThrowsAdvice

//@AfterThrowing: 异常通知  e为切入点执行异常的信息
 @AfterThrowing(value="Pointcut()",throwing="e")
 public void doAfterThrowing(JoinPoint joinPoint, Exception e){
     log.info("调用了异常通知  joinPoint:{},e :{}",joinPoint, e.getStackTrace());
 }

@After

final增强,不管是抛出异常或者正常退出都会执行

//@After: 后置通知
@After("Pointcut()")
public void afterMethod(JoinPoint joinPoint){
	//joinPoint: 当前的连接点,即执行的切入点
    log.info("调用了后置通知,joinPoint:{}",joinPoint);
}

@Around

环绕增强,相当于MethodInterceptor

//@Around: 环绕通知
@Around("Pointcut()")
public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    log.info("around执行方法之前");
    // 执行方法
    Object object = proceedingJoinPoint.proceed();
    // 执行时长(毫秒)
    long time = System.currentTimeMillis() - beginTime;
    log.info("around执行方法之后--返回值: " +object);
    return object;
}

基于注解的切入点测试

SpringBoot使用Aspect AOP注解实现日志管理(一)

pom

<parent>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-parent</artifactId>
     <version>2.4.0</version>
</parent>
 
<dependencies>
	<dependency>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	
	<dependency>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-aop</artifactId>
	</dependency>
	
	<dependency>
	    <groupId>org.projectlombok</groupId>
	    <artifactId>lombok</artifactId>
	</dependency>
</dependencies>

自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
    //模块名
    String value() default "";
}

定义一个切面

@Aspect
@Component
@Slf4j
public class LogAspect {

    long beginTime;

    @Pointcut("@annotation(cn.zysheep.annotation.Log)")
    public void Pointcut() {}

    @Before("Pointcut()")
    public void beforeMethod() {
        log.info("调用了前置通知@Before");
        beginTime = System.currentTimeMillis();
    }

    @AfterReturning(value="Pointcut()",returning="result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result){
        log.info("调用了返回通知@AfterReturning,result :{}",result);
    }

    @AfterThrowing(value="Pointcut()",throwing="e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e){
        log.info("调用了异常通知@AfterThrowing  joinPoint:{},e :{}",joinPoint,  e.getMessage());
    }

    @After("Pointcut()")
    public void afterMethod(JoinPoint joinPoint){
        //joinPoint: 当前的连接点,即执行的切入点
        log.info("调用了后置通知@After,joinPoint:{}",joinPoint);
    }

    @Around("Pointcut()")
    public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("@Around执行方法之前");
        // 执行方法
        Object object = proceedingJoinPoint.proceed();
        // 执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        log.info("@Around执行方法之后--返回值: " +object);
        return object;
    }
}

controller

@RestController
@Slf4j
public class TestAopController {

    @GetMapping("/save")
    @Log("保存数据")
    public String save() {
        log.info("执行了controller中的save方法");
        return "保存数据";
    }
}

启动类

@SpringBootApplication
public class AOPApplication {
    public static void main(String[] args) {
        SpringApplication.run(AOPApplication.class,args);
    }
}

SpringBoot使用Aspect AOP注解实现日志管理(一)
执行流程:

  1. @Around前置增强
  2. @Before前置通知
  3. 执行了controller中的save方法
  4. @AfterReturning返回通知
  5. @After后置通知
  6. @Around后置增强

如果切入点出现异常:
SpringBoot使用Aspect AOP注解实现日志管理(一)
SpringBoot使用Aspect AOP注解实现日志管理(一)
执行流程:

  1. @Around前置增强
  2. @Before前置通知
  3. 执行了controller中的save方法,如果异常在之前执行,不会输出
  4. @AfterThrowing异常通知
  5. @After后置通知
上一篇:SpringBoot AOP中JoinPoint的用法和通知切点表达式


下一篇:在ROS-Noetic(Ubuntu 20.04)中安装stdr-simulator