Featured image of post SpringBoot原理(四):常用注解分析

SpringBoot原理(四):常用注解分析

SpringBoot原理(四):@Autowired

开始之前

SpringBoot启动流程图

复习一下SpringBoot启动流程图如下所示: SpringBoot启动流程

  • SpringApplication.run():启动流程的入口
  • StopWatch.start():
  • SpringApplicationRunListeners.starting():
  • prepareEnviroment():
  • printBanner():
  • createApplicationContext():
  • prepareContext():
  • refreshContext():
  • afterRefresh():
  • StopWatch.stop():
  • SpringApplicationRunListeners.started()
  • callRunners():
  • SpringApplicationRunListeners.running():
  • return context


@Component和@Autowired的使用

在测试项目中创建一个servicecontroller

  • service代码如下:
1
2
3
@Service
public class TestService  {
}

@Service是@Component的派生注解,我们知道SpringBoot读取注解是采用递归的方式,因此这里是能获取到@Component,最终存储到IOC容器中

  • controller代码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@RestController
public class TestController {

    @Autowired(required = false)
    private TestService testService;
    
    @RequestMapping("/test")
    public void test2() {
        System.out.println("testService的地址为:"+testService);
    }
    
}

代码较简单,提供一个/Test接打印testService的地址,启动程序访问该接口控制台打印如下:

1
testService的地址为:com.cx.controller.TestService@5e5aafc6

这个比较好理解,使用@Autowired注解获取到了对应实例,正常打印了地址,这种也是我们使用SpringBoot最基本的方式。

我们修改controller,修改后的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@RestController
public class TestController implements BeanDefinitionRegistryPostProcessor  {

    @Autowired(required = false)
    private TestService testService;

    @RequestMapping("/test")
    public void test2() {
        System.out.println("testService的地址为:"+testService);
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    }
}

仅仅让TestController实现了BeanDefinitionRegistryPostProcessor接口,启动程序访问接口控制台打印如下:

1
testService的地址为:null

没有获取到该对象,地址为null。

如果你不知道获取不到service得原因,说明你不知道注解@Autowired@Component如何产生作用,说明你不了解bean的生命周期。

原理分析

在研究上述问题原因之前,我们先了解两个常见的概念:

  • BeanDefinition:用一个BeanDefinition对象来描述bean,属性包含:全限定类名、Bean行为配置元素等
  • PostProcessor:源码中常见这个单词的后缀,与后置处理器相关

@Component

在流程图的createApplicationContext()中,context的构造方法最后调用AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);, 会注册ConfigurationClassPostProcessor的BeanDefinition(context是反射创建,需要你自己找到对应的构造方法)

注册key为org.springframework.context.annotation.internalConfigurationAnnotationProcessor字符串,value为对应的BeanDefinition

注册到哪里呢? 注册到DefaultListableBeanFactorybeanDefinitionMap,该属性类型为Map<String, BeanDefinition>

context与beanFactory的关系?
context拥有一个属性beanFactory,里面有非常多集合,顾名思义主要存储对象实例

ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessorpostProcessBeanDefinitionRegistry方法,该方法获取获取启动类上的ComponentScan注解(调试发现启动类也有@Component注解),最终调用componentScanParser.parse()根据注解配置的扫描路径(没有值则根据启动类路径设置 比如启动类为com.cx.TestApplication,扫描的basePackagecom.cx),过滤参数等得到最终的set 集合,集合元素为BeanDefinitionHolder类型,该类有两个重要属性:beanNamebeanDefinition

@Configuration也是@Component的派生注解

beanName记录bean的名称,比如testController,beanDefinition实际类型是一个ScannedGenericBeanDefinition,记录对应的class、是否单例(默认就是单例)、依赖的class、 initMethodName、destroyMethodName等等

接着扫描集合中的类有无@Import注解,会导入@Import的class(注意这是一种递归扫描),还记得@Import({AutoConfigurationImportSelector.class})这个注解吗?它会加载 META-INF/spring.factories定义的bean(详情请参考文章)

