Springboot自动装配过程
什么是Springboot自动装配?
我们使用ssm开发的时候,需要写spring.xml或者配置bean,需要配置<bean/>、@Bean把类交给工厂管理。而反观springboot开发的时候,只需要配置一些简单的.properties(.yml)就行了。其实springboot就是把这些我们以前要写的<bean/>、@Bean封装成启动器(starter)导入交给工厂了。
需要了解springboot的自动装配过程,必须从如下几个相关注解开始
- @SpringBootApplication
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
}
@Target({ElementType.TYPE}):注解可以标记在哪;
@Retention(RetentionPolicy.RUNTIME):注解标注的类编译以什么方式保留;
@Documented:生成文档信息的时候保留注解,对类作辅助说明;
@Inherited:继承关系;
@SpringBootConfiguration:Spring Boot的配置类;
@EnableAutoConfiguration:开启自动配置功能;
@ComponentScan(包路径):扫描包;
excludeFilters:排除策略;
如:@ComponentScan(basePackages = "com.not_lsj",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = {"com.not_lsj.controller.*"})})
扫描 com.not_lsj 包,但是把controller包下所有的类都排除掉,不参与扫描
- @EnableAutoConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
}
@AutoConfigurationPackage:将当前配置类所在包保存在BasePackages的Bean中,供Spring内部使用。就是注册了一个bean,这个bean用来保存当前配置类的;
@Import({AutoConfigurationImportSelector.class}):导入当前类;
-
Conditional衍生注解
作用:条件注解,必须是@Conditional指定的条件成立,才给容器中添加组件,组件才生效。
列举几种常见的:
@ConditionalOnBean:容器中存在指定Bean
@ConditionalOnMissingBean:容器中不存在指定Bean
@ConditionalOnClass:系统中有指定的类
@ConditionalOnMissingClass:系统中没有指定的类
@ConditionalOnProperty:系统中指定的属性是否有指定的值
@ConditionalOnWebApplication:当前是web环境
......
顺便说一下,spring创建bean的几种方式:
- XML解析
- 注解(@Configuration、@Component(及其衍生注解)、@Bean)
- ClassPathBeanDefinitionScanner扫描特定路径下的具有注解的Bean 进行注册
- @import
在此介绍一下@Import注解:作用是导入某个类(一般用于封装)
A.class是一个普通类
- @Import(A.class):直接导入某个类
B implements importSelector
- @Import(B.class):一次导入多个类
importSelector有一个子接口 DeferredImportSelector
C implements importBeanDefinitionRegister
- @Import(C.class):导入一个定制的类
Springboot就是用的@Import注解实现自动配置,实现了DeferredImportSelector接口
- DeferredImportSelector
有两大特性
- 延迟加载:spring源码中解析@Import注解,都是再其他注解解析完后。
如:某个Bean在容器中通过@Bean注册了,我们需要定制同一个Bean,这时就需要延迟加载了,因为会通过@ConditionOnBean..校验当前容器是否存在这个Bean,如果存在,我们定制的就不生效。- 分组:当前类导入的Bean,都是与Spring容器里面的Bean分开来的,排序只是在本组内;
如:当前spring自己也有很多Bean要注册,然后我们通过DeferredImportSelector又导入一组Bean,这两组Bean时互补干扰的,相反如果他们没有分组,会导致容器的与我们定制的不能保证加载顺序,上诉的@ConditionOnBean..校验会存在问题;如果先加载我们定制的Bean,而到容器加载的时候,发现存在这个Bean就会报错。
实现类的方法执行顺序:
先调用getImportGroup()
有返回值:执行返回值的内容 -->process() -->selectImports(),会实现排序
无返回值:执行父类的selectImports()导入Bean
- 部分实现的源码
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
});
// getAutoConfigurationEntry进行扫描具有META-INF/spring.factories文件的jar
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();
while(var4.hasNext()) {
String importClassName = (String)var4.next();
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
通过@Import导入一个DeferredImportSelector类型的类:AutoConfigurationImportSelector
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
// 获取EnableAutoConfiguration注解属性信息
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 从META‐INF/spring.factories中获得候选的自动配置类(!!!)
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 排重
configurations = this.removeDuplicates(configurations);
//根据EnableAutoConfiguration注解中属性,获取不需要自动装配的类名单
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
// 根据:@EnableAutoConfiguration.exclude
// @EnableAutoConfiguration.excludeName
// spring.autoconfigure.exclude 进行排除
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 通过读取spring.factories 中的OnBeanCondition\OnClassCondition\OnWebApplicationCondition进行过滤
configurations = this.getConfigurationClassFilter().filter(configurations);
// 这个方法是调用实现了AutoConfigurationImportListener的bean.. 分别把候选的配置名单,和排除的配置名单传进去做扩展
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
// 最后排序并注册Bean
public Iterable<Entry> selectImports() {}
每一个springboot的应用,都会导入一个spring-boot-autoconfigure模块,而META-INF/spring.factories文件就在这个模块下面。而校验自动装配生效的是org.springframework.boot.autoconfigure.EnableAutoConfiguration,如图:
说明:在springboot启动类内部执行run时,会执行selectImports()方法,找到对应的配置类的权限定名对应的class,加载到spring容器中。
最后看个例子:上面截图的AopAutoConfiguration
@Configuration(
proxyBeanMethods = false // @Configuration表示是一个配置类, proxyBeanMethods = false表示当前类不是代理类(默认true)
)
@ConditionalOnProperty( // 表示当前项目环境必须存在某种配置
prefix = "spring.aop",
name = {"auto"}, // 必须spring.aop.auto配置
havingValue = "true",
matchIfMissing = true // 如果等于false,不存在spring.aop.auto,aop不生效
) // 但是这里等于true表示不存在spring.aop.auto配置,就是用默认的aop
public class AopAutoConfiguration {
}
pom.xml引入xxx-starter,自动装配xxx功能,根据定义的规则校验成功之后,就是将所需的类导入spring工厂管理,完成自动装配。
-
流程图
总结:
- 首先通过@Import导入一个DeferredImportSelector类型的类
- 扫描所有jar中的META‐INF/spring.factories
- 得到所有的有类路径的Bean组成List进行排序加载
- 最后spring就能管理这些类了
- 自定义启动器(starter)
通过对springboot自动配置原理的了解,只要根据springboot规则,通过org.springframework.boot.autoconfigure.EnableAutoConfiguration导入,就能制定我们自定义的starter:
规则:
启动器(starter)就是空的jar文件,用来提供辅助性依赖管理。
自定义一个spring-boot-autoconfigure的配置模块,写配置类
命名:
官方:spring-boot-starter-xxx
非官方:xxx-spring-boot-starter(mybatis)
实现步骤:
构建一个管理父项目(类是微服务的父项目)
构建xxx-spring-boot-autoconfigure模块实现配置类
构建xxx-spring-boot-starter提供调用
自定义starter案例:https://gitee.com/not-lsj/spring-boot-starter.git
结尾:
- 本文介绍了springboot自动装配原理
- 模拟实现了自定义starter
- 某个时刻会把springboot启动原理总结发布和两篇文章的总结流程图奉上
- 欢迎来交流
最后的最后:第一次写总结,可能会不太好,但是会越来越努力!!!