SpringBoot自动配置原理

一、什么是Spring Boot的自动配置?

Spring Boot的最大的特点就是简化了各种xml配置内容,还记得曾经使用SSM框架时我们在spring-mybatis.xml配置了多少内容吗?数据源、连接池、会话工厂、事务管理···,而现在Spring Boot告诉你这些都不需要了,一切交给它的自动配置吧!

所以现在能大概明白什么是Spring Boot的自动配置了吗?简单来说就是用注解来对一些常规的配置做默认配置,简化xml配置内容,使你的项目能够快速运行。

是否对Spring Boot自动配置的原理感到好奇呢?下面我们将浅析Spring Boot的自动配置原理。

二、三大注解

在启动类中可以看到@SpringBootApplication注解,它是SpringBoot的核心注解,也是一个组合注解。我们进入这个注解可以看到里面又包含了其它很多注解,但其中@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan三个注解尤为重要。

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
@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:继承自Configuration,支持JavaConfig的方式进行配置。
  • @EnableAutoConfiguration:本文重点讲解,主要用于开启自动配置。
  • @ComponentScan:自动扫描组件,可以指定扫描路径,Spring会把指定路径下带有指定注解的类注册到IOC容器中。

三、@EnableAutoConfiguration

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

3.1 @AutoConfigurationPackage

我们先进入@AutoConfigurationPackage,发现它里面依然引用了@Import注解,继续进入AutoConfigurationPackages.Registrar.class,找到registerBeanDefinitions的方法。

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

}
	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));
		}

	}

可以看到这个方法包含两个参数metadataregistry。下面简单介绍下这两个参数。

  • metadata:用来获取启动类的信息,获取该类所在的包。
  • registry:用于bean注册。

可以大概知道这个方法是用于注册bean的定义的。

因此@AutoConfigurationPackage这个注解的作用是将添加该注解的类所在的包作为自动配置package进行管理。而该注解包含在@SpringBootApplication注解里面,所以SpringBoot应用启动时会将启动类所在的包作为自动配置的package。

3.2 @Import({AutoConfigurationImportSelector.class})

AutoConfigurationImportSelector.class进入查看源码,

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

它实现了DeferredImportSelector接口,DeferredImportSelector又继承了ImportSelector

public interface ImportSelector {

	String[] selectImports(AnnotationMetadata importingClassMetadata);
	
	@Nullable
	default Predicate<String> getExclusionFilter() {
		return null;
	}

}

可以看到所有的配置信息通过getCandidateConfigurations(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())得到,并最终由一个列表保存。我们继续查看getCandidateConfigurations()方法。

3.2.1 selectImports

AutoConfigurationImportSelector中实现的selectImports

selectImports

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());
	}

加载元数据的配置用到了AutoConfigurationMetadataLoader类提供的loadMetaData方法,该方法会默认加载类路径下 META-INF/springautoconfigure-metadata.properties 内的配置。

final class AutoConfigurationMetadataLoader {

    //默认加载元数据的路径
	protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

	private AutoConfigurationMetadataLoader() {
	}

    //默认调用改方法,传入默认 PATH
	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		return loadMetadata(classLoader, PATH);
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
		try {
			//获取数据存储 FEnumeration 中
			Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
					: ClassLoader.getSystemResources(path);
			Properties properties = new Properties();
			while (urls.hasMoreElements()) {
				//遍历 Enumeration 中的 URL,加载其中的属性, 存储到 Properties 中
				properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
			}
			return loadMetadata(properties);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
		}
	}

    //创建 AutoConfigurat ionMe tadata 的实现类 PropertiesAutoConf igurat ionMetadat
	static AutoConfigurationMetadata loadMetadata(Properties properties) {
		return new PropertiesAutoConfigurationMetadata(properties);
	}

	// AutoConfigurationMetadata 的内部实现类
	private static class PropertiesAutoConfigurationMetadata implements AutoConfigurationMetadata {

		private final Properties properties;

		PropertiesAutoConfigurationMetadata(Properties properties) {
			this.properties = properties;
		}

		@Override
		public boolean wasProcessed(String className) {
			return this.properties.containsKey(className);
		}

		@Override
		public Integer getInteger(String className, String key) {
			return getInteger(className, key, null);
		}

		@Override
		public Integer getInteger(String className, String key, Integer defaultValue) {
			String value = get(className, key);
			return (value != null) ? Integer.valueOf(value) : defaultValue;
		}

		@Override
		public Set<String> getSet(String className, String key) {
			return getSet(className, key, null);
		}

		@Override
		public Set<String> getSet(String className, String key, Set<String> defaultValue) {
			String value = get(className, key);
			return (value != null) ? StringUtils.commaDelimitedListToSet(value) : defaultValue;
		}

		@Override
		public String get(String className, String key) {
			return get(className, key, null);
		}

		@Override
		public String get(String className, String key, String defaultValue) {
			String value = this.properties.getProperty(className + "." + key);
			return (value != null) ? value : defaultValue;
		}

	}

}

