xtyuns / DevTech

技术笔记, 见 Issues
0 stars 0 forks source link

SpringBoot 源码笔记 #5

Open xtyuns opened 1 year ago

xtyuns commented 1 year ago

在框架设计中, SpringBoot 大量使用了类 SPI 机制和事件广播

xtyuns commented 1 year ago

关于 @Configuration

@Configuration 注解继承了 @Component ,因此它的本质也是用于标注一个 Spring Bean,不同的是 @Configuration 常用于那些包含 @Bean 方法的类上(我们通常称为配置类),此时 Spirng 会使用 CGLIB 为这些配置类生成动态代理对象,并且在代理类中将标注了 @Bean 的方法的返回值进行缓存(只有第一次调用方法时会产生新的 Bean 并装入 IOC 容器, 后续的其他调用则直接返回 IOC 中对应的 Bean),以使得多次调用该方法时始终返回同一个对象,这被称为 FullConfiguration,也可以为配置类设置 @Configuration(proxyBeanMethods = false) 以禁用该特性,@Configuration(proxyBeanMethods = false) 等同于 @Component,这被称为 LiteConfiguration

演示代码:

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class Beans {
    @Bean
    fun defaultMap() : Map<String, Any> {
        val defaultMap = mapOf<String, Any>("now" to System.currentTimeMillis())
        println("create hashMap: ${defaultMap.hashCode()}")
        return defaultMap
    }

    @Bean
    fun defaultMapSize(): Int {
        val defaultMap = defaultMap()
        println("use defaultMap: ${defaultMap.hashCode()}")
        return defaultMap.size
    }
}

当 Spring 启动时打印内容为

create hashMap: 2013879770
use defaultMap: 2013879770

但是将 @Configuration 修改为 @Configuration(proxyBeanMethods = false)@Component 后,打印内容为

create hashMap: 2016753065
create hashMap: 2016753064
use defaultMap: 2016753064

在 IDEA 中会分别提示:

使用建议

因此当配置类中不存在 Bean Mehtod 的直接调用时,推荐使用 LiteConfiguration 以消除 CGLIB 带来的性能影响

相关源码:标注 FullConfiguration设置 BeanDefinition 的 BeanClass 为动态代理类

xtyuns commented 1 year ago

SpringBoot 自动装配流程

Spring 中常见的 ApplicationContext 类型有

前提条件:在 SpringBoot 的启动类上添加 @SpringBootApplication 注解

一、在 createApplicationContext 阶段会创建对应的 Context,并且为属性 reader 实例化一个类型为 AnnotatedBeanDefinitionReader 的对象,而该属性在实例化的过程中通过调用 AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry) 向 Context.beanFactory 中注册了一个类型为 ConfigurationClassPostProcessor 的 BeanDefinition

二、另一方面,在 prepareContext 阶段会将 SpringApplication 中的所有 source(一般都是作为参数传入 SpringApplication.run 的 primarySources) 封装为 BeanDefinition 注册到 Context.beanFactory 中

至此,SpringBoot 自动装配的前置条件都已经准备完毕,接下来的 refreshContext 将执行自动装配功能

三、在 refreshContext 阶段中 invokeBeanFactoryPostProcessors 阶段主要是按照以下顺序依次调用了 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry 和 BeanFactoryPostProcessor#postProcessBeanFactory 这两个接口中定义的方法

  1. Context.beanFactoryPostProcessors 中的对象
  2. Context.beanFactory 中 Bean 类型为 BeanDefinitionRegistryPostProcessor 的 BeanDefinition 所定义的对象
  3. Context.beanFactory 中 Bean 类型为 BeanFactoryPostProcessor 的 BeanDefinition 所定义的对象

其中在第 2 步的调用中会触发阶段一中注册的 BeanDefinition, 因此会调用 ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

四、在 ConfigurationClassPostProcessor#processConfigBeanDefinitions 中会过滤出 BeanDefinition 中的配置类(FullConfiguration 和 LiteConfiguration),并在排序后进行解析,而这之中的配置类就包含步骤二中由 primarySources 封装的 BeanDefinition(因为启动类上的 @SpringBootApplication 注解继承了 @Configuration )

五、在针对启动类的解析过程

  1. 处理来自 @SpringBootApplication@ComponentScan 注解,扫描 basePackages 下的所有 Component 并转换为 BeanDefinition 进行递归解析
  2. 处理 @EnableAutoConfiguration 中的 @Import 注解

六、在针对启动类的解析过程结束后,将对步骤五 @EnableAutoConfiguration 注解中导入的 AutoConfigurationImportSelector 进行处理,大致的流程就是加载 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中的配置类过滤并进行导入处理,导入处理的过程和解析配置类的流程类似,采用递归处理 @Import 和配置类的解析,从而将所有相关的 Bean 都封装为 BeanDefinition 注册到 Context.beanFactory 中

七、接下来,在 refreshContext 阶段中的 registerBeanPostProcessors 会采用类似步骤三的流程将类型为 BeanPostProcessor 的 BeanDefinition 实例化为对应的 Bean 并注册到 Context.beanFactory.beanPostProcessors 中

八、在 refreshContext 阶段中的 finishBeanFactoryInitialization 将会通过调用 AbstractBeanFactory#getBean(java.lang.String) 方法来触发所有非懒加载 Bean 的实例化过程

至此,refreshContext 阶段及自动装配流程结束

xtyuns commented 1 year ago

关于 @Autowired

Spring Bean 属性注入是在对象创建之后的属性填充阶段完成的。

在这个阶段将通过 BeanFactory 中 AutowiredAnnotationBeanPostProcessor#postProcessProperties 完成对 @Autowired 注解的属性注入。

所注入的属性值的是通过 DefaultListableBeanFactory#doResolveDependency 获取的,在获取备选对象的过程中主要有以下两个问题:

  1. 应用中不存在合适的备选对象
  2. 应用中存在多个备选对象

在 doResolveDependency 中会先通过 findAutowireCandidates(byType) 过滤出应用中所有的备选注入对象(如 @Qualifier 过滤)并返回一个 Map 集合。 对于第一种情况,则会进一步判断该注入属性是否为必须属性(@Autoired(required = true),默认值),如果为 required 则抛出异常终止应用启动,否则返回 null 进行属性注入。 而对于应用中存在多个备选对象的情况,则通过 determineAutowireCandidate 从上述的 map 中获取一个合适的 beanName 然后从 map 中获取对应的对象并返回(byName),获取 beanName 的具体的规则为:

  1. 优先使用 @Primary 标注的对象名称
  2. 获取候选对象所属类上的 @jakarta.annotation.Priority 注解,使用其最高优先级的对象名称
  3. 优先选用已作为候选对象成功注入的对象名称
  4. 候选对象的名称与注入属性的属性名进行匹配,如果匹配成功则使用该名称

最后,使用反射将选取的注入对象设置到目标 Bean 中。

xtyuns commented 1 year ago

SpirngBoot 中是如何实现 AOP 的

todo

xtyuns commented 11 months ago

SpringBoot 中 Controller 的注册流程

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initHandlerMethods