【Spring】关于 Spring 中动态代理引发一些小问题的思考
本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
在使用 Spring 异步任务 @Async 相关的内容时,遇到了一个小问题引发了一些思考,借此文做个总结
Async 场景
问题起源于如下一段测试代码:
@SpringBootApplication
public class DemoAopApplication {
public static void main(String[] args) {
SpringApplication.run(DemoAopApplication.class, args);
}
@Autowired
ApplicationContext applicationContext;
@Component
public class TestRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println(Thread.currentThread().getName());
DemoAopApplication bean = applicationContext.getBean(DemoAopApplication.class);
bean.test();
}
}
@Async
public void test() {
System.out.println(Thread.currentThread().getName());
}
}
- 常规的
@EnabledAsync配置类此处没有贴出来 - 乍一看,这段代码没什么问题(至少我这么觉得。。),应该输出主线程名和异步线程名,但实际上抛出了
NPE,为空的正是主类的属性ApplicationContext applicationContext - 事实上,在主类中注入属性
ApplicationContext applicationContext的行为本身肯定是没问题的,执行CommandLineRunner的阶段也是在refresh阶段之后,所有bean包括主类bean的创建肯定都是已经完成的 - 在
Runner类中debug发现对应的主类DemoAopApplication是一个代理类(因为它有个@Async方法嘛) - 于是注释
@Async方法,NPE解决
Aspect 场景
同理,如果主类被 Aspect 切了,也是这样的现象
@Component
@Aspect
public class AspectTest {
@Pointcut("execution(* com.example.demoaop.*.*())")
public void pointCut() {
}
@Before("pointCut()")
public void before() {
System.out.println("proxy");
}
}
- 注释掉
@Async的方法,加入切面配置类如上,扫描路径要覆盖主类(并记得给主类写个符合切面条件的方法) - 则同样复现上述场景,至此总结这个现象为:
- 一个
bean创建的大致流程是Instantiation(实例化) -> (属性赋值) -> Initialization(初始化),而上述两种代理的场景均发生在postProcessAfterInitialization阶段,即Initialization(初始化)阶段之后,那代理对象必然是没有属性赋值的 - 了解问题症结后,继续如下场景
Configuration 场景
于是在去掉 @Async 和 @Aspect 场景下再做一次验证,可以发现成功获取属性,但主类仍然是个代理类:
- 注释掉
@Async,去掉切面类,Runner类成功访问到主类的applicationContext属性,但可见此处的主类仍旧是个代理类,这个现象是这样的: - 主类由注解
@SpringBootApplication标记,其由元注解@Configuration标记,而在容器启动的postProcessBeanFactory阶段,会对所有@Configuration(FULL 配置类)进行enhancer增强:进行一次代理使这些配置类中的方法调用指向容器 - 上述代理行为发生在
postProcessBeanFactory(bean 工厂的后置处理)阶段,此时还未开始容器中的bean的创建,因此enhancer后的主类也在后续生命周期阶段被成功赋值了applicationContext属性 - 可以使用
@Configuration的proxyBeanMethods = false属性来关闭enhancer行为,关闭后下图如预期:
总结
起初乍一下遇到一个本不该出现的问题,内心还蛮困惑,但经过排查梳理后清晰了一点,同时也总结回顾了一些知识点,写此文分享