漫游Spring Boot(一):@SpringBootApplication

670 阅读7分钟

用过SpringBoot的同学对下面的代码都不会陌生,这段代码十分简洁,为了学习Spring Boot,我们可以先从 这段代码的两个核心部分开始,也就是:

  • 一个注解 @SpringBootApplication
  • 一个方法SpringApplication.run()

本文主要带大家来看看这“一个注解 @SpringBootApplication”,至于“一个方法SpringApplication.run()”,等有空的时候再来补充一下。

/**
 *  @SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用
 */
@SpringBootApplication
public class HelloWorldMainApplication {

    public static void main(String[] args) {

        // Spring应用启动起来
        SpringApplication.run(HelloWorldMainApplication.class,args);
    }
}

1 开局一注解

@SpringBootApplication 是一个组合注解,它标注在某个类上,则说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用。

好奇心促使我们“点”进去看看:

@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 {

在这里,我们发现里面还有三个注解:

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

闲话少说,让我们一个一个来看。

2 全靠三兄弟

2.1 老大@SpringBootConfiguration

我们先来看看老大@SpringBootConfiguration长什么样的:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

不难看出,@SpringBootConfiguration只有一个@Configuration,相当于这个老大其实就是一个@Configuration,表示当前类就是一个需要注册到IOC容器的组件。

2.2 老二@EnableAutoConfiguration

接下来再看看老二@EnableAutoConfiguration,从注解的字面意思上来理解,这个注解应该就和自动配置相关了。

带上了这个配置,就是告诉SpringBoot开启自动配置功能;这样自动配置才能生效。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage   //2.2.1
@Import(AutoConfigurationImportSelector.class)  //2.2.2
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 */
	Class<?>[] exclude() default {};
	/**
	 * Exclude specific auto-configuration class names such that they will never be applied.
	 */
	String[] excludeName() default {};

}

既然是和自动配置相关的注解,内容肯定不少,果然,这个老二还有两个“跟班”:

  • @AutoConfigurationPackage
  • @Import(AutoConfigurationImportSelector.class)

2.2.1 @AutoConfigurationPackage

老二的第一个跟班是一个注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

在这个注解里面,又发现了一个 @Import(AutoConfigurationPackages.Registrar.class),@Import的作用是往IOC容器中添加一个组件,而这里添加的是一个子类Registar,那我们就来看看这个子类到底是什么东西:

package org.springframework.boot.autoconfigure;
public abstract class AutoConfigurationPackages {
    
    private static final String BEAN = AutoConfigurationPackages.class.getName();
    
    /**
     * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing configuration.
     */
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    	@Override
    	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    		//通过metadata获取[主程序类]的包名
    		register(registry, new PackageImport(metadata).getPackageName());
    	}
    	@Override
    	public Set<Object> determineImports(AnnotationMetadata metadata) {
    		return Collections.singleton(new PackageImport(metadata));
    	}
    }
    
    //将[主程序类]所在包及所有子包下的组件到扫描到spring容器中
    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);
		}
	}
}

结合代码,我们可以得出结论:@AutoConfigurationPackage注解,通过@Import(AutoConfigurationPackages.Registrar.class)将主程序类所在包及所有子包下的组件到扫描到spring容器中!

2.2.2 @Import(AutoConfigurationImportSelector.class)

老二的第二个跟班,就是一个@Import,这个@Import向IOC容器中导入了AutoConfigurationImportSelector组件,而这个组件是自动配置的核心中的核心,它决定了到底需要为SpringBoot程序导入多少组件!下面的源码非常长,初次阅读可以先记住下面的结论,全篇看完后再回过头来细读:

结论:@AutoConfigurationPackage注解,通过@Import(AutoConfigurationImportSelector.class)告诉SpringBoot,需要自动导入的组件有哪些!

selectImports源码

AutoConfigurationImportSelector的核心方法是public String[] selectImports(AnnotationMetadata annotationMetadata)。

这个方法返回的是一个字符串数组,数组的每个元素实际是一个由SpringBoot默认实现的一个个配置类的全类名,在SpringBoot启动的时候会根据这个方法返回的数组来加载相应的配置类!

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {//判断是否开启自动装配(找出spring.boot.enableautoconfiguration参数是否为true)
			return NO_IMPORTS; //空数组
		}
		//获取所有的自动配置元信息(mataData)(内部维护了Properties),详见源码1
		//实际上项目下所有【spring-autoconfigure-metadata.properties】配置文件的key-value值
		AutoConfigurationMetadata autoConfigurationMetadata = 
		        AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
		
		//将注解元信息封装成注解属性对象(底层是个LinkedHashMap)
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		
		//获取SpringBoot已写好的自动配置组件
		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 StringUtils.toStringArray(configurations);
	}
	
	protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
		String name = getAnnotationClass().getName();   //这里实际就是EnableAutoConfiguration的全类名
		//AnnotationAttributes是LinkedHashMap<String, Object>的子类
		//这里就是将EnableAutoConfiguration的参数转成Map返回
		AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
		Assert.notNull(attributes,
				() -> "No auto-configuration attributes found. Is "
						+ metadata.getClassName() + " annotated with "
						+ ClassUtils.getShortName(name) + "?");
		return attributes;
	}
	
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,AnnotationAttributes attributes) {
		//加载所有的SpringBoot已经写好的自动配置类,详见源码2
		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;
	}
	
}
源码1:selectImports()中的loadMetadata

