共计 7654 个字符,预计需要花费 20 分钟才能阅读完成。
SpringBoot 自动配置
我们都知道一个 SpringBoot 主配置类只要标注上 <u>@SpringBootApplication</u> 的注解,Spring 就会帮我们自动配置各个组件和实例化 Bean,我们来通过源码分析一下 SpringBoot 自动配置原理。
首先我们要知道,SpringBoot 将符合条件的 @Configuration 类都加载到 Spring 容器中,就像一只八爪鱼,我们的启动类就是一个典型的 @Configuration 类。
@SpringBootApplication
包括下面两个关键的注解
@SpringBootConfiguration
@EnableAutoConfiguration
其中 @SpringBootConfiguration 就是 get 主配置类添加上 @Configuration 注解让主配置类的自动配置能被扫描到
下面我们主要分析一下 @EnableAutoConfiguration 注解
@EnableAutoConfiguration
其中也包含两个关键注解
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
- 第一个用作包扫描自动配置
- 第二个导入 AutoConfigurationImportSelector 类用作 SpringBoot 提供的其他组件的自动配置选择器
我们先看一下第一个
@AutoConfigurationPackage
这个注解导入了 SpringBoot 中的 Registrar 类 用作包路径下的 Bean 扫描并注册到 BeanFactory 中
@Import({Registrar.class})
详细看一下这个类
Registrar 注册类
其中主要的方法是 registerBeanDefinitions
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 获取到元信息的包名传入注册器
AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
}
传入两个参数:
- metadata 启动类元信息
- registry 用作注册的 Bean 注册器
目录结构如下:
元信息如下:
new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()获取到该启动类所在路径的包名,传如 register 方法注册该包名下的所有需要注册并实例化的 Bean(包括 @Component @Service @Mapper @Repository 等)
AutoConfigurationPackages 中 register 方法
public static void register(BeanDefinitionRegistry registry, String... packageNames)
根据传入的 register 和包名 packageName 注册该包名下的所有需要注册并实例化的 Bean
其中我们要关注的是下面这段代码:
- GenericBeanDefinition 创建 Bean 的一站式组件,包括 Bean 的参数、属性、类的信息
// 新建一个 GenericBeanDefinition 描述 Bean 的实例
GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); // 设置 bean 的类名称
beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class);
// 获取构造器参数并保存
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
//Bean 的角色 感兴趣的可以去了解一下,有 0,1,2 对应三种不同角色
beanDefinition.setRole(2);
// 参数设置完调用 registerBeanDefinition 注册并实例化 Bean
registry.registerBeanDefinition(BEAN, beanDefinition);
}
getConstructorArgumentValues
这个方法用于获取构造器参数并保存
- ConstructorArgumentValues 是一个构造器参数保存器,保存 Bean 的构造方法的参数
public ConstructorArgumentValues getConstructorArgumentValues() {if (this.constructorArgumentValues == null) {
// 创建一个新的构造器参数保存器
this.constructorArgumentValues = new ConstructorArgumentValues();}
return this.constructorArgumentValues;
}
DefaultListableBeanFactory 中实现的 registerBeanDefinition 方法
该方法对 GenericBeanDefinition 创建的 Bean 进行注册到 BeanFactory
传入 beanName 和 beanDefinition
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
- 检查完 Bean 是否合法后先判断是否存在相同 Bean 的注册,存在抛出异常,不存在执行如下
其中主要代码如下:
// 开始注册 Bean
// 如果已启动注册状态则要加锁注册单例 singleton
if (this.hasBeanCreationStarted()) {synchronized(this.beanDefinitionMap) {
// 把 Bean 存入 beanDefinitionMap
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1);
// 把需要注册的 Bean 添加到 map 中
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
// 默认为单例
if (this.manualSingletonNames.contains(beanName)) {Set<String> updatedSingletons = new LinkedHashSet(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
// 如果未启动直接注册无需加锁
} else {this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
下图就是 beanDefinitionMap 返回的值,里面除了 Spring 框架提供的一些必要的 Bean 需要注册外,就是我们主启动类所在包下的所有需要扫描的 Bean,我只有一个主启动类和一个 controller 下面标出
当我尝试把写的 HelloWorldController 的 @RestController 注解注释掉以后,SpringBoot 没有扫描到这个 Controller,也就没有把它注册到 BeanFactory 中
AutoConfigurationImportSelector
看完 @AutoConfigurationPackage 注解我们看一下 @EnableAutoConfiguration 另一个注解 @Import({AutoConfigurationImportSelector.class}) 该注解导入了 SpringBoot 中 AutoConfigurationImportSelector 类(自动配置选择器)用作选择 SpringBoot 提供的所需组件 Bean 的选择并自动配置
主要是下面的方法
getAutoConfigurationEntry 方法
传入两个参数
- autoConfigurationMetadata 自顶配置元信息
- annotationMetadata 注解元信息
注解元信息的参数(配置类上添加的 @ComponentScan(Exclude)):
excludeName 和 exclude 表示需要排除扫描自动配置的类,String[0]表示了没有需要排除的
// 获取注解元信息参数
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 调用 getCandidateConfigurations 获取需要自动配置的类或者功能
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 去重
configurations = this.removeDuplicates(configurations);
// 检查并排除 exclude 类
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
getCandidateConfigurations
负责加载 META-INF/spring.factories 中的配置的类,这些类就是 SpringBoot 提供的所需要加载的那些 *AutoConfiguration 类,也就是要注册的 Bean 或功能,获取到候选类的 BeanName 返回一个 List
借助 SpringFactoriesLoader 类实现加载自动配置类
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 加载类路径下 META-INF/spring.factories 中的自动配置类
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
返回结果如下:
loadFactoryNames
SpringBoot 使用 ClassLoader 类加载机制加载 META-INF/spring.factories
将根据 EnableAutoConfiguration 类名称去加载需要的类或者功能
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
SpringFactoriesLoader 的实现机制跟 util 包下的 ServiceLoader(SPL)实现机制类似,是一种服务查找机制,为接口查找服务实现类,感兴趣的可以去了解一下
DependsOn 注解
DependsOn 注解是一个标注在类上的注解,可以帮助我们定义依赖 bean 之间的配置注册顺序
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DependsOn {String[] value() default {};
}
比如:
一般加载:
我们在 SearchController 中注入了 WordConifg 这个 bean,那么在配置时,会优先配置 WordConfig 这个类,之后才会继续配置 SearchController 这个类,然后加载 SearchController 时会先检查是否在 IOC 容器中存在 WordConfig 这个 bean,有就加载 Controller,没有就报错
@DependsOn({"wordConfig"})
public class SearchController {
@Autowired
private WordConfig wordConfig;
}
但是如果是这样,IOC 就不能正确判断优先加载哪个 bean,(Service 中相互引用
),但是这两个类中都有两个初始化方法:
- afterPropertiesSet
- 或者 postConstruct 方法(先于 afterPropertiesSet 执行)
@Service
public class A implements InitializingBean{
@Autowired
private B b;
public void afterPropertiesSet(){}
}
@Service
public class B implements InitializingBean {
@Autowired
private A a;
public void afterPropertiesSet(){}
}
如果此时我们想要 A 的初始化方法
afterPropertiesSet
先于 B 的初始化方法执行,那么我们需要使用到 @DependsOn 注解,
如下:
标注上 @DependsOn({“b”})注解,说明优先加载 beanName 为 b 的 component
@Service
@DependsOn({"b"})
public class A implements InitializingBean{
@Autowired
private B b;
public void afterPropertiesSet(){}
}
我们看看它的源码:
在 AbstractBeanFactory.doGetBean
方法中有下面这段代码
try {final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {for (String dep : dependsOn) {if (isDependent(beanName, dep)) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between'" + beanName + "'and'" + dep + "'");
}
registerDependentBean(dep, beanName);
try {getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'"+ beanName +"' depends on missing bean '"+ dep +"'", ex);
}
}
}
---------------------------------------
// 上面逻辑结束加载该 bean
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
分析下这里的逻辑:
- 拿到要加载
bean1
的BeanDefinition
- 检查该 BeanDefinition 上是否存在
dependson
注解 - 如果不为空,根据注解中的
beanName
拿到相应的beans
(多个),注册 beans - 依赖 bean 注册加载结束,执行
bean1
的加载和注册