zoukankan      html  css  js  c++  java
  • 我的Spring Boot学习记录(一):自动配置的大致调用过程

    1. 背景

    Spring Boot通过包管理工具引入starter包就可以轻松使用,省去了配置的繁琐工作,这里简要的通过个人的理解说下Spring Boot启动过程中如何去自动加载配置。

    本文中使用的Spring Boot版本为2.0.0.RELEASE
    这里主要是说自动配置大致调用流程,其他暂不做分析

    2. 主要内容

    2.1. spring.factories

    首先,需要了解一件事,首先得知道有这么一件事,而自动配置这一件事得从META-INF/spring.factories说起。其本质类似properties文件,一种key-value型的文件。
    在Spring Boot的官方文档中,Creating Your Own Auto-configuration里面,它可以扫描加载META-INF/spring.factories中的EnableAutoConfiguration为key的配置类。引入一个依赖,例如:

    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    

    这个starter只是引入了其必须的依赖,没有做任何工作,最主要的是有个依赖为spring-boot-starter,这里包含了一个spring.factories,里面就有各种自动配置的EnableAutoConfiguration

    2.2. 怎么加载EnableAutoConfiguration

    2.2.1 SpringApplication.run

    此函数是一个Spring Boot项目的入口,这里与@SpringBootApplication有很大的关联。

    Spring Boot项目一般的启动代码如下:

    @SpringBootApplication
    public class SpringBootDemoApplication {
    	public static void main(String[] args) {
    		//主要提供了一个静态函数run来调用
    		SpringApplication.run(SpringBootDemoApplication.class, args);
    	}
    }
    

    再看run函数

    	public static ConfigurableApplicationContext run(Class<?> primarySource,
    			String... args) {
    		return run(new Class<?>[] { primarySource }, args); // 
    	}
    
    	public static ConfigurableApplicationContext run(Class<?>[] primarySources,
    			String[] args) {
    		return new SpringApplication(primarySources).run(args);
    	}
    	
    	// 最终被调用的run函数
    	public ConfigurableApplicationContext run(String... args) {	
    		//......		
    		context = createApplicationContext();			
    		prepareContext(context, environment, listeners, applicationArguments,
    					printedBanner);
    		refreshContext(context);
    		//......	
    		return context;
    	}
    

    以上中,最终是调用了org.springframework.boot.SpringApplication#run(java.lang.String...)方法,此方法主要是准备了Environment和ApplicationContext。而ApplicationContext就是Spring项目核心的东西,那与自动配置又有什么关系,这里就需要回去看下@SpringBootApplication注解

    2.2.2 @SpringBootApplication

    //....
    @EnableAutoConfiguration
    public @interface SpringBootApplication {
    	//.....
    }
    //在上面@SpringBootApplication的注解代码中,有个@EnableAutoConfiguration
    //.....
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    	//...
    }
    
    

    上面为SpringBootApplication和EnableAutoConfiguration注解的部分代码,其中,在EnableAutoConfiguration注解中又有@Import(AutoConfigurationImportSelector.class),这里的@Import是Spring context的内容,与后面的内容中org.springframework.context.annotation.ConfigurationClassParser类有关联,目前要知道其主要功能就是将AutoConfigurationImportSelector加载至上下文中。在了解AutoConfigurationImportSelector源码之前,我们需要先知道SpringFactoriesLoader,这就是一个META-INF/spring.factories文件加载器。 其源码可看org.springframework.core.io.support.SpringFactoriesLoader

    现在,我们看至AutoConfigurationImportSelector的源码:

    /**
     * {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration
     * auto-configuration}. This class can also be subclassed if a custom variant of
     * {@link EnableAutoConfiguration @EnableAutoConfiguration}. is needed.
     * 
     * 主要的意思为:DeferredImportSelector能够去处理 EnableAutoConfiguration自动配置类Import工作
     */
    public class AutoConfigurationImportSelector
    		implements DeferredImportSelector,... {
    	
    	/**
    	 * 返回一个需要被加载至Spring上下文的的类名数组
    	 */
    	@Override
    	public String[] selectImports(AnnotationMetadata annotationMetadata) {
    		//....
    		List<String> configurations = getCandidateConfigurations(annotationMetadata,
    					attributes);
    		//....
    		return StringUtils.toStringArray(configurations);
    	}
    
    	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
    			AnnotationAttributes attributes) {
    			//通过SpringFactoriesLoader加载出所有的EnableAutoConfiguration类
    		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
    				getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
    		return configurations;
    	}
    	
    	/**
    	 * 返回了一个让SpringFactoriesLoader加载的Class,就是EnableAutoConfiguration
    	 */
    	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    		return EnableAutoConfiguration.class;
    	}
    }
    
    

    那么究竟是谁去调用了org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports并加载了那些EnableAutoConfiguration。这里的步骤比较多,我们就从selectImports倒序说起,这里分为几点来说:

    • 首先,AutoConfigurationImportSelector 继承了接口ImportSelector
    • org.springframework.context.annotation.ConfigurationClassParser类通过接口org.springframework.context.annotation.ImportSelector调用了selectImports,这里调用的方法分别为#processDeferredImportSelectors#processImports,最终的指向都是#parse(Set<BeanDefinitionHolder>)方法。这里需要说明的是#processImports方法就是对于处理@Import注解的相关方法,该类的源码中注释有说明。
    • org.springframework.context.annotation.ConfigurationClassParser#parse(Set<BeanDefinitionHolder>)却是org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions调用,而#processConfigBeanDefinitions为自身方法所调用。
    • ConfigurationClassPostProcessor调用的源头是类org.springframework.context.support.PostProcessorRegistrationDelegate,这个类中有两个公共的调用方法。
    • 最后由org.springframework.context.support.AbstractApplicationContext#refresh调用
    • org.springframework.context.support.AbstractApplicationContext#refresh方法在org.springframework.boot.SpringApplication#run被调用了

    因此,这里就与我们上面介绍的SpringApplication.run产生了联系,就是通过其调用了抽象上下文AbstractApplicationContext的refresh方法,从而产生了上面的一系列步骤。

    可以看下UML类图了解它们关系
    UML
    可能这里说的不清楚,建议使用IDE进行debug看源码。而且这里对于Spring Context的内容没有展开,本人也一知半解(或者说不解,不了解),望见谅,有需要可以参考以下文章

    https://docs.spring.io/spring/docs/5.2.0.BUILD-SNAPSHOT/spring-framework-reference/core.html#spring-core
    https://www.cnblogs.com/davidwang456/p/5717972.html
    https://blog.csdn.net/yangyangiud/article/details/79835594

    3. 总结

    阅读了别人写的代码,看别人为何这么写,这里看到的就是对于接口的活用,对于封装以及工厂模式的应用,对于扩展,文件配置等等,自己能学到的还有很多,继续敲代码,看代码,向人家学习

    参考链接:
    https://docs.spring.io/spring-boot/docs/2.1.4.RELEASE/reference/htmlsingle/
    https://www.cnblogs.com/saaav/tag/spring boot/

  • 相关阅读:
    linux RCU锁机制分析
    什么是上下文切换
    Linux内核哈希表分析与应用
    linux内核中hlist_head和hlist_node结构解析
    如何使用queue_delayed_work函数
    《Linux内核设计与实现》读书笔记(十)- 内核同步方法
    自旋锁,读写锁和顺序锁的实现原理
    C语言中内存分布及程序运行中(BSS段、数据段、代码段、堆栈)
    深入理解linux系统下proc文件系统内容
    以太网最小帧长为什么是64字节
  • 原文地址:https://www.cnblogs.com/lger/p/10690633.html
Copyright © 2011-2022 走看看