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));
}
}
可以看到这个方法包含两个参数metadata
和registry
。下面简单介绍下这两个参数。
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,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功!