在上面的代码中AutoConfigurationMetadataLoader 调用 ladMetadaClassLoadar cassLoaden)方法,会获取默认变量 PATH 指定的文件,然后加载并存储于 Enumeration 数据结构中。随后从变量 PATH 指定的文件中获取其中配置的属性存储 Poperties 内,最终调用在该类内部实现的 AutoConfigurationMetadata 的子类的构造方法。

spring-autoconfigure-metadata.properties 文件内的配置格式如下。

自动配置类的全限定名.注解名称=值

如果 spnaotningre-etadata properties 文件内有多个值,就用英文逗号分隔,例如:

org. springframework . boot . autoconfigure . data. jdbc . IdbcRepositoriesAutoConfiguration . ConditionalOnClass=org. springframework. data. jdbc . repos itory. config.JdbcConfigurat ion, org. springframework. jdbc . core . namedpar am .NamedParameterJdbcOperations
...

为什么要加载此元数据呢?加载元数据主要是为了后续过滤自动配置使用。Spring Boot 使用-Annlation 的处理器来收集自动加载的条件,这些条件可以在元数据文件中进行配置。SpingBoot 会将收集好的 @Confguration 进行一 次过滤,进而剔除不满足条件的配置类。

3.2.2 getAutoConfigurationEntry

selectImports中调用了getAutoConfigurationEntry获得自动配置的实体,下面来看下getAutoConfigurationEntry

getAutoConfigurationEntry

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			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);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

getAutoConfigurationEntry()方法内部调用 getCandidateConfigurations()方法,获取候选的配置

3.2.3 getCandidateConfigurations

getCandidateConfigurations

	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;
	}

继续进入loadFactoryNames()方法,可以发现一个获取资源的可疑地址:FACTORIES_RESOURCE_LOCATION

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, 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 properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

再进入FACTORIES_RESOURCE_LOCATION,发现值为:META-INF/spring.factories

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

原来Spring启动的时候会扫描所有jar下的META-INF/spring.factories,将其包装成Properties对象,然后再从Properties对象中获取key值为:EnableAutoConfiguration的数据添加到容器中。

回到之前的getCandidateConfigurations(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())方法,注意第一个参数,是一个方法,我们进入这个方法,发现返回的是EnableAutoConfiguration.class。可以得知第一个参数的作用就是用来确定key值的。

	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}

通过此方法,就可以获取到所有的spring.factories中的所有自动配置类,实现SpringBoot的自动装配。但是实现自动装配后,不一定能生效,需要满足@ConditionalOn***的条件。

@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
注解 作用
@ConditionalOnProperty application.properties 或 application.yml中是否有满足条件的配置
@ConditionalOnBean Bean 已经存在应用上下文时才会加载
@ConditionalOnMissingBean Bean 不存在应用上下文时才会加载
@ConditionalOnClass 某个类存在于 classpath 中才加载
@ConditionalOnMissingClass 某个类不存在于 classpath 中才加载
@ConditionalOnExpression 当条件为true时才加载
@ConditionalOnSingleCandidate 只有指定类已存在于 BeanFactory 中,并且可以确定单个
@ConditionalOnResource 加载的 bean 依赖指定资源存在于 classpath
@ConditionalOnJndi 只有指定的资源通过 JNDI 加载后才加载 bean
@ConditionalOnJava 只有运行指定版本的 Java 才会加载 Bean
@ConditionalOnWebApplication 只有运行在 web 应用里才会加载这个 bean
@ConditionalOnNotWebApplication 只有运行在非 web 应用里才会加载这个 bean
@ConditionalOnCloudPlatform 只有运行在指定的云平台上才加载指定的 bean,CloudPlatform 是 org.springframework.boot.cloud 下一个 enum 类型的类

四、总结

springboot 所有的自动配置都是在启动的时候扫描并加载:spring.factories所有的自动配置类都在这里面,但是不一定生效。要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功!
image

posted @ 2023-05-08 10:23  leepandar  阅读(135)  评论(0编辑  收藏  举报