SpringBoot
自动配置原理解析
SpringBoot是基于Spring的框架,SpringBoot尝试通过添加
the jar dependencies
的方式来自动化的为我们配置我们需要的各种组件,要想使用SpringBoot的自动配置,就必须添加@EnableAutoConfiguration 或者 @SpringBootApplication
注解到@Configuration
修饰的类上面
我们通常情况下构建的SpringBoot的主配置类都是如下形式:
@SpringBootApplication //自动配置的操作都在里面啦
public class MainApplication2 {
public static void main(String[] args) {
SpringApplication.run(MainApplication2.class);
}
}
要说自动配置的原理,当然是来到@SpringBootApplication
注解
注解主要有以下三个注解合成:
@SpringBootConfiguration //声明当前类是一个配置类
@EnableAutoConfiguration //做两件事:1.确定包扫描路径 2.加载自动配置类
@ComponentScan(...) //把指定路径下的组件加入容器
@SpringBootConfiguration
里面主要是有@Configuration
注解
@Configuration //作用是声明配置类
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
@ComponentScan
注解的作用是把指定路径下的组件扫描进容器里面 :
SpringBoot 里面的这个注解使用如下文:主要是为了进行一些组件的过滤工作
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
我们可以使用下面三个注解来代替@SpringBootApplication
注解的作用
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.llm")
public class MainApplication2 {
public static void main(String[] args) {
SpringApplication.run(MainApplication2.class);
}
}
上面的三个注解等价于:
@SpringBootApplication(scanBasePackages = {"com.llm"}) //scanBasePackages定义扫描的基础包
关于这个scanBasePackages
属性,可以在@SpringBootApplication
找到它的定义:
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
可以看到这个属性等价于@ComponentScan
注解的basePackages
属性:
也就是说通过这个属性可以实现指定文件路径的扫描
最后的一个当然就是自动配置的核心:
@EnableAutoConfiguration
注解,使用这个注解完成自动配置的骚操作
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {}; //定义需要排除的类的Class
String[] excludeName() default {};
}
@AutoConfigurationPackage注解分析
里面主要有两个注解,首先是@AutoConfigurationPackage
,这个注解主要来定义当前类为主配置类(就是SpringBootApplication
修饰的类)并且把当前主配置类所属的包定义为扫描的基础包
首先来到这个注解里面,可以看到主要是导入了一个组件
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {}; //可以在`SpringBootApplication`注解里面定义basePackages属性,就是从这里来的
Class<?>[] basePackageClasses() default {};
}
除了这个注解里面的属性可以用来定义扫描基础包外,注解类上面还有一个@Import({Registrar.class})
来进行修饰
这个注解的作用是往容器中导入了一个类为Registrar
的组件,关于@Import()
注解的作用,读者可以自行百度
然后我们来看看这个Registrar
类里面到底有何乾坤:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
//这个方法在Registrar被注入容器的时候被调用,主要完成基础包的导入
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
可以看到这个类继承了ImportBeanDefinitionRegistrar
类,熟悉Spring的Bean生命周期的同学都应该知道,在容器初始化BeanDefination
的时候会调用registerBeanDefinitions()
方法,具体细节,读者可以自己百度
在上面的registerBeanDefinitions(...)
方法上面打一个断点,观察里面的运行情况:
里面主要有下面这一行代码:
AutoConfigurationPackages.register(registry, (String[])(newAutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
-
AutoConfigurationPackages.register(...)
主要完成指定包下类向容器中的注册 -
主要和核心处理逻辑在
newAutoConfigurationPackages.PackageImports(metadata)).getPackageNames()
中PackageImports是AutoConfigurationPackages
的子类:-
首先来到
PackageImports
类中:private static final class PackageImports { //存储需要扫描的包的名字 private final List<String> packageNames; PackageImports(AnnotationMetadata metadata) { //首先从@AutoConfigurationPackage注解中的两个属性中找找是否定义basePackages和basePackageClasses的属性是否已经定义 AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false)); List<String> packageNames = new ArrayList(); String[] var4 = attributes.getStringArray("basePackages"); int var5 = var4.length; int var6; for(var6 = 0; var6 < var5; ++var6) { String basePackage = var4[var6]; packageNames.add(basePackage); } Class[] var8 = attributes.getClassArray("basePackageClasses"); var5 = var8.length; for(var6 = 0; var6 < var5; ++var6) { Class<?> basePackageClass = var8[var6]; packageNames.add(basePackageClass.getPackage().getName()); } //如果注解里面的属性没有定义,就会进入到这个判断方法中,然后调用元注解里,得到主配置类下面的包名字 if (packageNames.isEmpty()) { packageNames.add(ClassUtils.getPackageName(metadata.getClassName())); } this.packageNames = Collections.unmodifiableList(packageNames); } List<String> getPackageNames() { return this.packageNames; } //.... }
-
可以看到在这个类的构造方法中会完成属性packageNames
的配置,如果没有在@SpringBootApplication
中配置basePackages或者basePackageClasses的属性
的话就会默认注入当前主配置类所在的包
- 这个过程就完成了我们经常熟知的`SpringBoot`的属性:<kbd>默认扫描当前主配置类所属的包下面的所有的类</kbd>
@Import({AutoConfigurationImportSelector.class})注解
上面就完成了@AutoConfigurationPackage
注解的分析,下面就是另外一个重要的注解,
@Import({AutoConfigurationImportSelector.class})
粉墨登场啦!!!
可以看到,这也是通过@Import
注解来向容器中注入AutoConfigurationImportSelector
组件:
当然,我们需要进入到AutoConfigurationImportSelector
类里面一探究竟:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
public AutoConfigurationImportSelector() {
}
//这个方法用来导入指定地址的自动配置类
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//getCandidateConfigurations(...)方法会从META-INF/spring.factories中加载所有的配置类
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//移除掉重复的配置类,后面还有其他类似的操作,可按相同方法理解
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
//...
打断点,来到List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
里面
这个方法里面的一行关键代码就是:
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
这个loadFactoryNames
方法调用了loadSpringFactories
方法,在这个方法里面就有我们常见的文件位置啦!!!
懂SpringBoot的朋友想必都不会对
META-INF/spring.factories
文件陌生,这个文件里面写好了有关我们开发所有的配置类的名字,在这些类的名字通常都以***AutoConfiguration
结尾
写到这里,关于整个SpringBoot的大致配置流程,就走的差不多啦,在下实力有限,
肯定会有疏漏之处,若是前辈看到,若能指点一二,感激不尽。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/202486.html