SpringBoot 学习(三)---- SpringBoot 核心 之 Spring Boot自动配置原理

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  Collection getSpringFactoriesInstances(Class type,
     Class[] parameterTypes, Object... args) {
     ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
     // Use names and ensure unique to protect against duplicates
     // 使⽤Set保存names来避免重复元素
     Set names = new LinkedHashSet(
     SpringFactoriesLoader.loadFactoryNames(type, classLoader));
     // 根据names来进⾏实例化
     List 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 loadFactoryNames(Class factoryClass, ClassLoader classLoader) {
 String factoryClassName = factoryClass.getName();
 try {
 Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURC
E_LOCATION) :
 ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
 List result = new ArrayList();
 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.
*

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 是如何实现该自动配置的。


    
    

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,以前是通过便签来定义的。

@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

 

你可能感兴趣的:(SpringBoot 学习(三)---- SpringBoot 核心 之 Spring Boot自动配置原理)