如何配置AspectJ使Spring组件在@Around拦截前完成Autowired初始化
问题根因
该问题本质是Spring Bean初始化顺序和切面编织时机冲突:单元测试场景下若切面加载/编织时机过早,会提前触发目标Bean的实例化,此时@Autowired、@Value对应的依赖还未完成注入,就会触发空指针。
可行解决方案
1. 优先使用Spring AOP代理而非原生AspectJ编织
如果无需使用AspectJ专属特性,优先采用Spring自带的AOP实现,从根源避免初始化顺序问题:
- 切面类同时标注
@Aspect和@Component,交给Spring容器统一管理 - 测试类/启动类添加
@EnableAspectJAutoProxy注解,开启Spring AOP代理能力
Spring AOP的代理对象会在所有Bean的属性注入、初始化流程完全完成后才生成,不会提前触发Bean实例化。
2. 若必须使用原生AspectJ编织,调整编织时机
如果需要用到AspectJ高级能力(比如拦截非public方法、非Spring管理类),可调整编织配置:
- 单元测试阶段关闭编译时编织,改用加载时编织(LTW),在测试类上添加
@EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.ENABLED) - 切面类添加
@Lazy注解,强制切面仅在第一次被调用时完成初始化,此时所有普通Bean已经完成依赖注入 - 若使用Maven/Gradle的AspectJ编译插件,为测试阶段的编织规则添加过滤逻辑,仅编织完成初始化的Spring Bean实例
3. 优化切面执行逻辑,兼容初始化阶段
如果配置调整成本过高,可修改@Around切面的执行逻辑,跳过未完成初始化的Bean的拦截:
@Autowired private ApplicationContext applicationContext; @Around("execution(* com.your.business.targetMethod(..))") public Object countExecTime(ProceedingJoinPoint pjp) throws Throwable { // 校验目标Bean是否处于初始化流程中 String beanName = pjp.getTarget().getClass().getName(); if (applicationContext.isSingletonCurrentlyInCreation(beanName)) { // 未完成初始化直接执行原方法 return pjp.proceed(); } // 正常执行计时逻辑 long start = System.nanoTime(); try { return pjp.proceed(); } finally { long execTime = System.nanoTime() - start; // 自行处理计时结果输出/存储逻辑 } }
4. 单元测试专属优化
如果仅在单元测试场景出现该问题,可添加如下测试配置:
- 测试类添加
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD),强制每个测试用例刷新独立的Spring上下文,避免上下文复用导致的初始化顺序异常 - 为切面类添加
@Order(Ordered.LOWEST_PRECEDENCE)注解,设置切面为最低优先级,确保所有Bean初始化相关的处理器执行完成后,切面逻辑才生效
内容的提问来源于stack exchange,提问作者user1767316
相关产品推荐
相关产品推荐

