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
的加载和注册
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。