哎呀,又是午夜时分,又是一个失眠的夜晚,和去年一样,记得去年今日,也是睡不着觉,就注册了csdn的账号,开始写东西,csdn真是深夜最好的安魂剂。
Spring都发布了6.0,这不赶紧看看源码,咱们来一起学习学习,废话不多说了,开始吧。
文章目录
- IOC核心流程简介
- BeanDefinition的设计思想
- 解析@ComponentScan
- 解析@Bean
- 解析@Import
IOC核心流程简介
环境:Spring6、SpringBoot3.0、JDK17
读源码小tips:
- 多看注释
- 梳理整体设计思想
- don’t care
IOC是一个容器,对象的创建、使用和销毁都是由IOC容器来管理。
小白可以先看这篇:《五分钟带你速通Spring IOC》
大体的流程如下:
- 加载配置文件(XML\YAML…)、配置类,并解析成BeanDefinition
- BeanFactoryPostProcessor对BeanDefinition做一些处理
- 实例化bean对象
- 初始化bean对象(属性填充等),并且在初始化前后通过BeanPostProcessor对bean对象进行相关处理
所以,我们先从第一步开始:BeanDefinition的封装
BeanDefinition的设计思想
BeanDefinition是存储Bean的元信息,包括Bean本身的信息,以及Bean注解信息。这个信息就是从我们的配置文件以及配置类等加载以及进行一系列处理而来。
以注解这种方式来说,大概有三种:
- 以@ComponentScan扫描的方式,包括@Controller、@Service、@Repository、@Component。
- 以@Configuration声明的配置类,内部会用@Bean来声明需要创建的对象
- @Import,用来引入其他组件
首先会构建一个解析器去扫描所有的@Controller、@Service、@Repository、@Component、@Configuration类
通过这三种方法加载 BeanDefinition,并将他们放到缓存beanDefinitionMap中。
那么是如何解析这些注解的?时机是什么?我们来一起看一看
相关资料:《@Import注解的作用以及如何使用》
解析@ComponentScan
以SpringBoot的启动来说,我们通过SpringApplication#run,最终调用AbstractApplicationContext 的 refresh() 方法。
invokeBeanFactoryPostProcessors这个方法就是用来加载BeanDefinition。
通过如下方法
- PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
- ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
- ConfigurationClassPostProcessor#processConfigBeanDefinitions
最后进入到ConfigurationClassPostProcessor 的 processConfigBeanDefinitions 方法
这里是找到我们启动类的BeanDefinition,然后构建了一个ConfigurationClassParser解析器,去扫描我们加上注解的类,并加载为 BeanDefinition。
进入 parse 方法,最终我们会来到,老套路了,方法前面加do的,都是比较核心的方法,包括后面扫描的时候有一个doScan。
进入到 ConfigurationClassParser 的 doProcessConfigurationClass 方法
这里可以看到,这个又有一个解析器ComponentScanAnnotationParser,它是用来处理@Controller、@Service、@Repository、@Component、@Configuration这些注解的。
我们来看看它做了什么事。
首先构建了一个ClassPathBeanDefinitionScanner对象,然后对它进行一些set操作,最后进入核心方法doScan中。
首先findCandidateComponents(basePackage) 方法会扫描启动类所在的包(默认),找到符合条件的类(被@ComponentScan扫描到的,以及@Configuration),最后在通过registerBeanDefinition(definitionHolder, this.registry)方法将BeanDefinition注册金beanDefinitionMap中。
我们可以细讲一下这两个地方,首先看如何筛选类的。
findCandidateComponents(basePackage) 方法最后进入下面这个核心方法
首先通过getResourcePatternResolver().getResources(packageSearchPath)加载出所有的类并封装成Resource数组,然后在通过isCandidateComponent(metadataReader)筛选出符合的类,最后构建成BeanDefinition类,添加到set集合中,最后返回。
registerBeanDefinition(definitionHolder, this.registry)方法呢,就比较直接了,如果在缓存中获取不到就直接加锁,然后添进缓存中。
到此,@ComponentScan 就扫描完成了,BeanDefinition 也加载完成了。
解析@Bean
大多时候,@Bean是和@Configuration一起使用的,由上文可知,@Configuration相关类在ComponentScanAnnotationParser处就会被解析到。
如图上面这个方法是上文一直在讲解的(讲@ComponentScan扫描到的类加入缓存中),下面这个就是真正解析@Bean和@Import并将其加入缓存的方法。
我们来看看吧,因为一个配置类可能不止一个@Bean,所以循环对每一个@Bean处理
最后通过this.registry.registerBeanDefinition(beanName, beanDefToRegister)将@Bean相关BeanDefinition添加进beanDefinitionMap中。
解析@Import
其实@Import更为简单一些,这里优先判断其是否为@Import。
在parser.parse 方法中,先将 类转化为一个 ConfigurationClass 类,设置到它的 importedBy 属性中。然后在之后的 loadBeanDefinitions 方法中,判断 ConfigurationClass 的 importedBy 属性是否为空,如果不为空,说明是需要加载的,将它加载为 BeanDefinition,最后registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass)方法中的this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition())将BeanDefinition添加进IOC容器中。
完事了。等下一章吧