SpringBoot从入门到精通—自动配置原理(深入源码)

1、引言

    不论在工作中,亦或是求职面试,Spring Boot已经成为我们必知必会的技能项。除了某些老旧的政府项目或金融项目持有观望态度外,如今的各行各业都在飞速的拥抱这个已经不是很新的Spring启动框架。

    当然,作为Spring Boot的精髓,自动配置原理的工作过程往往只有在“面试”的时候才能用得上,但是如果在工作中你能够深入的理解Spring Boot的自动配置原理,将无往不利。

    Spring Boot的出现,得益于“习惯优于配置”的理念,没有繁琐的配置、难以集成的内容(大多数流行第三方技术都被集成),这是基于Spring 4.x提供的按条件配置Bean的能力。

2、 Spring Boot自动配置原理

2.1 配置文件到底能写什么?怎么写?自动配置原理?

    我们一接触Spring Boot的时候就了解到:Spring Boot有一个全局配置文件application.properties或application.yml。我们的各种属性都可以在这个文件中进行配置,最常配置的比如:server.portlogging.level.*数据库配置Redis配置 等等,然而我们实际用到的往往只是很少的一部分,而且可以配置的属性都可以在官方文档中查找到:
https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#common-application-properties

2.1 @EnableAutoConfiguration

    Spring Boot有一个启动类,所有的SpringBoot项目必须从启动类启动(一般情况下SpringBoot启动类类名就是项目名+Application),启动类上有@SpringBootApplication注解,这个注解是Spring Boot的核心注解,标有这个注解的应用就是基于SpringBoot的应用,我们的自动配置原理和这个注解还有着千丝万缕的联系!我们可以点看进去看看。@SpeingBootApplication注解的源码如下

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited  //上面都是Java原生的注解,不清楚意思的请自行百度或Google
@SpringBootConfiguration
@EnableAutoConfiguration
@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;
}

        在@SpringBootApplication源码,这里面最重要的就是@SpringBootConfiguration@EnableAutoConfiguration,前者是Spring Boot的配置注解,底层使用的是@Configuration这个注解,后者翻译他的名字就知道这是开启自动配置,它是Spring Boot自动配置的核心。我们可以依次进入源码看看。
* @SpringConfiguration注解源码

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

        @SpringConfiguratuon注解的作用是用来告诉SpringBoot这是一个SpringBoot的配置类,从他的源码可以看出它是@Configuration注解的派生注解,对于这个注解我们应该不会陌生,这个类是Spring的核心注解之一。因此可以知道的是这两个注解的功能大致是一样的,只不过一个是专门用于SpringBoot的配置类,另一个则在Spinrg体系中是通用的。

  • @EnableAutoConfiguration注解源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
	Class<?>[] exclude() default {};
	String[] excludeName() default {};
}

        @EnableAutoConfiguration的作用开启SpringBoot自动配置,从源码中可以看到,@EnableAutoConfiguration注解内部的代码是非常的简单,但是@Import(AutoConfigurationImportSelector.class)这个注解引起了我的好奇,@Import这个注解我们都不陌生,他的作用有以下几种:
1. 作为@Configuration注解的配置类
2. 声明@Bean注解的bean方法
3. 导入ImportSelector的实现类或导入ImportBeanDefinitionRegistrar的实现类。

        按源码的写法,他是导入了ImportSelector的实现类AutoConfigurationImportSelector。对于这个类它的核心方法就是 selectImports(),它控制哪些自动配置类的实例加入到容器中,在Spring Boot 2.1.7.RELEASE版本中得源码如下:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	}
	AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
			.loadMetadata(this.beanClassLoader);
	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
			annotationMetadata);
	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

        在selectImports()方法的源码中使用了getAutoConfigurationEntry()这个方法,其中configurations存放的数据就是加入容器的自动配置类的完整包路径,继续追踪源码,进入getAutoConfigurationEntry()源码:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
		AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	
	//扫描具有META-INF/spring.factories文件的jar包
	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);
}

        从getCandidateConfigurations()方法的实现中,我们发现他调用SpringFactoriesLoader.loadFactoryNames产生了一个List,返回的东西是啥呢?继续往下看

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	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;
}

        我们继续往下看,发现他确实最终在loadFactories方法中使用本类的一个私有静态方法loadSpringFactories加载了META-INF/spring.factories这个配置文件。

public final class SpringFactoriesLoader {

	/**
	 * The location to look for factories.
	 * <p>Can be present in multiple JAR files.
	 */
	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 {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			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()) {
					String factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}
}

查看spring-boot-autoconfigure包下META-INF/spring.factories:

......
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
......

看到的非常多的xxxxAutoConfiguration类,这些类都是容器中的一个组件,加入到容器中,用他们做自动配置。

2.2 总结Spring Boot自动配置的精髓

        SpringBoot启动的时候会加载大量的自动配置类xxxxAutoConfiguration(就如在spring.factories那些类),当我们需要的功能在Spring Boot中恰好有默认的自动配置类,那么这个自动配置类在Spring Boot中一定有一个对应的XxxPropertoes类(相当于对应自动配置类的配置文件),这个类中有很多的属性,我们可以使用Spring Boot默认的属性,但是当这些默认值无法满足我们的时候,我们也可以在主配置文件中来使用XxxPropertoes类中的属性来配置我们需求的值。

3、 @Conditional派生注解

然而,虽然Spring Boot在启动的时候加载了全部的自动配置类,但是不是所用的这些类都是可以使用的,只有符合条件的自动配置类才可以使用,用于判断的就是这些@ConditionalXxx注解



@Conditional扩展注解
作用(判断是否满足当前指定条件)
@ConditionalOnJava 系统的java版本是否符合要求
@ConditionalOnBean容器中存在指定Bean;
@ConditionalOnMissingBean 容器中不存在指定Bean;
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication当前是web环境
@ConditionalOnNotWebApplication当前不是web环境
@ConditionalOnJndiJNDI存在指定项

问:如何知道哪些自动配置类生效了,哪些配置类没有生效?
我们可以通过在主配置文件中配置 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效。如下:

留言区

还能输入500个字符