Spring AOP底层是怎么织入的?——ProxyFactory、JDK Proxy vs CGLIB与编译期织入

很多人以为Spring AOP就是"动态代理",但当你发现@Transactional在某些场景下失效,或者想在不重启的情况下动态添加切面时,才发现自己对AOP的理解只停留在表面。这篇从ProxyFactory开始,讲清楚Spring是如何在运行时决定用JDK Proxy还是CGLIB的,以及AspectJ编译期织入和Spring AOP的本质区别。

一、Spring AOP的两种实现方式

1.1 JDK动态代理

java 复制代码
// 只能代理接口
public interface UserService {
    void save(User user);
}

@Service
public class UserServiceImpl implements UserService {
    public void save(User user) { ... }
}

// Spring生成的代理类(简化)
class $Proxy0 implements UserService {
    private UserService target;
    
    public void save(User user) {
        // 前置通知
        before();
        try {
            target.save(user);  // 调用目标方法
        } finally {
            // 后置通知
            after();
        }
    }
}

限制:只能代理实现了接口的类。如果类没有实现任何接口,JDK Proxy无能为力。

1.2 CGLIB代理

java 复制代码
// 可以代理具体类
@Service
public class OrderService {  // 没有实现接口
    public void create(Order order) { ... }
}

// Spring生成的代理类(简化)
class OrderService$$EnhancerByCGLIB extends OrderService {
    public void create(Order order) {
        MethodInterceptor interceptor = ...;
        interceptor.intercept(this, method, args, proxy);
    }
}

特点:通过继承实现,所以不能代理final类或final方法。

二、Spring如何选择代理方式?

核心逻辑在DefaultAopProxyFactory

java 复制代码
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() 
        || hasNoUserSuppliedProxyInterfaces(config)) {
        
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class");
        }
        
        // 如果是接口或已经是代理类 → JDK Proxy
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        
        // 否则 → CGLIB
        return new ObjenesisCglibAopProxy(config);
    } else {
        // 默认:如果有接口就用JDK Proxy
        return new JdkDynamicAopProxy(config);
    }
}

决策树

复制代码
1. 配置了 proxyTargetClass=true? → 是 → CGLIB
2. 目标类是接口? → 是 → JDK Proxy
3. 目标类没有实现任何用户定义的接口? → 是 → CGLIB
4. 默认 → JDK Proxy

2.1 强制使用CGLIB

yaml 复制代码
spring:
  aop:
    proxy-target-class: true  # 全局强制CGLIB

或者在特定Bean上:

java 复制代码
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig { ... }

三、ProxyFactory:AOP的核心工厂

java 复制代码
// 手动创建AOP代理
ProxyFactory factory = new ProxyFactory(new UserServiceImpl());
factory.addAdvice(new TransactionInterceptor());
factory.addAdvisor(new DefaultPointcutAdvisor(pointcut, advice));

UserService proxy = (UserService) factory.getProxy();

关键组件

组件 作用
TargetSource 提供目标对象(可以是单例、原型、ThreadLocal等)
Advisor 切点+通知的组合
Pointcut 匹配哪些方法需要被拦截
Advice 拦截后要执行的逻辑

四、@Aspect解析流程

4.1 自动代理创建器

Spring Boot启动时,AnnotationAwareAspectJAutoProxyCreator会被注册为BeanPostProcessor:

java 复制代码
@Component
public class LoggingAspect {
    @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
    public Object logTransaction(ProceedingJoinPoint pjp) throws Throwable {
        log.info("Starting transaction");
        try {
            return pjp.proceed();
        } finally {
            log.info("Ending transaction");
        }
    }
}

解析步骤

  1. 扫描@Aspect注解的类
  2. 解析@Pointcut表达式,转换为Pointcut对象
  3. 解析@Before/@After/@Around等通知,转换为Advice对象
  4. 创建Advisor,将Pointcut和Advice组合
  5. 注册到BeanFactory,在Bean初始化时应用

4.2 Bean初始化时的代理创建

