SpringBoot实践(三十九):如何使用AOP

时间:2023-01-14 12:56:26

目录

直接使用@Aspect

定义切面逻辑

模拟业务代码

测试输出

 自定义注解方式

自定义切面注解

定义切入点逻辑

模拟业务代码

测试输出


面向切面(AOP) 是spring重要特性,在功能上切面编程是面向对象编程的很好的补充,面向对象强调封装和开闭原则,如果多个对象有通用行为和方法,将造成很多冗余代码,AOP将通用功能作为切面插入到业务逻辑中(如通用的日志打印,异常处理,license判断等)抽取通用逻辑,减少代码冗余,并且无侵入地修改代码,在实现层面上都是通过动态代理实现,默认cglib方式,SpringBoot简化了它的使用,常用的2种方式:直接使用aspect注解扫描或基于自定义的注解。

首先在springBoot的依赖管理基础上,引入aop的依赖:

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

直接使用@Aspect

定义切面逻辑

使用@Aspect实现一个通用的业务逻辑,比如日志打印,提供了几种切面增强方式:前置增强@Before,后置增强@After,环绕返回@AfterReturning,环绕增强@Around(前后都有),异常抛出增强AfterThrowing,执行顺序是:

  1. 正常情况:@Around-->@Before-->目标方法执行-->@AfterReturning-->@After-->@AfterReturning-->@Around
  2. 异常情况:@Around-->@Before-->目标方法执行-->@AfterThrowing-->@After-->AfterThrowing-->@Around

 如下:

@Component
@Aspect
@Slf4j
public class LoggingAspect {
    @Pointcut("execution(public * com.example.hello.controller.*.*())")
    public void LogAspect(){}
    @Before("LogAspect()")
    public void doBefore(JoinPoint joinPoint){
        log.info(" -------------------------> this is before.");
    }
    @After("LogAspect()")
    public void doAfter(JoinPoint joinPoint){
        log.info(" -------------------------> this is after.");
    }
    @AfterReturning("LogAspect()")
    public void doAfterReturning(JoinPoint joinPoint){
        log.info(" -------------------------> this is afterReturning.");
    }
    @AfterThrowing("LogAspect()")
    public void deAfterThrowing(JoinPoint joinPoint){
        log.info(" -------------------------> this is deAfterThrowing.");
    }
    @Around("LogAspect()")
    public Object deAround(ProceedingJoinPoint joinPoint) throws Throwable{
        log.info(" -------------------------> this is deAround.");
        return joinPoint.proceed();
    }
}

模拟业务代码

这里的切入点是controller类中方法,controller的方法定义如下,其中helloService模拟的是真实的业务代码,这个controller不需要做任何修改,测试正常输入和异常输出;

    @GetMapping("/hello")
    public String sayHello(){
        log.info("---- 模拟基于标准注解的切面过程 ---------- >running hello");
        return helloService.sayHello();
    }

    @GetMapping("/hello2")
    @LogAnnotation(param = "HelloController")
    public String sayHello2(){
        log.info("---- 模拟异常后切面过程 ---------- >running hello2");
        throw new RuntimeException();
    }

测试输出

 在正常输出业务代码的前后,及方法的全局都被注入了自定义的逻辑: 

SpringBoot实践(三十九):如何使用AOP

异常输出:

SpringBoot实践(三十九):如何使用AOP

 自定义注解方式

上面的方式可以无侵入地为我们的业务代码增加某些通用逻辑,同时我们也可以自定义注解方式,然后将自定义注解增加到部分业务代码中;

自定义切面注解

定义注解LogAannotation,能够在方法和类上标注,有个参数param默认空;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface LogAnnotation {
    String param() default "";
}

定义切入点逻辑

LoggingAspect2实现了切入点和自定义注解@LogAnnotation的逻辑,在切入点前后执行时间打印;

@Aspect
@Component
@Slf4j
public class LoggingAspect2 {

    @Around("@annotation(logAnnotation)")
    public Object around(ProceedingJoinPoint joinPoint, LogAnnotation logAnnotation) throws Throwable {
        log.info(String.format("time now :%s",new Date()));
        Object o = joinPoint.proceed();
        log.info(String.format("time now :%s",new Date()));
        return o;
    }
}

模拟业务代码

这种方式需要修改业务代码,增加对于自定义注解的传参;

    @GetMapping("/hello3")
    @LogAnnotation(param = "HelloController")
    public String sayHello3(){
        log.info("---- 模拟基于自定义注解的切面 ---------- >running hello3");
        return helloService.sayHello();
    }

测试输出

这里因为上面的@aspect注解增加了环绕增强,因此hello3的上下日期打印是自定义注解的实现。

SpringBoot实践(三十九):如何使用AOP