众所周知我们要想启动一个SpringBoot项目,至少需要像下面的方式启动 而这里就在启动类上标记了
@SpringBootApplication
1. @SpringBootApplication全貌
上来我们先看此注解的样子
java
...
@SpringBootConfiguration
@EnableAutoConfiguration(exclude = AssertionError.class)
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@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 {};
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
Component aa() default @Component;
}
我们可以发现这个注解上带有了三个重要的注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
这三个注解其实才是@SpringBootApplication的本体,接下来我们一一介绍
2. @SpringBootConfiguration
这个注解的很简单,本质上就是一个翻版的@Configuration
java
...
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
但是这个注解又必须出现在@SpringBootApplication
中,因为在ConfigurationClassPostProcessor
中会执行下面这样代码
而这个方法中就会要求必须携带@Configuration
3. @ComponentScan
@ComponentScan :在默认的情况下扫描和启动类包路径一样的class文件,并检查是否带有@Component
或@ManagedBean
, 一旦带有那么就注册到容器中
java
public @interface ComponentScan {
/**
* 包扫描的路径
*/
@AliasFor("basePackages")
String[] value() default {};
/**
* 包扫描的路径
*/
@AliasFor("value")
String[] basePackages() default {};
/**
* 从指定的Class的包路径开始扫描
*/
Class<?>[] basePackageClasses() default {};
/**
* bean名称生产器
*/
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
/**
* 范围解析器:检查bean是否有@Scope注解注解的解析器
*/
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
/**
* 如果bean是多例,以什么方式创建
*/
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
/**
* 是一个关于文件类型的过滤,默认是 * * / *.class
*/
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
/**
* 是否使用默认的filter扫描类,默认可以扫描@Component@Repository、@Service或@Controller注解的类。
*/
boolean useDefaultFilters() default true;
/**
* 指定符合扫描条件:为了进一步缩小候选bean的范围
*/
Filter[] includeFilters() default {};
/**
* 指定不符合组件扫描条件
*/
Filter[] excludeFilters() default {};
/**
* 注册的bean是否延迟初始化。
*/
boolean lazyInit() default false;
...
}
此时让我们将目光回到@SpringBootApplication
中的@ComponentScan
,我们会发现他携带了两个Filter
- TypeExcludeFilter
- AutoConfigurationExcludeFilter
要想了解这两个类得先理解他们实现的接口 TypeFilter
java
@FunctionalInterface
public interface TypeFilter {
boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException;
}
分析这个类的类名和方法就能得出:当我们不需要某些Bean注册到容器中的时候,可以指定此过滤器,然后就可以根据类型进行过滤
2.1 TypeExcludeFilter
接下来我们直接看这个类的源码
java
public class TypeExcludeFilter implements TypeFilter, BeanFactoryAware {
...
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
if (this.beanFactory instanceof ListableBeanFactory && getClass() == TypeExcludeFilter.class) {
for (TypeExcludeFilter delegate : getDelegates()) {
if (delegate.match(metadataReader, metadataReaderFactory)) {
return true;
}
}
}
return false;
}
private Collection<TypeExcludeFilter> getDelegates() {
Collection<TypeExcludeFilter> delegates = this.delegates;
if (delegates == null) {
delegates = ((ListableBeanFactory) this.beanFactory).getBeansOfType(TypeExcludeFilter.class).values();
this.delegates = delegates;
}
return delegates;
}
}
我们可以发现这个类是一个带有委托机制的类,他的getDelegates()方法用于获取到容器中类型为TypeExcludeFilter的类,再去处理
2.2 AutoConfigurationExcludeFilter
上来我们先看此类的源码
java
public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {
private ClassLoader beanClassLoader;
//自动配置类集合
private volatile List<String> autoConfigurations;
@Override
public void setBeanClassLoader(ClassLoader beanClassLoader) {
this.beanClassLoader = beanClassLoader;
}
//如果是配置类而且是自动配置类就返回true
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
}
private boolean isConfiguration(MetadataReader metadataReader) {
return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());
}
private boolean isAutoConfiguration(MetadataReader metadataReader) {
return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
}
/**
* 实际上是获取的所有的自动配置类
* @return
*/
protected List<String> getAutoConfigurations() {
if (this.autoConfigurations == null) {
this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,
this.beanClassLoader);
}
return this.autoConfigurations;
}
}
分析源码我们可以得知这是一个排除自动配置类的TypeFilter
- 实际上自动配置类都在
org.springframework.boot.autoconfigure
路径下,默认情况下@ComponentScan是以启动类作为默认路径进行扫描,所以说这个TypeFilter
条件一直为false - 并且自动配置类实际上是由于
@EnableAutoConfiguration
导入的AutoConfigurationImportSelector
负责注册的
4. @EnableAutoConfiguration
@EnableAutoConfiguration
是SpringBoot中开启自动配置类的入口注解
java
...
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* 排除某些自动配置类,传入的是Class对象
*/
Class<?>[] exclude() default {};
/**
* 排除某些自动配置类,传入的是类名
*/
String[] excludeName() default {};
}
分析源码我可以得知这个注解重点就是引入了下面这两个东西
@AutoConfigurationPackage
AutoConfigurationImportSelector
5. @AutoConfigurationPackage
我们看@AutoConfigurationPackage
发现他导入了一个Registrar
java
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
然后我们再看Registrar
,发现他实现了两个接口ImportBeanDefinitionRegistrar
和DeterminableImports
java
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
5.1 @ImportBeanDefinitionRegistrar
我们先来介绍下ImportBeanDefinitionRegistrar
接口,这个类的作用我们看名字就知道,这是一个注册BeanDefinition
的类,这个类会在ConfigurationClass
转换为BeanDefinition
的阶段被调用
java
public interface ImportBeanDefinitionRegistrar {
/**
* @param importingClassMetadata 导入类的注解元数据
* @param registry 当前Bean的注册中心,默认为 {@link org.springframework.beans.factory.support.DefaultListableBeanFactory DefaultListableBeanFactory}
* @param importBeanNameGenerator Bean名称生成器策略
*/
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
...
}
知道了Registrar
的执行逻辑,我们再看其register方法干了什么
分析其代码我们可以知道这是一个将启动类的包路径包装为BasePackages
类,并注册到容器中 ,以供后续使用
java
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);
}
}
6. AutoConfigurationMetadataLoader
@AutoConfigurationImportSelector
是实际上负责注册自动配置类的类
在讲这个Selector之前我们需要先了解两个类,其中第一个是负责从META-INF/spring.factories
中加载自动配置类名称的SpringFactoriesLoader
SpringFactoriesLoader
AutoConfigurationMetadataLoader
至于另外一个,大家想想一个场景,当spring.factories中有SpringSecurity的自动配置类,但是我们的项目中并没有导入SpringSecurity的jar包,那这个自动配置类应该工作吗,或者说A自动配置类可能要求在B自动配置类以前初始化又怎么办?
在这种场景下AutoConfigurationMetadataLoader
应运而生,是负责从META-INF/spring-autoconfigure-metadata.properties
中加载自动配置类规则
,
那我们再看看这个properties的庐山真面目,刚好在spring-boot-actuator-autoconfigure中有这个文件
这个文件比较长,我总结了它的规则如下:A和B都是自动配置类的包路径+名称
- A.
ConditionalOnClass
=类名:A起作用需能够加载某个类 - A.
ConditionalOnBean
=Bean名称:A起作用需要容器中某个Bean - A.
ConditionalOnWebApplication
=SERVLET:A起作用要求当前环境为Servlet - A.
AutoConfigureAfter
=B:A的初始化需要在B之后 - A.
AutoConfigureBefore
=B:A的初始化需要在B之前
至此我们知道了spring.factories
和spring-autoconfigure-metadata.properties
的作用,但是这两个东西最终还是靠AutoConfigurationImportSelector
工作的
7. AutoConfigurationImportSelector
@AutoConfigurationImportSelector
是实际上负责注册自动配置类的类,我们先看下这个类的结构
虽然这个类实现了很多接口,但是大部分都是什么Aware,这种接口都是直接设置对应的对象,不理解也不会影响到这个类的逻辑,但是其中有一个DeferredImportSelector
, 这个接口翻译过来就是延时导入的意思,一般情况下我们Bean的初始化都是在我们下面的第一个红框中处理的,但是DeferredImportSelector
的Bean确是在我标记的第二个红框中处理的
为什么要这样设计呢,我理解是因为@ConditionalOnBean
这种注解的情况下,自动配置类的条件计算至少要等到其他Bean处理完毕,才可以处理自动配置类
在这种情况下ImportSelector的selectImports(...)方法反而不会执行,而getAutoConfigurationEntry(...)
会被执行,这个方法会返回符合规则的自动配置类
java
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
//判断是否开启了自动配置选项
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
//获得有关@EnableAutoConfiguration的属性
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 = filter(configurations, autoConfigurationMetadata);
//推送自动配置类导入事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
重要的代码我都已经写了注释,但是其中有一个filter(...)
方法需要单独讲一下
java
/**
* 获取bean工厂的AutoConfigurationImportFilter,进行过滤
* spring.factories表示要导入的自动配置类,而spring-autoconfigure-metadata.properties中是自动配置类的规则
* @param configurations 从META-INF/spring.factories中读取到的有关EnableAutoConfiguration的类
* @param autoConfigurationMetadata 从META-INF/spring-autoconfigure-metadata.properties中读取到的类
* @return
*/
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime();
//候选自动配置类名称
String[] candidates = StringUtils.toStringArray(configurations);
//不需要自动配置名称的集合,顺序和configurations是对应起来的
boolean[] skip = new boolean[candidates.length];
//是否需要跳过某些自动配置类的标志位,默认不跳过
boolean skipped = false;
//执行所有的过滤器
for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
//执行有关Aware的方法
invokeAwareMethods(filter);
//重点:进行匹配
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
//如果无法匹配
if (!match[i]) {
skip[i] = true;
candidates[i] = null;
skipped = true;
}
}
}
//完全没有需要跳过的自动配置类
if (!skipped) {
return configurations;
}
//将不需要跳过的自动配置类加入到result中
//最终需要返回的也是result
List<String> result = new ArrayList<>(candidates.length);
for (int i = 0; i < candidates.length; i++) {
if (!skip[i]) {
result.add(candidates[i]);
}
}
...
return new ArrayList<>(result);
}
filter(...)方法说白了就是通过AutoConfigurationImportFilter对于自动配置类进行过滤,返回符合规则的自动配置类,而AutoConfigurationImportFilter有三个重要的实现:
OnClassCondition
OnBeanCondition
OnWebApplicationCondition
而这里就刚好就对应了我前面讲的spring-autoconfigure-metadata.properties
中的其中一部分规则
8. 总结
@SpringBootApplication
的作用就是导入三个注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
@EnableAutoConfiguration
是负责开启并注册自动配置类,@ComponentScan
是最终在ConfigurationClassPostProcessor
中负责注册我们自定义的Bean的