final class AutoConfigurationMetadataLoader {

protected static final String PATH = "META-INF/"+"spring-autoconfigure-metadata.properties";
    
    public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		return loadMetadata(classLoader, PATH);
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
		try {
            //获取项目下所有【META-INF/spring-autoconfigure-metadata.properties】的路径资源
			Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(path)
					: ClassLoader.getSystemResources(path));
			
			//提取所有同名资源文件中的所有key-value数据
			Properties properties = new Properties();
			while (urls.hasMoreElements()) {
				properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
			}
			//最后将【Properties】封装成【AutoConfigurationMetadata】返回
			return loadMetadata(properties);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException(
					"Unable to load @ConditionalOnClass location [" + path + "]", ex);
		}
	}

源码2:getCandidateConfigurations()的loadFactoryNames()
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
	}
	
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
		    //获取项目下所有【META-INF/spring.factories】的路径资源
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			
			//加载这些资源文件的所有key-value参数到MultiValueMap中并返回
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					List<String> factoryClassNames = Arrays.asList(
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
					result.addAll((String) entry.getKey(), factoryClassNames);
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

2.3 老三 @ComponentScan

这个老三就不是SpringBoot特有的新注解了,我们在Spring也经常会用到,主要是起到扫描并将扫描到的组件加入到IOC容器中,从这个@ComponentScan参数中可以看出,这里是【excludeFilters】,意味着,这整个注解就是为了去掉多余的组件。

@ComponentScan(excludeFilters = {
      @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

我们再看,使用到了两个@Filter,类型都是CUSTOM(表示自定义),那意味着这个注解的核心就是这两个@Filter的classes了。

换句话说:这两个classes就是SpringBoot为了排除掉一些多余的组件而自定义的组件过滤器

2.3.1 TypeExcludeFilter

下面的代码主要是从beanFactory中获取所有的TypeExcludeFilter,并执行他们的match方法,只要匹配上了就返回true。

这个过滤器其实可以理解成一个口子,在程序启动时,SpringBoot会加载Spring Bean池中所有针对TypeExcludeFilter的扩展,并遍历调用他们的match方法。 所以只要继承TypeExcludeFilter并重写match方法,都将会在该方法被调用,从而达到对客户代码的扩展

需要注意的是,因为这个过滤器在Springboot中的使用方式为:excludeFilters ,意味着,返会true的组件是要被排除的组件!

@Override
	public boolean match(MetadataReader metadataReader,
			MetadataReaderFactory metadataReaderFactory) throws IOException {
		if (this.beanFactory instanceof ListableBeanFactory
				&& getClass() == TypeExcludeFilter.class) {
			//
			Collection<TypeExcludeFilter> delegates = ((ListableBeanFactory) this.beanFactory)
					.getBeansOfType(TypeExcludeFilter.class).values();
			for (TypeExcludeFilter delegate : delegates) {
				if (delegate.match(metadataReader, metadataReaderFactory)) {
					return true;
				}
			}
		}
		return false;
	}

2.3.1 AutoConfigurationExcludeFilter

这个Filter主要是过滤掉【没有开启某配置的相应注解】或者【没有发现某配置必须的属性】的组件。

@Override
	public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
			throws IOException {
			//判断【是否为配置类】+【是否满足自动配置】
		return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
	}

    //判断是否为配置类:判断当前组件是否带有@Configuration这个注解
	private boolean isConfiguration(MetadataReader metadataReader) {
		return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());
	}
    //判断是否满足自动配置:判断当前组件是否是autoConfigurations中的一员
	private boolean isAutoConfiguration(MetadataReader metadataReader) {
		return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
	}
    
    //获取autoConfigurations,即MATE/INF下的
	protected List<String> getAutoConfigurations() {
		if (this.autoConfigurations == null) {
			//这行代码就是去读取MATE/INF下的所有配置类全类名
			this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,
					this.beanClassLoader);
		}
		return this.autoConfigurations;
	}

3 小结

  • @SpringBootApplication
    • 1 @SpringBootConfiguration:表明为注解类
    • 2 @EnableAutoConfiguration:自动装配核心
      • 2.1 @AutoConfigurationPackage
        • @Import(AutoConfigurationPackages.Registrar.class):导入启动类包下的所有组件
      • 2.2 @Import(AutoConfigurationImportSelector.class):导入符合自动装配组件的所有组件
    • 3 @ComponentScan:排除对象
      • 3.1 TypeExcludeFilter 继承TypeExcludeFilter的子类在此执行其match方法
      • 3.2 AutoConfigurationExcludeFilter 过滤是否为配置类且带有Auto配置的注解