前一节启动了一个简单的SpringBoot Web项目,并分析了pom文件,从这节开始,我们将从DemoApplication应用启动类入手,剥丝抽茧地分析、理解目之所及的每个细节。首先映入眼帘的就是今天的主角——SpringBootApplication注解。
1 @SpringBootApplication
不用怕它,先点进去,原来是个组合注解,包含了SpringBootConfiguration、EnableAutoConfiguration、ComponentScan三个注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
1.1 @SpringBootConfiguration
1.1.1 注释解读
* Indicates that a class provides Spring Boot application
* {@link Configuration @Configuration}. Can be used as an alternative to the Spring's
* standard {@code @Configuration} annotation so that configuration can be found
* automatically (for example in tests).
* <p>
* Application should only ever include <em>one</em> {@code @SpringBootConfiguration} and
* most idiomatic Spring Boot applications will inherit it from
* {@code @SpringBootApplication}.
用我的CET-6给大家翻译下,两点:
- 是@Configuration的替代品
- 一般通过@SpringBootApplication注解使用
1.1.2 一个疑问
但是在第一点中有这么半句“so that configuration can be found automatically(for example in tests)”,不禁想问问被什么自动发现,是“tests”吗,这里tests是什么?我们尝试一下引入spring-boot-test
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
果然在org.springframework.boot.test.context.SpringBootTestContextBootstrapper#getOrFindConfigurationClasses中找到了“自动发现”@SpringBootConfiguration的地方。因此这就是@SpringBootConfiguration和@Configuration的唯一区别之处了——被框架自动发现。
protected Class<?>[] getOrFindConfigurationClasses(MergedContextConfiguration mergedConfig) {
Class<?>[] classes = mergedConfig.getClasses();
if (containsNonTestComponent(classes) || mergedConfig.hasLocations()) {
return classes;
}
Class<?> found = new AnnotatedClassFinder(SpringBootConfiguration.class)
.findFromClass(mergedConfig.getTestClass());
Assert.state(found != null, "Unable to find a @SpringBootConfiguration, you need to use "
+ "@ContextConfiguration or @SpringBootTest(classes=...) with your test");
logger.info("Found @SpringBootConfiguration " + found.getName() + " for test " + mergedConfig.getTestClass());
return merge(found, classes);
}
1.2 @EnableAutoConfiguration
1.2.1 使用示例
为了讲清楚这个注解的意义,我们从一个例子开始,首先我另外建了一个名称为test的SpringBoot工程,test中主要是创建了一个TestBeanOne bean、一个TestAutoConfiguration 配置类
package com.letterh.test.one;
import org.springframework.stereotype.Component;
@Component
public class TestBeanOne {
}
package com.letterh.test;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.letterh.test.one")
public class TestAutoConfiguration {
}
运行test,自然可以正常地读取到TestBeanOne这个bean,这里不再赘述。现在,我们回到demo工程中,在pom中依赖引入test,并在main方法中尝试获取TestBeanOne实例
<dependency>
<groupId>com.letterh</groupId>
<artifactId>test</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class);
TestBeanOne testBeanOne = context.getBean(TestBeanOne.class);
System.out.println(testBeanOne);
}
}
运行结果报了找不到bean的错误
再次回到test工程,在spring.factories文件中增加配置,并重新打包上传
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.letterh.test.TestAutoConfiguration
再次运行demo工程,发现这次可以正常获取到test工程中的TestBeanOne实例了
总结一下:@EnableAutoConfiguration的作用是,从classpath中搜索所有META-INF/spring.factories配置文件,然后将其中org.springframework.boot.autoconfigure.EnableAutoConfiguration key对应的配置项加载到spring容器。
1.2.2 源码解读
分析@EnableAutoConfiguration的源码,一是组合了@AutoConfigurationPackage,二是导入了AutoConfigurationImportSelector实例。
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
1.2.2.1 @AutoConfigurationPackage
跟读源码,首先是导入了AutoConfigurationPackages.Registrar类
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
然后在Registrar中,调用了register方法,方法参数packageNames来自于PackageImports的构造方法中生成
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
private static final class PackageImports {
private final List<String> packageNames;
PackageImports(AnnotationMetadata metadata) {
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
//先取AutoConfigurationPackage注解配置的basePackages或basePackageClasses
List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));
for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
packageNames.add(basePackageClass.getPackage().getName());
}
//如果没有basePackages或basePackageClasses,则默认取被注解的当前类的package
if (packageNames.isEmpty()) {
packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
}
this.packageNames = Collections.unmodifiableList(packageNames);
}
继续跟进register方法,方法中将packageNames包装成了BasePackagesBeanDefinition(packageNames放入basePackages集合中),然后以AutoConfigurationPackages的类名为key,把BasePackagesBeanDefinition放入了DefaultListableBeanFactory的beanDefinitionMap中
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
beanDefinition.addBasePackages(packageNames);
}
else {
registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
}
}
总结一下:通过@AutoConfigurationPackage注解,将其中配置的包名(默认为当前类的包名),放入了一个BeanDefinition对象的basePackages属性中,该BeanDefinition对应的beanName是AutoConfigurationPackage的类名。
看到这里,感觉已经懂了AutoConfigurationPackage,但是又好像还没懂。因为我发现还是不知道这个AutoConfigurationPackage究竟起了什么作用,所以我们再来仔细看下这个类
好像很简单就三个方法,其中register还是已经分析过的,所以只需要看一下get和has方法。
/**
* Determine if the auto-configuration base packages for the given bean factory are
* available.
* @param beanFactory the source bean factory
* @return true if there are auto-config packages available
*/
public static boolean has(BeanFactory beanFactory) {
return beanFactory.containsBean(BEAN) && !get(beanFactory).isEmpty();
}
/**
* Return the auto-configuration base packages for the given bean factory.
* @param beanFactory the source bean factory
* @return a list of auto-configuration packages
* @throws IllegalStateException if auto-configuration is not enabled
*/
public static List<String> get(BeanFactory beanFactory) {
try {
return beanFactory.getBean(BEAN, BasePackages.class).get();
}
catch (NoSuchBeanDefinitionException ex) {
throw new IllegalStateException("Unable to retrieve @EnableAutoConfiguration base packages");
}
}
因为方法比较简单,所以直接说结论:has是判断basePackages是否存在且不为空,get是获取basePackages。终于恍然大悟,原来AutoConfigurationPackage的意义就在于对外提供这个basePackages。比如在SpringBoot Mybatis中,就调用了get方法获取basePackages,具体调用位置参考:org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar#registerBeanDefinitions
1.2.2.2 AutoConfigurationImportSelector
* {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration
* auto-configuration}. This class can also be subclassed if a custom variant of
* {@link EnableAutoConfiguration @EnableAutoConfiguration} is needed.
翻译一下:这个类是为了处理EnableAutoConfiguration的自动导入
看一下它实现的selectImports方法
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//判断是否配置了spring.boot.enableautoconfiguration为true
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;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//获取自动配置类们
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//过滤掉重复的
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
//过滤掉被排除的,这里就是@EnableAutoConfiguration中配置的exclude或excludeName的过滤逻辑
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//spring.factories中配置的自动配置类
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
//有@AutoConfiguration注解的自动配置类
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;
}
所以SpringBoot就是通过AutoConfigurationImportSelector来管理自动配置类的导入的,自动配置类可以在spring.factories中配置,也可以使用@AutoConfiguration注解。
1.3 @ComponentScan
指定了两个扫描过滤器:TypeExcludeFilter和AutoConfigurationExcludeFilter
1.3.1 TypeExcludeFilter
一句话,开发者可以继承TypeExcludeFilter,实现自定义的扫描过滤逻辑。
但是使用时需要注意,如果通过普通的方式(如@Component)注册TypeExcludeFilter的实现类bean就太晚了,实际不起作用,具体怎么注入暂时不做展开。
读源码过程中,发现TypeExcludeFilter还有个比较有意思的地方,先看代码:
@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;
}
match方法中获取了所有类型为TypeExcludeFilter的bean实例,然后逐个执行match方法。乍一看,这很容易产生死循环啊,实则不然:一是TypeExcludeFilter是没有注册为bean的,所以getDelegates()获取不到自身;二是进循环前判断了当前class类型,确保只有当前这个TypeExcludeFilter才能执行循环逻辑,继承者们执行不到。
1.3.2 @AutoConfigurationExcludeFilter
因为这是个Filter,所以直接从match方法开始读起
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
//排除掉这种类:有@Configuration注解,且是AutoConfiguration类(spring.factories中配置了EnableAutoConfiguration,或有@AutoConfiguration注解)
return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
}
代码看着很简单,排除掉定义在spring.factories中的EnableAutoConfiguration key下的配置类们,但是自然会有一个疑问:为啥要排除?如果排除了,岂不是加载不到了吗?不过这个问题就不用再解答了吧(参见本节1.2.2.2)