@EnableAspectJAutoProxy
定义与作用
@EnableAspectJAutoProxy 是 Spring 提供的配置类级别注解,用于开启 Spring 对 @AspectJ 风格切面的自动代理支持。没有此注解,Spring 容器不会扫描 @Aspect 标注的类,也不会为匹配的目标 Bean 创建 AOP 代理对象。
在 Spring Boot 2.x 项目中,虽然 Spring Boot 的自动配置已经隐式开启了 AOP 支持,但在纯 Spring Framework 5.x 项目或自定义配置类中,必须显式添加此注解才能使 AOP 生效。
适用位置与常用属性
适用位置
@EnableAspectJAutoProxy 只能标注在配置类上,即带有 @Configuration 注解的类。它通过 @Import(AspectJAutoProxyRegistrar.class) 向 Spring 容器注册一个 AnnotationAwareAspectJAutoProxyCreator,该后置处理器负责在 Bean 初始化后检测是否需要创建 AOP 代理。
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.feixiang.student")
public class AppConfig {
}
常用属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
proxyTargetClass | boolean | false | 是否强制使用 CGLIB 代理。true 时优先使用 CGLIB(即使目标实现了接口);false 时优先使用 JDK 动态代理(目标有接口时) |
exposeProxy | boolean | false | 是否将代理对象暴露到 AopContext 中。设置为 true 后,可在目标方法内通过 AopContext.currentProxy() 获取当前代理对象,解决同类内部 this 调用不触发 AOP 的问题 |
核心原理
AOP代理创建流程
当 Spring 容器启动时,@EnableAspectJAutoProxy 注册的 AnnotationAwareAspectJAutoProxyCreator 作为 BeanPostProcessor 介入每个 Bean 的初始化过程:
proxyTargetClass 属性对代理方式的影响
关键理解:
proxyTargetClass = false时,Spring 遵循"有接口用 JDK,无接口用 CGLIB"的策略proxyTargetClass = true时,Spring 统一使用 CGLIB,代理对象是目标类的子类- Spring Boot 2.x 在
application.properties中默认配置了spring.aop.proxy-target-class=true,因此即使不显式设置,也倾向于使用 CGLIB
完整示例:飞翔科技学生管理系统开启 AOP 代理
场景简述
广州飞翔科技公司的后端开发小崔正在搭建学生成绩管理系统的 Spring 配置。架构师白歌要求:
- 系统必须支持 AOP 切面编程,用于后续接入日志和权限模块
- 大翔(CTO)明确要求所有代理统一使用 CGLIB,避免 JDK 代理带来的接口方法限制
- 部分 Service 内部需要同类方法调用,必须支持
AopContext.currentProxy()获取代理对象
操作前代码/配置
在未添加 @EnableAspectJAutoProxy 之前,小崔的配置类如下:
@Configuration
@ComponentScan("com.feixiang.student")
public class AppConfig {
}
此时即使编写了 @Aspect 切面类,Spring 容器也不会为其创建代理:
@Aspect
@Component
public class LogAspect {
@Before("execution(* com.feixiang.student.service.*.*(..))")
public void logBefore() {
System.out.println("[LOG] 方法即将执行");
}
}
运行结果:调用 StudentScoreService.queryScore() 时,控制台没有任何日志输出。切面完全失效。
原因:Spring 容器没有注册 AnnotationAwareAspectJAutoProxyCreator,不知道需要为 Bean 创建 AOP 代理。
使用该注解的完整代码
第一步:配置类开启 AOP 代理
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
@ComponentScan("com.feixiang.student")
public class AppConfig {
}
第二步:定义切面类(此时可正常生效)
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(* com.feixiang.student.service.*.*(..))")
public void serviceLayer() {}
@Before("serviceLayer()")
public void logBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.printf("[飞翔科技-日志] %s 方法开始执行%n", methodName);
}
}
第三步:目标 Service 类
@Service
public class StudentScoreService {
public Score queryScore(Long studentId) {
System.out.println("执行业务:查询学生 " + studentId + " 的成绩");
return new Score(studentId, 88.5);
}
public void updateScore(Long studentId, Double newScore) {
System.out.println("执行业务:更新学生 " + studentId + " 的成绩为 " + newScore);
}
}
第四步:验证代理类型
public class ProxyTypeDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(AppConfig.class);
StudentScoreService service = ctx.getBean(StudentScoreService.class);
// 打印实际类名,观察代理方式
System.out.println("Bean 实际类型:" + service.getClass().getName());
service.queryScore(2024001L);
ctx.close();
}
}
操作后运行结果及分析
控制台输出:
Bean 实际类型:com.feixiang.student.service.StudentScoreService$$EnhancerBySpringCGLIB$$3a2f1b9c
[飞翔科技-日志] queryScore 方法开始执行
执行业务:查询学生 2024001 的成绩
分析:
- 类名中包含
$$EnhancerBySpringCGLIB$$,证明确实使用了 CGLIB 代理 @Before日志正常输出,证明@EnableAspectJAutoProxy已成功开启 AOP 支持- 由于设置了
exposeProxy = true,后续可在StudentScoreService内部通过AopContext.currentProxy()获取代理对象,解决同类内部调用不触发 AOP 的问题
对比实验:将 proxyTargetClass 改为 false,且确保 StudentScoreService 实现了接口:
public interface ScoreService {
Score queryScore(Long studentId);
void updateScore(Long studentId, Double newScore);
}
@Service
public class StudentScoreService implements ScoreService {
// ...
}
此时运行输出:
Bean 实际类型:com.sun.proxy.$Proxy42
类名以 $Proxy 开头,证明使用了 JDK 动态代理。此时只能通过接口类型获取 Bean:ctx.getBean(ScoreService.class),若通过实现类类型获取会抛出 NoSuchBeanDefinitionException。
易错场景与面试考点
易错场景一:忘记添加 @EnableAspectJAutoProxy 导致切面不生效
反例:
@Configuration
@ComponentScan("com.feixiang.student")
// 错误!缺少 @EnableAspectJAutoProxy
public class AppConfig {
}
@Aspect
@Component
public class SecurityAspect {
@Before("execution(* com.feixiang.student.service.*.*(..))")
public void checkPermission() {
System.out.println("权限校验通过");
}
}
现象:应用正常启动,没有任何报错,但调用 Service 方法时权限校验始终不执行。
排查思路:
- 检查配置类是否有
@EnableAspectJAutoProxy - 检查切面类是否有
@Aspect和@Component(或任意 Spring 组件注解) - 检查 Pointcut 表达式是否匹配正确包路径
- 检查目标类是否被 Spring 容器管理(是否有
@Service等注解且在扫描包下)
易错场景二:JDK 代理下通过实现类类型获取 Bean 失败
反例:
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false) // 使用 JDK 代理
public class AppConfig {
}
@Service
public class StudentScoreService implements ScoreService {
}
// 错误!JDK 代理下不能通过实现类获取
StudentScoreService service = ctx.getBean(StudentScoreService.class);
// 抛出 NoSuchBeanDefinitionException
正确做法:
// JDK 代理下必须通过接口获取
ScoreService service = ctx.getBean(ScoreService.class);
或统一设置 proxyTargetClass = true,允许通过实现类获取。
面试高频考点
考点一:@EnableAspectJAutoProxy 的作用是什么?不添加会怎样?
该注解向 Spring 容器注册
AnnotationAwareAspectJAutoProxyCreator,用于在 Bean 初始化后检测是否需要创建 AOP 代理。不添加此注解,Spring 不会识别@Aspect切面类,也不会为 Bean 创建代理,所有 Advice 失效。
考点二:proxyTargetClass = true 和 false 的区别?
false(Spring 默认)时,目标类实现接口则使用 JDK 动态代理,未实现接口则使用 CGLIB。true(Spring Boot 2.x 默认)时,统一使用 CGLIB 代理,无论目标是否实现接口。CGLIB 通过生成子类实现代理,无法代理final类和方法;JDK 代理基于接口,只能代理接口中声明的方法。
考点三:exposeProxy = true 有什么作用?
将当前代理对象暴露到
AopContext的 ThreadLocal 中,使得目标方法内部可以通过AopContext.currentProxy()获取代理对象。这是解决同类内部this.method()调用不触发 AOP 问题的关键手段之一。