Spring Boot 在进行 SpringApplication 对象实例化时会加载 META-INF/spring.factories 文件,将该配置文件中的配置载入 Spring容器,进行自动配置。
一、源码分析
1、首先进入启动 Spring Boot 项目代码 SpringApplication.run(App.class,args)的源码。
程序清单:org/springframework/boot/SpringApplication.java
public static ConfigurableApplicationContext run (Object[ ] sources,String[ ] args ){
return new SpringApplication( sources ).run( args ) ;
}
2、可以看到 run 方法实际上在创建 SpringApplication对象实例,下面来看创建 SpringApplication 对象实例的代码。
程序清单:org/springframework/boot/SpringApplication.java
public SpringApplication( Object... sources ){
initialize( sources ) ;
}
3、接下来就是调用 initialize( sources ) 方法,该方法的源码如下:
程序清单:org/springframework/boot/SpringApplication.java
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
this.webEnvironment = deduceWebEnvironment();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
initialize( sources ) 方法解析:
-
如果sources⻓度⼤于0的话,加⼊到SpringApplication的sources中,该sources是⼀个LinkedHashSet.
-
调⽤deduceWebEnvironment⽅法判断是否是web环境
-
设置initializers.
-
设置Listeners.
-
设置mainApplicationClass.
4、initialize 方法调用了 getSpringFactoriesInstances 方法,代码如下:
程序清单:org/springframework/boot/SpringApplication.java
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
// 使⽤Set保存names来避免重复元素
Set<String> names = new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 根据names来进⾏实例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
// 对实例进⾏排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
getSpringFactoriesInstances 方法解析:
- 先获得ClassLoader.
- 调 SpringFactoriesLoader#loadFactoryNames进⾏加载,然后放⼊到LinkedHashSet进⾏去重.
- 调 createSpringFactoriesInstances进⾏初始化
- 排序
5、在 getSpringFactoriesInstances 中又调用了 loadFactoryNames 方法,继续进入该方法,查看源码如下:
程序清单:org/springframework/boot/SpringApplication.java
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURC
E_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassN
ames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
loadFactoryNames 方法解析:
- 获得factoryClassName,对于当前来说factoryClassName =org.springframework.context.ApplicationContextInitializer.
- 通过传⼊的classLoader加载META-INF/spring.factories⽂件.
- 通过调⽤PropertiesLoaderUtils#loadProperties将其转为Properties.
- 获得factoryClassName对应的值进⾏返回.
对于当前来说,由于我们只加⼊了spring-boot-starter-web的依赖,因此会加载如下的配置: - 在spring-boot/META-INF/spring.factories中.org.springframework.context.ApplicationContextInitializer值
6、从上述源码中可以看到架子啊了一个常量:FACTORIES_RESOURCE_LOCATION,该常量的源码如下:
/**
*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";
从该源码中可以看出,最终 Spring Boot 是通过加载 META-INF/spring.factories 文件进行自动配置的。其所在位置如下图:
二、spring.factories 分析
spring factories 文件非常重要,用来指导 Spring Boot 找到制定的自动配置文件。
spring factories 文件重点内容分析如下:
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
org.springframework.boot.env.PropertySourceLoader 对应的值表示指定的 Spring Boot 配置文件支持的格式。Spring Boot 的配置文件内置支持 properties、xml、yml、和 yaml 几种格式。其中 properties 和 xml 对应的 Loader 类为 PropertiesPropertySourceLoader,yml 和 yaml 对应的 Loader 类为 YamlPropertySourceLoader。
#Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
org.springframework.boot.SpringApplicationRunListener 对应的值表示运行的监听器类。默认会加载 EventPublishingRunListener,这个 RunListener 是在SpringApplication 对象的 run 方法执行到不同阶段时,发布相应的 event 给 SpringApplication 对象的 Listeners 中记录的事件监听器。
#Applicaiton Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer
org.springframework.context.ApplicationContextInitializer 对应的值表示 Spring Boot 中的应用程序的初始化类。默认加载4个ApplicationContextInitializer类。
- ConfigurationWarningsApplicationContextInitializer 的作用是报告常见的配置错误。
- ContextIdApplicationContextIntializer 的作用是给ApplicationContext设置一个ID。
- DelegatingApplicationContextIntializer 的作用是将初始化的工作委托给 context.initializer.classes 环境变量指定的初始化器。
- ServerPortInfoApplicationContextInitializer 的作用是监听 EmveddedServletCibtauber-InitializedEvent 类型的事件,然后将内嵌的Web服务器使用的端口设置到 Application-Context 中。
三、Spring Boot Web 开发自动配置
在 spring.factories 中可以看出,Web 开发的自动配置类是 org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,这个类中自动实现了 Spring MVC 的配置。现在以 Spring MVC 的如下配置为例,了解 Spring Boot 是如何实现该自动配置的。
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
1、查询 WebMvcAutoConfiguration 的源码如下:
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration{
其中 @ConditionalOnClass 是一个条件注解,意思就是只有当前项目运行环境中有 Servlet 类,并且有 DispatcherServlet 类以及 WebMvcConfigurerAdapter 类(说明本项目是需要集成 Spring MVC 的),Spring Boot 才会初始化 WebMvcAutoConfiguration 进行自动配置。
2、自动配置视图解析器 ViewResolver:
在WebMvcAutoConfiguration 类下找到以下源码:
@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix(this.mvcProperties.getView().getPrefix());
resolver.setSuffix(this.mvcProperties.getView().getSuffix());
return resolver;
}
@Bean 在这里定义了一个 Bean 对象 InternalResourceViewResolver,以前是通过<bean>便签来定义的。
@ConditionalOnMissingBean 是一个条件注释,在当前环境下没有这个 Bean 的时候才会创建该 Bean。
方法在返回值即 InternalResourceViewResolver ,正是我们需要的对象,那么视图解析器中前缀和后缀 Spring Boot 是如何实现自动配置的呢?
resolver.setPrefix(this.mvcProperties.getView().getPrefix());
resolver.setSuffix(this.mvcProperties.getView().getSuffix());
该源码会找到一个 View对象:
public static class view{
/**
* Spring MVC 视图前缀
*/
private String prefix;
/**
* Spring MVC 视图后缀
*/
private String suffix;
public String getPrefix(){
return this.prefix;
}
public void setPrefix(String prefix){
this.prefix = prefix;
}
public String getSuffix(){
return this.suffix;
}
public void setSuffix(String suffix){
this.suffix = suffix;
}
}
View 对象则会通过获取prefix、suffix 加载视图解析器需要的前缀和后缀,该参数的值是可以通过全局配置文件来指定前缀和后缀的,配置如下:
spring.mvc.view.prefix = # Spring MVC view prefix
spring.mvc.view.suffix = # Spring MVC view suffix