还会获取@Component下的beanMethod(即带有@bean注解的方法),最终也会加载对应的bean,以前背八股文的时候说@bean必须结合@Configuration使用, 显然这是错误的,结合@Component也是一样的

小结我们调试阅读ConfigurationClassPostProcessor这个后置处理器的方法,从获取启动类的ComponentScan注解,到扫描到所有的@Component注解,并不只是如此, 还会扫描@Import注解加载对应的类,还会扫描@bean注解加载对应的类,该方法最终在哪被调用呢?流程图中的refreshContext()中的invokeBeanFactoryPostProcessors方法。


@Autowired

  • ConfigurationClassPostProcessor同样的位置(间隔几行代码)还会注册一个AutowiredAnnotationBeanPostProcessor的BeanDefinition,AutowiredAnnotationBeanPostProcessor这个后置处理器 与注解@Autowired息息相关。

  • 在执行完refreshContext()中的invokeBeanFactoryPostProcessors方法后带有@Component注解的bean已经被包装成成BeanDefinition存储到beanFactory.beanDefinitionMap,还未真正实例化, 但我们测试代码中testController作为一个后置处理器已经实例化

  • AutowiredAnnotationBeanPostProcessor这个后置处理器还未执行,这是为什么呢? 原来该后置处理器实现的是MergedBeanDefinitionPostProcessor接口,ConfigurationClassPostProcessor后置处理器实现的是BeanDefinitionRegistryPostProcessor接口,invokeBeanFactoryPostProcessors方法 中专门获取BeanDefinitionRegistryPostProcessor.class的类型。

  • 执行完AutowiredAnnotationBeanPostProcessor对应方法后后还会获取testController(测试代码中也实现了这个后置处理器接口)执行对应方法。

  • AutowiredAnnotationBeanPostProcessor这个后置处理器什么时候被调用呢?
    invokeBeanFactoryPostProcessors的下一行代码registerBeanPostProcessors会实例化AutowiredAnnotationBeanPostProcessor并注册到beanFactory,这两行代码都与后置处理器相关, 但它们有一些命名上的区别BeanFactoryPostProcessorBeanPostProcessor,这两个接口都是Spring对外暴露的扩展点。

    • BeanFactoryPostProcessor:是在实例化之前被调用
    • BeanPostProcessor:是在实例化过程中被调用

这两个是非常重要的扩展点,比如SpringBoot中的@EnableScheduling注解,用于定时任务,底层就是扩展的BeanPostProcessor接口

  • 回到上面的问题调用流程图的createApplicationContext()中的finishBeanFactoryInitialization方法(在invokeBeanFactoryPostProcessors后面)将BeanDefinition实例化
  • 实例化方法为beanFactory.getBean(),内层调用doGetBean,实例化bean时根据依赖项优先注入testService,再执行@AutoWired对应的后置处理器

小结

经过探索,我们能清晰地认识到产生上面的问题原因,在testController实现BeanDefinitionRegistryPostProcessor接口后,

在第一次调用后置器方法时,testController作为一个BeanFactoryPostProcessor已经被实例化,此时与@Autowired没有任何逻辑关系, ConfigurationClassPostProcessor会将 @Component注解的类包装成BeanDefinition实例

后面在将BeanDefinition实例创建为真正的bean时,每个bean都会调用@Autowired关联的后置处理器(如果类中有注解),每个bean在创建时会判断单例是否存在, testController已存在就不会再创建bean,也就不会执行@Autowired相关的逻辑了


其他

SpringBoot中与bean生命周期有关的注解还有:@PostConstrct@PreDestroy,它们对应的后置处理器为CommonAnnotationBeanPostProcessor

CommonAnnotationBeanPostProcessor也是在与AutowiredAnnotationBeanPostProcessor同一时机被注册,实际上本文的三个后置处理器都是同一时机注册的。

解决问题的同时也会出现新的问题:SpringBoot的后置处理器体系bean的生命周期,还需要进一步学习。

Please call the seeds under the diligent.
Built with Hugo
主题 StackJimmy 设计