Spring Boot是Pivotal团队提供的全新框架,使用“习惯优于配置”的理念,将开发过程中的习惯配置以自动注入的形式注入配置,只需很少的配置就可以快速开始项目。
本文使用的Spring Boot版本为2.1.3.RELEASE
作为示例。
自定义一个自动配置类
首先我们使用idea新建一个Spring Boot应用,发现入口类上面有一个@SpringBootApplication
的注解,这个就是Spring Boot自动配置的核心。
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
接下来我们新建一个配置类CustomConfig
:
@Configuration
public class CustomConfig {
@Bean
public CustomConfig customConfig() {
System.out.println("CustomConfig has been loaded");
return new CustomConfig();
}
}
在resources
目录下新建META-INF
文件夹,然后新建spring.factories
文件,内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.superlee.CustomConfig
指定了CustomConfig
的类路径。这样我们自定义的一个自动配置类就完成类,然后就是启动应用验证了,启用应用后控制台打印如下:
Connected to the target VM, address: '127.0.0.1:50615', transport: 'socket'
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.3.RELEASE)
2019-03-10 15:19:53.716 INFO 7969 --- [ main] com.superlee.TestApplication : Starting TestApplication on superleedeMacBook-Pro.local with PID 7969 (/Users/superlee/Documents/idea_work/superlee/target/classes started by superlee in /Users/superlee/Documents/idea_work/superlee)
2019-03-10 15:19:53.719 INFO 7969 --- [ main] com.superlee.TestApplication : No active profile set, falling back to default profiles: default
CustomConfig has been loaded
2019-03-10 15:19:54.172 INFO 7969 --- [ main] com.superlee.TestApplication : Started TestApplication in 0.73 seconds (JVM running for 1.219)
Disconnected from the target VM, address: '127.0.0.1:50615', transport: 'socket'
Process finished with exit code 0
我们可以看到打印出来了CustomConfig has been loaded
这句话,说明我们的自动配置生效了。
自动配置的思考
Spring Boot怎么会知道要把CustomConfig
这个类自动加载到配置中呢?回想一下我们做了哪些操作:
1.在程序入口TestApplication
上加了@SpringBootApplication
注解
2.在CustomConfig
上加上@Configuration
注解
3.新建了resources/META-INF/spring.factories
文件,并将CustomConfig
的包路径配置到了EnableAutoConfiguration
的key下面
我们可以猜测,Spring Boot应该是根据我们配置的CustomConfig
包路径去实例化该对象,然后放到Spring context中,接下来就要看下源码看看是如何实现的。
源代码分析
SpringBootApplication
@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 {
//省略
}
我们可以发现SpringBootApplication
这个注解集成了很多个注解,重点关注SpringBootConfiguration
EnableAutoConfiguration
ComponentScan
这三个注解。
@Configuration
public @interface SpringBootConfiguration {
}
SpringBootConfiguration
注解其实和Configuration
等效的,标示为Spring Boot应用提供配置。
ComponentScan
组件扫描注解,用于扫描指定包的bean,没有指定包名则扫描该注解所在的路径下的文件。本例即TestApplication
所在的根目录。
这里需要注意excludeFilters
属性,即组件扫描不包含的过滤器,看下AutoConfigurationExcludeFilter
这个过滤器:
public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {
private ClassLoader beanClassLoader;
private volatile List<String> autoConfigurations;
@Override
public void setBeanClassLoader(ClassLoader beanClassLoader) {
this.beanClassLoader = beanClassLoader;
}
@Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory) throws IOException {
return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
}
private boolean isConfiguration(MetadataReader metadataReader) {
return metadataReader.getAnnotationMetadata()
.isAnnotated(Configuration.class.getName());
}
private boolean isAutoConfiguration(MetadataReader metadataReader) {
return getAutoConfigurations()
.contains(metadataReader.getClassMetadata().getClassName());
}
protected List<String> getAutoConfigurations() {
if (this.autoConfigurations == null) {
this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(
EnableAutoConfiguration.class, this.beanClassLoader);
}
return this.autoConfigurations;
}
}
我们可以看到这个过滤器有个match()方法,如果类加了@Configuration
并且声明为EnableAutoConfiguration
的自动配置类,则匹配。
但这里组件扫描注解不包含这个filter,也就是说组件扫描的时候,并不会示例化自动配置的类。那么这些自动配置类是如何被找到然后加载到IOC容器中呢?
下面我们看下EnableAutoConfiguration
注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
我们可以看到该注解导入了一个AutoConfigurationImportSelector
选择器,该选择器有一个AutoConfigurationGroup
的内部类,我们关注一下该内部类的process()
方法
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
···
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
//判断是否启用了EnableAutoConfiguration注解
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
//获取excludeName和exclude属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//拿到所有的自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
//移除重复的自动配置类
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
//校验注解中不包含的自动配置类的合法性,如果classpath中不包含忽略的类或者自动配置的类中不包含忽略的类,抛异常
checkExcludedClasses(configurations, exclusions);
//移除要忽略的类
configurations.removeAll(exclusions);
//类加载器遍历class,如果某个自动配置类的Conditional不满足,则移除改配置
configurations = filter(configurations, autoConfigurationMetadata);
//发送自动配置类导入的事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
...
private static class AutoConfigurationGroup implements DeferredImportSelector.Group,
BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
...
@Override
public void process(AnnotationMetadata annotationMetadata,
DeferredImportSelector deferredImportSelector) {
Assert.state(
deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
//加载autoConfiguration的路径名。
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(),
annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
}
}
我们看一下getAutoConfigurationEntry(_, _)
方法里调用的getCandidateConfigurations(annotationMetadata, attributes)
方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
//getSpringFactoriesLoaderFactoryClass()方法返回EnableAutoConfiguration.class类
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;
}
SpringFactoriesLoader
是自动配置扫描过程中很重要的一个类,我们看下它的代码:
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
//拿到传入的EnableAutoConfiguration.class
String factoryClassName = factoryClass.getName();
//获取key=org.springframework.boot.autoconfigure.EnableAutoConfiguration的所有集合
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 {
//classLoader去搜索所有包含META-INF/spring.factories文件的jar包
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);
//读取jar中的spring.factories文件,然后转换为key-value形式
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
//spring.factories文件中的属性名
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);
}
}
}
这样就可以把classpath里所有的自动配置类找出来,放到list集合中,后面可以根据包路径使用反射的方法去实例化。
Conditional条件注解
条件注解为自动配置提供类实现基础,我们可以看到很多自动配置类都和条件注解结合使用,这样就可以灵活的根据classpath中的具体类去加载具体的配置。
总结
1.SpringBoot程序启动的时候,回去看是否启用了EnableAutoConfiguration
2.启用的情况下,classLoader在classpath中搜索所有的包含META-INF/spring.factories
文件的jar包,然后解析spring.factories
文件,拿到所有key=org.springframework.boot.autoconfigure.EnableAutoConfiguration的字符集合
3.根据条件注解和classpath中包含的类去筛选满足条件的配置,然后使用反射实例化
4.把实例化的配置加载到Spring IOC容器中,然后刷新上下文,即可自动加载配置了。