1.概述
之前我们对Spring
的注解导入@Import 和 注解扫描@ComponentScan分别进行了详细的总结,不清楚的可以点击链接自行阅读了解,基于这些总结的知识点,我们今天可以来分析一下Spring Boot
自动配置的实现原理和自己手动封装一个starter了。
我们一直在强调Spring Boot
能成为当下主流首选开发框架的主要原因在于其核心思想:约定大于配置,自动配置,条件装配。基于这些特性使得Spring Boot
集成其他框架非常简单快捷。
使用Spring Boot
创建的项目启动、执行也非常简单,只需要执行启动类的main()方法即可,不需要做其他操作,Spring Boot
会自动装配相关所需依赖和配置。
@SpringBootApplication
public class CommonDemoApplication {
public static void main(String[] args) {
SpringApplication.run(CommonDemoApplication.class, args);
}
}
项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用
Github地址:https://github.com/plasticene/plasticene-boot-starter-parent
Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parent
微信公众号:Shepherd进阶笔记
2.Spring Boot自动配置原理
从上面项目启动类可以看出,没有什么复杂的启动逻辑,就只使用一个注解@SpringBootApplication
,这就是Spring Boot
自动配置的核心入口所在,其定义如下:
@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 {
// 排除掉自动配置的class
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
// 排除掉自动配置的全路径类名
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
// 配置扫描的包路径
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
// 配置扫描的类
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
// beanName生成器
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
// 配置类代理模式:proxyBeanMethods:代理bean的方法
// Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
// Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
从定义可知@SpringBootApplication
是一个复合注解,所以接下来我们逐一看看其关联使用的注解。
2.1 @SpringBootConfiguration
@SpringBootConfiguration
的定义如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
从定义可知,该注解就是一个配置类注解,其作用和属性和@Configuration
注解一样,只是这里语义化罢了,就像@Controller
和@Component
一个道理。
2.2 @EnableAutoConfiguration
从名字上看,@EnableAutoConfiguration
是自动配置核心所在,先看看其定义:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
可以看出@EnableAutoConfiguration
也是一个复合注解,所以我们接下来对其关联的注解进行解析:
2.2.1 @AutoConfigurationPackage
该注解的作用是将添加该注解的类所在的package作为自动配置package 进行管理,也就是说当Spring Boot应用启动时默认会将启动类所在的package作为自动配置的package。老规矩,先看看其定义:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
AutoConfigurationPackages.Registrar.class
的#register()
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
}
else {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
这里就是简单注册自动配置包名,方便后续引用,如Spring Boot
集成第三方OMR框架mybatis-plus
,我们如下编写代码就能把DAO类注入到Spring容器中:
@Mapper
public interface BrandDAO extends BaseMapper<Brand> {
}
@Mapper
是mybatis
框架中的注解,并不是Spring框架中的注解,那么Spring Boot
集成mybatis
之后是怎么做到自动扫描@Mapper
进行注入的呢?这时候我们就要关注到mybatis-plus
的stater的自动配置类MybatisPlusAutoConfiguration
的内部类
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
// 当前bean工厂容器
private BeanFactory beanFactory;
public AutoConfiguredMapperScannerRegistrar() {
}
// 注册beanDefinition
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 判断是否注册了自动配置包
if (!AutoConfigurationPackages.has(this.beanFactory)) {
MybatisPlusAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
} else {
MybatisPlusAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");
// 获取到之前注册的所有自动配置包路径
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (MybatisPlusAutoConfiguration.logger.isDebugEnabled()) {
packages.forEach((pkg) -> {
MybatisPlusAutoConfiguration.logger.debug("Using auto-configuration base package '{}'", pkg);
});
}
// 构建beanDefinition
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
// 指定扫描主机@Mapper
builder.addPropertyValue("annotationClass", Mapper.class);
// 指定扫描的包路径,也就是前面注册的自动配置包路径
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Stream.of(beanWrapper.getPropertyDescriptors()).filter((x) -> {
return x.getName().equals("lazyInitialization");
}).findAny().ifPresent((x) -> {
builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
});
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
}
可以看出:@AutoConfigurationPackage
和@ComponentScan
一样,都是将Spring Boot
启动类所在的包及其子包里面的组件扫描到IOC容器中,但是区别是@AutoConfigurationPackage
扫描@Enitity、@Mapper
等第三方依赖的注解,@ComponentScan
只扫描@Controller/@Service/@Component/@Repository
这些常见注解。所以这两个注解扫描的对象是不一样的。当然这只是直观上的区别,更深层次说,@AutoConfigurationPackage是自动配置的体现,是Spring Boot中注解,而@ComponentScan是Spring的注解
2.2.2 @Import(AutoConfigurationImportSelector.class)
这个注解配置是Spring Boot
的自动装配核心所在,需重点关注AutoConfigurationImportSelector
,定义如下:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
}
可以看到AutoConfigurationImportSelector
除了实现一系列的aware
接口获取相关信息之外,就是实现了DeferredImportSelector
接口,DeferredImportSelector
是ImportSelector
的子接口,Deferred
是延迟的意思。
根据之前我们总结的 @Import的使用和实现原理 可知,对@Import
的解析会来到ConfigurationClassParser
的#processImports()
,方法代码片段如下:
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
这里会判断当前selector
是DeferredImportSelector
还是ImportSelector
,如果是ImportSelector
,才会执行其#selectImports()
方法;如果是DeferredImportSelector
,会进入执行this.deferredImportSelectorHandler.handle()
,该方法会把DeferredImportSelector
封装成DeferredImportSelectorHolder
放入到this.deferredImportSelectors
集合中。根据DeferredImportSelector
意思来看,就是延迟注入的意思,所以他会等Spring对配置类相关其他注解进行解析完之后,才执行这里的注入逻辑,可从ConfigurationClassParser
的#parse()
方法得到验证:
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
// 等上面的解析完成之后再执行
this.deferredImportSelectorHandler.process();
}
来到DeferredImportSelectorHolder
的#process()
方法:
public void process() {
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
// 遍历调用handler的register()
deferredImports.forEach(handler::register);
// 遍历完之后执行processGroupImports()
handler.processGroupImports();
}
}
finally {
this.deferredImportSelectors = new ArrayList<>();
}
}
遍历deferredImportSelectors
集合,每个都会调用handler的#register()
方法,这里将AutoConfigurationImportSelector
的内部类AutoConfigurationGroup
添加到groupings
集合当中,并将对应的配置类添加到configurationClasses
当中。遍历完deferredImportSelectors
之后,调用handler.processGroupImports()
public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
Predicate<String> exclusionFilter = grouping.getCandidateFilter();
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
try {
processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
exclusionFilter, false);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configurationClass.getMetadata().getClassName() + "]", ex);
}
});
}
}
遍历之前放在groupings
中的DeferredImportSelectorGrouping
对象,调用#getImports()
方法,该方法返回的是延迟注入的类名封装成的Entry结点的迭代器对象。
public Iterable<Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
return this.group.selectImports();
}
这里的this.group
就是AutoConfigurationImportSelector
的内部类AutoConfigurationGroup
,遍历延迟注入类,调用#process()
方法处理,该方法得到自动配置结点,将其添加到autoConfigurationEntries集合当中。再遍历自动配置结点的所有配置类的类名,添加到entries集合当中。
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
// 获取自动配置类entry
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
// 放入到autoConfigurationEntries集合中
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
#getAutoConfigurationEntry()
方法如下:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
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);
// 封装成AutoConfigurationEntry返回
return new AutoConfigurationEntry(configurations, exclusions);
}
#getCandidateConfigurations(annotationMetadata, attributes)
方法如下:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
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;
}
SpringFactoriesLoader.loadFactoryNames()
会调用#loadSpringFactories()
方法:
这里就是加载META-INF/spring.factories
目录下的自动配置类,使用的是Java提供SPI(Service Provider Interface)扩展机制,不清楚该机制原理的可以看看之前总结的 SPI机制原理和使用,例如mybatis-plus
的start的自动配置如下:
拿到所有自动配置类之后回到上面的#processGroupImports()
,grouping.getImports()
获取到所有需要自动装配的类封装对象,接下来会进行一一遍历,调用#processImports()
进行注入,至此Spring Boot
就完成了自动装配。
3.自定义封装实现一个starter
首先需要新增一个maven项目,按照Spring Boot
官方建议命名格式为xxx-spring-boot-starter
, 当然不遵从也是可以,我就没有遵从,这里我例举上面项目推荐中的基于mybatis-plus
进行二次封装的一个框架starter:plasticene-spring-boot-starter-mybatis
,实现了分页插件,多租户插件集成,实体类公共字段的自动填充、复杂字段的类型处理,数据加密,以及条件构造流式查询等等功能封装。代码路径:https://github.com/plasticene/plasticene-boot-starter-parent/tree/main/plasticene-boot-starter-mybatis。结构示意图如下:
starter项目大概分两个包路径:autoconfigure
存放自动配置类,core
存放核心逻辑的封装,然后在resources
建目录META-INF
,写入配置文件spring.factories
即可:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.plasticene.boot.mybatis.autoconfigure.PlasticeneMybatisAutoConfiguration
综上一个starter就封装好了,接下来我们只需要使用mvn install
命令生成pom依赖文件进行发布,其他项目就可以引用了,如果是本地调试,不需要发布,因为install
之后本地就有这个依赖包了。
4.总结
以上全部就是对Spring Boot
自动配置原理的分析与讲解,其主要借助于@Import
注解和SPI机制
进行实现,搞清原理之后我们也手动封装了一个starter进行原理的理解与验证,完美诠释前面所述的实现原理。同时这也是面试高频考点,所以我们的花点心思搞懂它。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。