从源码解析到手动写start
前言
一、springboot自动装配原理
1.1 自动装配
什么是自动装配?简单来说就是自动将Bean装配到Ioc容器中
以添加redis为例:
以前的添加使用方式:maven引入redis依赖,配置文件中添加redis服务器连接配置信息,xml或java文件写个redis配置文件,使用xml或找个公共的javaconfig文件注入bean,相关业务类中直接@Resource或@Autowired就可以直接调用RedisTemplate相关接口了
start方式:maven引入springboot-start-data-redis依赖,yml配置下redis服务连接信息,相关业务类中直接@Resource或@Autowired就可以直接调用RedisTemplate相关接口了
可以看到satrt方式我们并没有通过XML或注解形式把RedisTemplate注入Ioc容器,但是已经可以直接使用@Autowired注入RedisTemplate类来使用了,说明Ioc容器中已经存在RedisTemplate了,这就归功于springboot的自动装配机制
1.2 @EnableAutoConfiguration
自动装配的实现由@EnableAutoConfiguration
注解来实现,这个注解藏在@SpringBootApplication
启动类注解里,
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
java.lang.String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
java.lang.Class<?>[] exclude() default {};
java.lang.String[] excludeName() default {};
}
可以看到有个关键注解@import
,自动注入的秘密就在AutoConfigurationImportSelector
这个类里了
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
}
// 进入DeferredImportSelector
public interface DeferredImportSelector extends ImportSelector {}
// 继续进入ImportSelector
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() { /* compiled code */ }
}
AutoConfigurationImportSelector
实现了ImportSelector
,它只有一个selectImports
抽象方法,并且返回一个String[]
,在这个数组中可以指定需要装配到Ioc容器的类,当在@Import
中导入一个ImportSelector
的实现类后,会把该实现类中返回的Class名称都装载到Ioc容器中。
1.3自动装配原理
通过前面的分析可以看到,自动装配的核心就是扫描约定目录下的文件进行解析,解析完成之后把得到的Configuarion
配置类通过ImportSelector
进行导入,从而完成bean的自动装配过程。接下看看ImportSelector 具体是如何实现的吧。
定位到selectImports
方法,它是ImportSelector
的实现类,这个方法作用就是收集所有符合条件的配置类,完成自动装配autoConfigurationEntry.getConfigurations()
注意:默认引入的maven不包含源码,需要down一下才能看到
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取@EnableAutoConfiguration注解中的属性,exclude、excludeName等
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获得所有自动装配的配置类、
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
// 广播事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
其中关键的是getCandidateConfigurations
方法,这里用到了SpringFactoriesLoader
,它会扫描classpath下的META-INF/spring.factories文件,spring.factories文件中的数据以Key=Value形式存储,SpringFactoriesLoader.loadFactoryNames
会根据key的到对应的value值。所以,在获取所有自动配置类这个场景中,key=EnableAutoConfiguration
,value是多个配置类,即getCandidateConfigurations
的返回值
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
当然getAutoConfigurationEntry
作用是收集过滤配置类,配置类的注册发生在bean实例化前。由ConfigurationClassPostProcessor
类中方法postProcessBeanDefinitionRegistry()
调用processConfigBeanDefinitions()
来完成配置类中bean的扫描和注册。最终还是会调用getAutoConfigurationEntry
获得所有需要自动装配的配置类
获取所有的beanName,看BeanDefinition中是否有处理完成的标识。没有处理完成的标识则在checkConfigurationClassCandidate中判断是否是候选的需要处理的BeanDefinition,如果是就放入容器configCandidates。
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);
processConfigBeanDefinitions(registry);
}
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
}
总结一下自动装配过程:
- 通过
@Import(AutoConfigurationImportSelector.class)
实现配置类的导入AutoConfigurationImportSelector
类实现了ImportSelector
接口,重写了方法selectImports
,实现配置类的批量装配- 通过Spring提供的
SpringFactoriesLoader
机制,扫描classpath路径下的META-INF/spring.factories
,读取需要实现自动配置的配置类- 通过条件筛选的方式,把不符合条件的配置类移除,最终完成自动装配
二、手动写个start
Start组件主要有三个功能:
- 涉及相关组件的jar依赖
- 自动实现bean装配
- 自动声明并加载application.properties文件中的属性配置
本次使用redission做一个简单start
- 创建一个maven工程
redisson-spring-boot-start
- 添加maven依赖
...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.16.8</version>
</dependency>
</dependencies>
...
- 定义属性类
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties {
private String address;
private String password;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
- 定义配置类
@Configuration
@ConditionalOnClass(RedissonProperties.class)
@EnableConfigurationProperties(value = RedissonProperties.class)
public class RedissonConfiguration {
@Autowired
private RedissonProperties redissonProperties;
@Bean
@ConditionalOnMissingBean(RedissonClient.class)
public RedissonClient redissonClient() {
Config config=new Config();
config.setTransportMode(TransportMode.NIO);
SingleServerConfig singleServerConfig = config.useSingleServer();
String addr =redissonProperties.getAddress();
if(StringUtils.isEmpty(addr)&&!addr.startsWith("redis")){
addr = "redis://"+addr;
}
singleServerConfig.setAddress(addr);
singleServerConfig.setPassword(redissonProperties.getPassword());
return Redisson.create(config);
}
}
- 在resources下创建META-INF/spring.factories文件,使得springboot可以扫描到文件完成自动装配
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.dreamfsk.configuration.RedissonConfiguration
- 发布到maven仓库
- 指定自己的groupId、artifactId、version
- 定义好distributionManagement
- 推送到maven仓库
- 引用start
maven的setting配置文件添加自己仓库的mirror或者项目maven配置文件添加自己仓库的资源引用
// 项目中引入maven
<dependency>
<groupId>com.dreamfsk</groupId>
<artifactId>redisson-spring-boot-start</artifactId>
<version>1.0</version>
</dependency>
// 配置文件增加redission配置
redisson.address = 127.0.0.1
redission.password = 123
总结
本次demo比较简单,不过自动装配原理搞懂了,剩下的就可以自由发挥了