Spring AOP 的实现原理

133 阅读5分钟

Spring AOP 的实现原理主要基于 动态代理技术,其核心是通过在运行时生成代理对象,将切面逻辑(如日志、事务等)织入到目标方法中。以下是其实现原理的详细解析:


一、动态代理的两种实现方式

Spring AOP 根据目标对象是否实现接口,选择以下两种动态代理方式:

1. JDK 动态代理(接口代理)

  • 适用场景:目标类实现了至少一个接口。

  • 实现原理

    • 通过 java.lang.reflect.Proxy 类动态生成代理对象。
    • 代理对象会实现目标接口,并重写接口方法。
    • 代理对象内部通过 InvocationHandler 接口的 invoke() 方法拦截目标方法的调用。
  • 示例代码

    public class JdkProxyDemo {
        public static void main(String[] args) {
            TargetInterface target = new TargetImpl();
            TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                (proxyObj, method, methodArgs) -> {
                    System.out.println("Before method: " + method.getName());
                    Object result = method.invoke(target, methodArgs);
                    System.out.println("After method: " + method.getName());
                    return result;
                }
            );
            proxy.doSomething();
        }
    }
    
  • 优点:无需引入第三方库。

  • 缺点:只能代理接口方法,无法代理未实现接口的类。

2. CGLIB 动态代理(子类代理)

  • 适用场景:目标类未实现接口。

  • 实现原理

    • 通过字节码框架 CGLIB 动态生成目标类的子类作为代理。
    • 代理子类会重写父类(目标类)的非 final 方法。
    • 拦截逻辑通过 MethodInterceptor 接口的 intercept() 方法实现。
  • 示例代码

    public class CglibProxyDemo {
        public static void main(String[] args) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(TargetClass.class);
            enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
                System.out.println("Before method: " + method.getName());
                Object result = proxy.invokeSuper(obj, args);
                System.out.println("After method: " + method.getName());
                return result;
            });
            TargetClass proxy = (TargetClass) enhancer.create();
            proxy.doSomething();
        }
    }
    
  • 优点:可代理未实现接口的类。

  • 缺点

    • 无法代理 final 类或 final 方法。
    • 需要引入 CGLIB 依赖(Spring 已内置)。

二、Spring AOP 的代理创建流程

当 Spring 容器初始化 Bean 时,会按以下步骤决定是否生成代理对象:

  1. Bean 的创建与初始化 Spring 容器创建目标 Bean 的实例(如 UserService)。

    在这之前,容器初始化的时候会通过@EnableAspectJAutoProxy 注册AOP的beanpostprocessor AnnotationAwareAspectJAutoProxyCreator

  2. 切面匹配检查 遍历所有切面(@Aspect 类),检查当前 Bean 是否有方法匹配切点表达式(Pointcut)。并得到advisor(包含pointcut与advice)

  3. 代理决策

    • 若目标 Bean 实现了接口 → 使用 JDK 动态代理
    • 若未实现接口 → 使用 CGLIB 代理
    • 可通过 @EnableAspectJAutoProxy(proxyTargetClass = true) 强制使用 CGLIB。
  4. 代理对象的生成

    • 动态生成代理类,并替换原始 Bean。
    • 代理对象会包含所有匹配的切面逻辑(Advice)。
  5. 方法调用拦截 当调用代理对象的方法时:

    • 代理对象根据方法签名匹配对应的切面。
    • 按顺序执行通知链(Advice Chain)中的逻辑(如 @Before@Around → 目标方法 → @After 等)。

三、代理对象的核心工作流程

@Around 环绕通知为例,代理对象的方法调用过程如下:

public class ProxyExample {
    // 伪代码:代理对象的拦截逻辑
    public Object interceptedMethod(Method method, Object[] args) {
        // 1. 执行 @Before 通知
        for (BeforeAdvice advice : beforeAdvices) {
            advice.before();
        }
​
        Object result = null;
        try {
            // 2. 执行 @Around 的 proceed() 前的逻辑
            for (AroundAdvice advice : aroundAdvices) {
                // 调用 proceed() 会进入下一个 Around 或执行目标方法
                result = advice.around(() -> {
                    return method.invoke(targetObject, args); // 最终调用原始方法
                });
            }
​
            // 3. 执行 @AfterReturning 通知
            for (AfterReturningAdvice advice : afterReturningAdvices) {
                advice.afterReturning(result);
            }
        } catch (Exception e) {
            // 4. 执行 @AfterThrowing 通知
            for (AfterThrowingAdvice advice : afterThrowingAdvices) {
                advice.afterThrowing(e);
            }
            throw e;
        } finally {
            // 5. 执行 @After 通知
            for (AfterAdvice advice : afterAdvices) {
                advice.after();
            }
        }
        return result;
    }
}

四、关键问题解析

1. 为什么同类内的方法调用不会触发 AOP?

  • 原因: Spring AOP 的代理对象是外部调用的入口,而同类内的方法调用(如 this.methodB())直接使用的是原始对象(this),而非代理对象,因此无法被拦截。

  • 解决方案

    • 通过 AopContext 获取当前代理对象:

      ((UserService) AopContext.currentProxy()).methodB();
      
    • 使用 AspectJ 的编译时织入(需配置 @EnableLoadTimeWeaving)。

2. Spring 如何优化代理对象的创建?

  • 代理对象缓存: Spring 会缓存已生成的代理对象,避免重复创建。
  • 延迟初始化: 代理对象在首次方法调用时生成(结合 @Lazy 注解使用)。

3. 动态代理的性能开销

  • JDK 动态代理:基于反射调用,性能略低。
  • CGLIB:生成子类直接调用方法(通过 FastClass 机制),性能接近原生代码。
  • 优化建议: 避免在频繁调用的方法上使用复杂的切面逻辑。

五、Spring AOP 与 AspectJ 的本质区别

特性Spring AOPAspectJ
织入时机运行时动态代理编译时或类加载时(LTW)
粒度仅方法级别方法、构造器、字段、静态代码块等
性能动态代理有轻微性能损耗直接修改字节码,性能更高
依赖无需额外配置需 AspectJ 编译器或 Load-Time Weaving
使用场景适合简单横切逻辑(如日志、事务)复杂切面需求(如修改字段、监控系统级行为)

六、总结

Spring AOP 的实现原理核心是 动态代理,通过运行时生成代理对象拦截方法调用,将横切逻辑与业务代码解耦。其关键点包括:

  1. 两种代理方式:JDK 动态代理(接口)和 CGLIB(子类)。
  2. 代理创建流程:Bean 初始化时匹配切面,动态生成代理对象。
  3. 拦截逻辑链:按顺序执行多个通知(Advice),实现环绕、前置、后置等逻辑。
  4. 局限性:同类调用不触发代理、仅支持方法级别拦截。

对于高性能或复杂切面需求,可结合 AspectJ 使用。理解其原理有助于合理设计切面,避免因代理机制导致的隐蔽问题(如事务失效)。