Spring AOP 的实现原理

一、AOP的基本概念

将横切关注点(日志、事务、权限)从业务逻辑中分离出来,提高代码的可维护性。

下面将解释,AOP专属名词,切面、连接点、切点、通知、目标对象、代理对象:

  • 切面:切面是封装横切关注点的模块,比如日志记录。 @Aspect 修饰类,如 LoggingAspect
  • 连接点:连接点就是作用的实际方法
  • 切点:@Pointcut("execution(* org.example.tao.service.impl.UserServiceImpl.getAllUsers(..))")
  • 通知:@Before @AfterReturning @AfterThrowing @After @Around
  • 目标对象:原始业务对象
  • 代理对象:Spring 启动时动态生成的代理对象

二、Spring AOP

2.1 实现原理

Spring AOP 是基于动态代理实现的,具体有俩种代理方式:

  • JDK 动态代理(需要有接口)
  • CGLIB 动态代理

代理模式 https://www.cnblogs.com/handsometaoa/p/16107991.html

2.2 优劣势

优点:

  • 解耦:将横切关注点与业务逻辑分离,提高代码的可维护性。
  • 灵活:通过切点表达式可以灵活地定义拦截规则。
  • 非侵入式:无需修改目标类代码,即可实现功能增强。

缺点:

  • 性能开销:动态代理会引入一定的性能开销。
  • 局限性:只能拦截Spring管理的Bean,且无法拦截非public方法。

2.3 工作流程

  1. 定义切面:
    使用@Aspect注解定义切面类。
    在切面类中定义通知方法,并使用@Before、@After等注解指定通知类型。
  2. 定义切点:
    使用@Pointcut注解定义切点表达式,指定哪些方法会被拦截。
  3. 生成代理对象:
    Spring容器在初始化时,根据切面和切点生成代理对象。
    如果目标类实现了接口,使用JDK动态代理;否则使用CGLIB动态代理。
  4. 执行通知:
    当目标方法被调用时,代理对象会根据切点表达式判断是否需要拦截。
    如果需要拦截,则按照通知类型(如前置通知、后置通知)执行通知逻辑。

三、展示一下

3.1 引入依赖

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

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
</dependency>

3.2 定义切面类

@Aspect // 封装切面关注点的模块,比如日志记录
@Component
public class LoggingAspect {


    // 切点,通过表达式匹配连接点  // todo: 实际替换为自己想要切面的方法。
    @Pointcut("execution(* org.example.tao.service.impl.UserServiceImpl.getAllUsers(..))")
    public void loggingPointCut() {}

    // 前置通知,作用于连接点
    @Before("loggingPointCut()")
    public void logBefore() {
        System.out.println("方法执行前的日志记录");
    }

    @AfterReturning(pointcut = "loggingPointCut()", returning = "result")
    public void logAfterReturning(Object result) {
        System.out.println("方法返回后的日志记录,返回值:" + result);
    }

    @AfterThrowing(pointcut = "loggingPointCut()", throwing = "exception")
    public void logAfterThrowing(Exception exception) {
        System.out.println("方法抛出异常后的日志记录,异常:" + exception.getMessage());
    }

    @After("loggingPointCut()")
    public void logAfter() {
        System.out.println("方法执行后的日志记录");
    }

    @Around("loggingPointCut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Signature signature = joinPoint.getSignature();
        System.out.println("环绕日志开始,方法名:" + signature.getName());
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed(); // 执行目标方法
        long elapsedTime = System.currentTimeMillis() - start;
        System.out.println("环绕日志结束,耗时:" + elapsedTime + "ms");
        return result;
    }

}


3.3 验证

启动程序后,调用连接点方法,便会执行方法。

posted @ 2025-03-20 00:08  帅气的涛啊  阅读(64)  评论(0)    收藏  举报