00 前言
惭愧地狠,前几天的一个面试问到 springboot 是怎么做到简化配置的,我就说了个事先约定,内部实现没有答上来。用 springboot 也用了一年多,从来没想着去看看 springboot 是怎么实现简化配置,让大家爱用这个玩意儿的。
然后搜了下,说是加载 jar 包下的 META-INF/spring.factories 文件,但是又有个面试官问我,这里面的配置代表了什么意思呢?
我又瞎说了一通。
今天就找了个下资料,学习了下,然后自己点开源码看了下,发现主脉络写的很清晰,并不是很难懂,就此写一篇文章,加深下自己的记忆吧。
也为我的两次面试哀悼。
可能我不是属于考试型的吧。唉。
面试还是要懂些原理性的东西。
有一个面试官说的好,人有两种能力,一种是面试时表现出的能力,一种是工作中解决实际问题的能力。
我的第一种能力太弱了,都是靠第二种能力撑着的,但是第二种能力面试时没法面试出来,所以我每次换工作时都挺痛苦的。
废话不多说了,开启今天的 springboot 自动化配置之旅吧。
01 EnableAutoConfiguration
springboot 的启动类上有个注解 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 {
}
在这其中,可以看到有个 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,可以看到里面是 import 了 AutoConfigurationImportSelector 这样一个类。
继续追踪这个类,AutoConfigurationImportSelector 类里主要看一个 selectImports 方法,这个方法的调用链如下:
selectImports
->getAutoConfigurationEntry
-> getCandidateConfigurations
->SpringFactoriesLoader.loadFactoryNames
SpringFactoriesLoader.loadFactoryNames 方法如下:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
可以看到断言中说到是去 META-INF/spring.factories 这个文件下去寻找有没有自动配置类。
我们再点开 SpringFactoriesLoader.loadFactoryNames 这个方法确认下。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
可以看到确实调用了 loadSpringFactories 这个方法,loadSpringFactories 方法中开头的这段代码
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
即告诉我们,它是要找到所有 META-INF/spring.factories 下的文件,然后加载进来,使用 PropertiesLoaderUtils.loadProperties 方法读取其中的文件配置。
02 spring.factories
哪些 jar 包下有 META-INF/spring.factories 文件呢?一般是在 springboot 的核心类及 XXX-spring-boot-autoconfigure 或 spring-boot-autoconfigure-XXX 这样的 jar 包中。
本次我们只说 EnableAutoConfiguration,打开 spring-boot-autoconfigure-2.2.1.RELEASE.jar,这个就是 springboot 中最重要的自动配置包。
打开下面的 META-INF/spring.factories 文件,可以发现这个文件也是也是一组一组的 key=value 的形式,与我们常用的 properties 文件没有什么区别。
截取其中部分内容如下:
# 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,\
……
这里 EnableAutoConfiguration 这个 key 对应的 value 很长,以逗号分割。
可以看到这里的 value 其实都是一个个类,那么它们是在哪里呢?
再返回上一层看看 spring-boot-autoconfigure-2.2.1.RELEASE.jar 下的 org.springframework.boot.autoconfigure 代码包。
这里,有各种各样事先写好的配置类。
我们思考下,为什么在 application.properties 中写上诸如 spring.datasource.url=XXX 的配置就能加载 jdbc 了?
03 application.properties
下面是我们在 application.properties 常用的加载 jdbc 的方式:
spring.datasource.url=jdbc:oracle:thin:@192.168.1.7:1521:orcl
spring.datasource.username=yaomaomao
spring.datasource.password=Iyaoshen369
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driverClassName=oracle.jdbc.driver.OracleDriver
这里配置的意思大家都应该明白,但是为什么它能起作用呢?
我们先从 spring.factories 文件中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 这个 key 中找到 jdbc 相关的 value,如下,找到了这些:
……
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
……
打开 org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration 这个类,发现这样的代码
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class})
public class DataSourceAutoConfiguration {
public DataSourceAutoConfiguration() {
}
}
主要看这段注解
@EnableConfigurationProperties({DataSourceProperties.class})
打开 DataSourceProperties 这个类,发现如下内容
@ConfigurationProperties(
prefix = "spring.datasource"
)
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
private ClassLoader classLoader;
private String name;
private boolean generateUniqueName;
private Class<? extends DataSource> type;
private String driverClassName;
private String url;
private String username;
private String password;
private String jndiName;
...
}
至此,我们基本上是明白了 @EnableAutoConfiguration 是怎么起作用的,又是怎么与 application.properties 关联的。
04 总结
最后,在面试时,我们能不能一句话总结下,springboot 是怎么做到简化配置的?
答:主要是 @EnableAutoConfiguration 这个注解起的作用,这个注解是间接隐藏在 springboot 的启动类注解 @SpringBootApplication 中。
通过这个注解,SpringApplication.run(...)的内部就会执行 selectImports()方法,寻找 META-INF/spring.factories 文件,读取里面的文件配置,将事先已经写好的自动配置类有选择地加载到 Spring 容器中,并且能按照约定的写法在 application.properties 中配置参数或开关。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于