第十六周_T- AOP 切面实战

72 阅读3分钟

AOP概念

面向切面编程,指扩展功能不修改源代码,将功能代码从业务逻辑中抽离出来。

  • 主要用途:日志记录,事务控制,异常统计等。

  • 主要思想:将这些额外的工作剥离正常的业务,进行统一操作,而不影响真正的业务。

下面使用的版本:

  • spring-aop 2.5.6
  • aspectjweaver 1.9.7

AOP 简单demo

概述

简单 demo 就是单纯使用 AOP ,指定需要被拦截的代码进行增强。只涉及 AOP 这一个知识点。

代码

@Aspect
@Component
public class AspectClassTest1 {
    private static final Logger log = LoggerFactory.getLogger(AspectClassTest1.class);

    //切入点:表示拦截 controller 包下的所有类的所有方法
    @Pointcut("execution(* com.practice.springdemo.controller.*.*(..))")
    public void cut() {
    }

    //Around 通知:环绕
    @Around(value = "cut()")
    public void doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] objs = joinPoint.getArgs();
        log.info("@Around前 入参的个数:" + objs.length);
        joinPoint.proceed();
        log.info("@Aroud 后面");
    }
    //Before 通知
    @Before(value = "cut()")
    public void befor(){
        log.info("before");
    }
    //AfterReturning 通知
    @AfterReturning(value = "cut()")
    public void AfterReturning(){
        log.info("AfterReturning");
    }
    //After 通知
    @After(value = "cut()")
    public void After(){
        log.info("After");
    }

}
  • 切面 Aspect :把增强应用到具体方法上
  • 切入点 Pointcut :需要被拦截的具体方法
  • 通知/增强:
    • Before 在方法执行之前
    • After 在方法执行之后
    • Around 环绕,在方法执行前后都行
    • AfterReturning 在方法执行之后,可以拿到方法的返回值
    • AfterThrowing 方法异常会执行

这是上面代码的执行顺序:

[nio-8080-exec-1] c.p.springdemo.aspect.AspectClassTest1 : @Around前 入参的个数:1

[nio-8080-exec-1] c.p.springdemo.aspect.AspectClassTest1 : before

[nio-8080-exec-1] c.p.springdemo.controller.AspectDemo : 进入 AspectDemo1 的 controller

[nio-8080-exec-1] c.p.springdemo.controller.AspectDemo : 传入的值:123

[nio-8080-exec-1] c.p.springdemo.aspect.AspectClassTest1 : AfterReturning

[nio-8080-exec-1] c.p.springdemo.aspect.AspectClassTest1 : After

[nio-8080-exec-1] c.p.springdemo.aspect.AspectClassTest1 : @Aroud 后面

AOP + 注解实现

概述

使用注解+AOP实现增强,使用更加灵活。

代码

Target:是 Java 元注解之一:指定使用的地方,比如这里可以用在形式参数和方法上面

Retention:是 Java 元注解之一:描述注解的保留策略,这里是运行期间起作用

Documented:是 Java 元注解之一:会被 javac 提取为文档

@Target( {ElementType.PARAMETER,ElementType.METHOD} )
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AspectAnnonationTest {
    /**
     * 模块
     */
    public String moudleId() default "";

    /**
     * 方法
     */
    public String methodId() default "";

}
@Aspect
@Component
public class AspectClassTest {
    private static final Logger log = LoggerFactory.getLogger(AspectClassTest.class);


    @Before(value = "@annotation(testLog)")
    public Object before(JoinPoint joinPoint, AspectAnnonationTest testLog) {
        log.info("@Before");
        return "";
    }

    @After(value = "@annotation(testLog)")
    public void doAfter(JoinPoint joinPoint, AspectAnnonationTest testLog) {
        log.info("@After");
    }


    @AfterReturning(value = "@annotation(testLog)", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, AspectAnnonationTest testLog, Object result) {
        log.info("@AfterReturning");
        log.info("doAfterReturning: " + result.toString());
    }

    @AfterThrowing(value = "@annotation(testLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, AspectAnnonationTest testLog, Exception e) {
        log.info("@AfterThrowing");
        log.error("捕获异常:" + e.getMessage());
    }

    /**
     *
     * @param joinPoint
     * @param testLog
     * @throws Throwable
     */
    @Around(value = "@annotation(testLog)")
    public void doAround(ProceedingJoinPoint joinPoint, AspectAnnonationTest testLog) throws Throwable {
        Object[] objs = joinPoint.getArgs();
        log.info("@Around前 入参的个数:" + objs.length);
        joinPoint.proceed();
        log.info("@Aroud 后面");
    }

}

执行顺序和上面的保持一致

实战分析

日志

一般使用 注解 + AOP 实现:

  1. 灵活,可以在方法上灵活使用,比如一般的查询是不需要操作日志的。
  2. 使用 AfterThrowing 和 AfterReturning,可以拿到返回值
  3. 真正保存数据库的时候使用异步保存

redis

背景:项目的一些页面在最高权限使用的时候,由于数据量太大,需要保存到 redis 。

使用注解+AOP(Around)。这里只能使用 Around:因为在执行方法前后,我们都需要操作:

  • 方法执行前:查询数据是否最新、redis 是否有数据。没有才执行具体方法
  • 方法执行后:把数据更新到 redis

总结

上面的代码应该是很清晰了,我在本地都能跑。

只是五个通知,执行顺序问题,在我看来 After 、AfterReturning 两者的执行顺序不是那么重要,实际开发中都是选用合适的。

  • AfterReturning 能获取到返回值,After 则不行
  • 还有其他很多实用用法,比如防重提交,比如限流,都是同样的思路