运行前准备——分析SpringBootApplication注解

280 阅读5分钟

前一节启动了一个简单的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)