java 复制代码
// AbstractAutoProxyCreator.postProcessAfterInitialization()
public Object postProcessAfterInitialization(Object bean, String beanName) {
    if (this.advisedBeans.containsKey(beanName)) {
        return bean;
    }
    
    // 检查是否需要代理
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(beanName, Boolean.FALSE);
        return bean;
    }
    
    // 获取Advisor列表
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(...);
    
    if (specificInterceptors != DO_NOT_PROXY) {
        // 创建代理
        Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }
    
    this.advisedBeans.put(beanName, Boolean.FALSE);
    return bean;
}

五、AspectJ编译期织入 vs Spring AOP

维度 Spring AOP AspectJ(编译期/加载期)
实现方式 动态代理(运行时) 字节码修改(编译期/加载期)
性能 略低(每次调用走代理) 高(直接织入字节码)
功能 仅支持方法拦截 支持字段访问、构造器调用等
自调用 不支持(绕过代理) 支持(直接织入)
复杂度 低(Spring内置) 高(需要ajc编译器或Java Agent)

5.1 AspectJ加载期织入(LTW)

xml 复制代码
<!-- aop.xml -->
<aspectj>
    <aspects>
        <aspect name="com.example.LoggingAspect"/>
    </aspects>
    <weaver options="-verbose">
        <include within="com.example..*"/>
    </weaver>
</aspectj>
bash 复制代码
# 启动时指定Java Agent
-javaagent:path/to/aspectjweaver.jar

适用场景:需要对第三方库进行增强,且无法修改源码时。

六、常见陷阱

6.1 自调用失效

java 复制代码
@Service
public class UserService {
    @Transactional
    public void save(User user) {
        this.validate(user);  // ❌ 不走事务
    }
    
    @Transactional
    public void validate(User user) { ... }
}

原因:this.validate()是直接调用,绕过了代理对象。

解决

  1. 注入自己:@Autowired private UserService self; self.validate(user);
  2. 使用AopContext.currentProxy()
  3. 重构代码,把validate移到另一个Service

6.2 final方法无法代理

java 复制代码
@Service
public class OrderService {
    public final void process() { ... }  // ❌ CGLIB无法代理final方法
}

解决:去掉final修饰符。

6.3 私有方法无法拦截

java 复制代码
@Service
public class UserService {
    @Transactional
    private void internalSave() { ... }  // ❌ AOP无法拦截私有方法
}

原因:JDK Proxy和CGLIB都基于方法重写/接口实现,私有方法不可见。

七、总结

  1. Spring AOP基于动态代理------JDK Proxy(接口)或CGLIB(类)
  2. 默认优先JDK Proxy,除非配置proxyTargetClass=true或目标类无接口
  3. ProxyFactory是核心,负责组装TargetSource、Advisor、Pointcut
  4. @Aspect解析由AnnotationAwareAspectJAutoProxyCreator完成,在Bean初始化后创建代理
  5. AspectJ编译期/加载期织入功能更强,但复杂度更高
  6. 自调用、final方法、私有方法是AOP的三大盲区
相关推荐
长不胖的路人甲2 小时前
SpringCloud 服务雪崩、熔断、降级
后端·spring·spring cloud
卓怡学长7 小时前
w263基于springboot + vue 健康饮食管理系统
java·数据库·vue.js·spring boot·spring·intellij-idea
KINGSEA_1689 小时前
Spring事务原理与高频考点
spring
Y0011123610 小时前
SpringBoot 基础篇
java·spring boot·spring
闲猫1 天前
Spring AI Agentic 模式(第1部分):Agent Skills——模块化、可复用的能力
java·人工智能·spring
952361 天前
RabbitMQ-基础操作
java·spring boot·分布式·后端·spring·rabbitmq
卓怡学长1 天前
w266基于spring boot + vue 圣地延安美食乐享系统
java·数据库·vue.js·spring boot·spring·intellij-idea
wang09071 天前
自己动手写一个spring之MVC_3
java·spring·mvc
闲猫1 天前
Spring AI / Models / Chat Models / Ollama
java·人工智能·spring