当前位置: 首页 > news >正文

Spring Boot自动装配原理详解

目录

1.环境和依赖

1.1.spring boot版本

1.2.依赖管理

2.自动装配

2.1.流程概述

2.2.三大步前的准备工作

2.2.1.注解入口

2.2.2.获取所有配置类

2.3.获取过滤列表

2.3.1.目的

2.3.2.过程

2.4.装载

2.4.1.目的

2.4.2.过程

2.5.自动配置

3.启动过程

3.1.整体流程

3.2.创建环境信息对象

3.3.创建应用上下文对象

3.4.刷新应用上下文对象

3.4.1.准备刷新

3.4.2.刷新


 

1.环境和依赖

1.1.spring boot版本

springboot 2.2.X版本采用的maven构建,2.3.X采用gradle构建,因此采用2.2.X,mavan构建的便于源码阅读。本文以2.2.9为例进行Spring Boot自动装配原理的解析。

1.2.依赖管理

引入Spring Boot的方式有两种

  • 引入spring-boot-dependencies的pom文件
  • 将spring-boot-starter-parent作为父级pom

这两种方式的底层都是都是一样的,都是引入了spring-boot-dependencies这个pom文件来管理Spring Boot的所有依赖。

SpringBoot中将一类场景要用到的依赖封装成一个starter,spring-boot-dependencies中包含了J2EE中所有场景(starter)的依赖,并声明了依赖的版本号。

2.自动装配

2.1.流程概述

首先所有JAVA程序的入口都是main方法,Spring Boot也不例外,只有main方法执行时,所有流程步骤才会执行,此处我们只是从启动流程中剥离出和自动装配相关的流程来进行单独解析。只需要大致知道自动装配流程有几步即可,如果有其它疑惑看后文的启动过程解析,就能豁然开朗。

自动装配的整个流程可以分为三大步

  1. 获取过滤列表
  2. 获取自动配置类列表
  3. 比对移除、封装返回

1.获取条件列表

获取类自动装载的条件列表。

2.获取自动配置列表

获取自动装载类的列表。

3.比对移除、封装返回

按照条件列表,将不满足被自动装载条件的类移除掉,返回满足条件的类列表。

2.2.三大步前的准备工作

2.2.1.注解入口

@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启动自动装配:

@Import(AutoConfigurationImportSelector.class)  ,AutoConfigurationImportSelector会完成所有配置类的获取以及相关的准备工作。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

2.2.2.获取所有配置类

AutoConfigurationImportSelector被加载后,经过层层调用,最终会调用到DeferredImportSelector中:

会去扫描所有@Configuration封装成一个列表返回。

public Iterable<Entry> getImports() {
            Iterator var1 = this.deferredImports.iterator();

            while(var1.hasNext()) {
                ConfigurationClassParser.DeferredImportSelectorHolder deferredImport = (ConfigurationClassParser.DeferredImportSelectorHolder)var1.next();
                this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());
            }
			//将得到的自动配置类按照@order进行排序
            return this.group.selectImports();
        }

2.3.获取过滤列表

2.3.1.目的

获取过滤列表,即去获取META-INF/spring-autoconfigure-metadata.properties这一文件。这个文件中会详细记录Spring Boot自带的各大J2EE场景的自动配置类(@Configuration)各自被自动装载生效的前提条件是什么。

2.3.2.过程

DeferredImportSelector.Group.process()中会首先获取自动装配的过滤条件列表,该列表中记录了待装配的类的装配条件。获取的核心方法是getAutoConfigurationMetadata(),该方法会根据传过来的ClassLoader去遍历加载classpath下的所有依赖,获取依赖中的META-INF/spring-autoconfigure-metadata.properties文件。

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()));
			//获取自动配置类
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
    		//解析存放自动配置类
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

private AutoConfigurationMetadata getAutoConfigurationMetadata() {
			if (this.autoConfigurationMetadata == null) {
				this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
			}
			return this.autoConfigurationMetadata;
		}
final class AutoConfigurationMetadataLoader {

	protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

	private AutoConfigurationMetadataLoader() {
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		return loadMetadata(classLoader, PATH);
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
		try {
			Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
					: ClassLoader.getSystemResources(path);
			Properties properties = new Properties();
			while (urls.hasMoreElements()) {
				properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
			}
			return loadMetadata(properties);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
		}
	}

过滤列表中会以KV键值对的方式记录装配条件,例如:

org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.annotation.EnableRabbit

2.4.装载

2.4.1.目的

  • 获取自动配置列表

  • 对比过滤列表,移除不满足自动装载的类

  • 封装返回

2.4.2.过程

process()方法中会调用getAutoConfigurationEntry()方法,并将过滤列表传和ClassLoader传过去,在getCandidateConfigurations()方法中通过传递的ClassLoader获取自动装配的列表"META-INF/spring.factories",然后比对过滤列表,将满足条件的待装配类的全路径记录在AutoConfigurationImportSelector.AutoConfigurationGroup的一个叫autoConfigurationEntries的List中。

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   //从spring.factories中加载所有自动配置类
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   //移除重复配置类
   configurations = removeDuplicates(configurations);
   //得到指定要移除的类(@SpringBootApplication(exclude=FreeMarkerAutoConfiguration.class))
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   //检查指定要移除的类,如果不是配置类,抛出异常
   checkExcludedClasses(configurations, exclusions);
   //移除指定要移除的自动配置类
   configurations.removeAll(exclusions);
   //获取满足条件的自动配置类列表
   configurations = filter(configurations, autoConfigurationMetadata);
   //记录下符合条件的对象,并封装在实体中返回
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

一切执行完毕后会回到入口出继续向下执行this.group.selectImports(),最终会调用到AutoConfigurationImportSelector的selectImports()方法,在该方法中会根据@order对自动配置类进行排序。

public Iterable<Entry> selectImports() {
   if (this.autoConfigurationEntries.isEmpty()) {
      return Collections.emptyList();
   }
   Set<String> allExclusions = this.autoConfigurationEntries.stream()
         .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
   Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
         .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
         .collect(Collectors.toCollection(LinkedHashSet::new));
   processedConfigurations.removeAll(allExclusions);

   return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
         .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
         .collect(Collectors.toList());
}

2.5.自动配置

在自动装载步骤中已经获得需要加载的自动配置类的全路径,接下来就是自动配置。

以随便一个AutoConfiguration类为例:

头上的一大串@Conditional注解其实就是过滤时的过滤条件,过滤列表其实就是通过这些条件注解生成的。

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(HttpProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {}

这个@Configuration满足条件后其中的@Bean都会被自动装入IOC。

3.启动过程

3.1.整体流程

Spring boot的启动过程就是围绕上下文的创建、准备、刷新(填充)展开的。

spring应用上下文和servletContext不是一个东西,servlet上下文用来维系当前应用的一块共享空间,目的是实现资源和数据在应用中的全局共享。spring的上下文是一个维护Bean定义以及对象之间协作关系的高级接口,目的是维护好整个spring中的资源,如配置文件、Bean对象等,其涵盖了IOC,但不只有IOC,可以理解为Spring应用的一个抽象。

在SpringApplication的run()方法中创建应用上下文,整个SpringApplication的run方法主要完成四个核心动作:

  1. prepareEnvironment

    创建环境信息对象,解析环境参数,包含配置文件、命令行传参等。

  2. createApplicationContext

    创建应用上下文对象

  3. prepareContext

    准备应用上下文对象

  4. refreshContext

    刷新应用上下文对象

// 类 SpringApplication 代码片段
	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
          // 包装通过命令行传入的名命令行参数
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
          // 结合命令行参数 准备环境对象,该环境对象将会被设置到应用上下文对象 ApplicationContext 上  ,
          // 环境对象通常包含如下信息 : 
          // 1. profile
          // 2. system properties
          // 3. system environment
          // 4. commandline arguments
          // 5. spring 配置文件
          // 6. 一个随机值属性源 random
          // 对于当前 WebFlux 应用来讲,这里实现类会使用 StandardReactiveWebEnvironment
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
          // 创建应用上下文对象 ApplicationContext  
          // 实现类会采用 : AnnotationConfigReactiveWebServerApplicationContext
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
          // 准备应用上下文对象 ApplicationContext          
          // 1. 关联环境信息对象到应用上下文对象
          // 2. 对象创建后置处理 : 设置容器的类型转换服务
          // 3. 初始化应用上下文对象:调用各个 ApplicationContextInitializer
          // 4. 广播事件 : ApplicationContextInitializedEvent
          // 5. 将应用程序参数作为一个 bean 注册到容器 : springApplicationArguments
          // 6. 将应用程序入口类作为 bean 注册到容器 (load)
          // 7. 上下文加载完成生命周期事件回调,为各个实现了 接口 ApplicationContextAware 的 
          //    ApplicationListener 设置应用上下文对象属性, 并广播事件 : ApplicationPreparedEvent
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
          // 刷新应用上下文对象  ApplicationContext 
          // 主要是调用应用上下文对象  ApplicationContext  自身的 refresh 方法
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
            
          // 应用程序上下文对象 ApplicationContext 已经准备就绪,
          // 现在调用各种开发人员或者框架其他部分定义的 
          // ApplicationRunner 或者 CommandLineRunner
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

3.2.创建环境信息对象

// SpringApplication 代码片段
	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
       // 创建环境信息对象 environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
       // 将应用程序参数关联到环境信息对象 environment
		configureEnvironment(environment, applicationArguments.getSourceArgs());
       // 发布应用程序事件 : 环境信息对象准备好了 ,
       // 同步调用各个事件监听器
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
					deduceEnvironmentClass());
		}       
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

普通web应用和reactive创建的环境信息对象类型不同,但是实际功能相同,并没有什么太大区别。

// SpringApplication 代码片段
    private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
		}
	}

3.3.创建应用上下文对象

根据之前环境推断中得到的当前应用的环境类型来创建不同类型的应用上下文。

// SpringApplication 代码片段
	protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
             // 根据 this.webApplicationType 确定应用上下文实现类
				switch (this.webApplicationType) {
				case SERVLET:
            // DEFAULT_SERVLET_WEB_CONTEXT_CLASS 常量值为 : 
            // org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
            // 对应 Spring MVC Servlet Web 环境
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
            // DEFAULT_REACTIVE_WEB_CONTEXT_CLASS 常量值为 : 
            // org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
            // 对应 Spring WebFlux Reactive Web 环境
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
            // DEFAULT_CONTEXT_CLASS 常量值为 : 
            // org.springframework.context.annotation.AnnotationConfigApplicationContext
            // 不对应任何 Web 环境
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
			"Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
			ex);
			}
		}
        
       // 确定应用上下文实现类之后,实例化应用上下文对象 
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

3.4.刷新应用上下文对象

3.4.1.准备刷新

这一步主要是完成刷新前的准备工作,将除IOC相关的一切context中的东西全部赋值初始化好。

主要完成以下动作:

  • 关联环境信息

  • 查找调用各种前置、后置处理器(自定义的、自带的)

  • 调用各种回调

  • 获取主启动类的路径,将主启动类封装成一个BeanDefinition

// SpringApplication 代码片段
	private void prepareContext(ConfigurableApplicationContext context, 
			ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, 
			ApplicationArguments applicationArguments, 
			Banner printedBanner) {
       // 1. 关联环境信息对象到应用上下文对象     
		context.setEnvironment(environment);
       // 2. 对象创建后置处理 : 设置容器的类型转换服务 
		postProcessApplicationContext(context);
       // 3. 初始化应用上下文对象:调用各个 ApplicationContextInitializer 
		applyInitializers(context);
       // 4. 广播事件 : ApplicationContextInitializedEvent 
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
       // 5. 将应用程序参数作为一个 bean 注册到容器 : springApplicationArguments 
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
        
		// 获取主启动类的路径
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
       // 6. 将主启动类封装成一个BeanDefinition 
		load(context, sources.toArray(new Object[0]));
       // 7. 上下文加载完成生命周期事件回调,为各个实现了 接口 ApplicationContextAware 的 
       //    ApplicationListener 设置应用上下文对象属性, 并广播事件 : ApplicationPreparedEvent        
		listeners.contextLoaded(context);
	}

3.4.2.刷新

主要是调用应用上下文对象 ApplicationContext 自身的 refresh 方法,这是上下文对象的初始化中最关键的一步,该步骤中会完成几个核心动作:

  • 初始化IOC容器(即BeanFactory)

    该步骤中就会扫描解析注解,触发自动装配.

  • 初始化WebServer容器

刷新应用上下文的动作其实是在spring相关的jar中,因此首先要有个概念,在这一步之前spring boot的动作已经完成,真正与IOC相关的动作还是由spring来完成,所以说spring boot是对spring的二次封装。

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// 做一些初始化动作
			prepareRefresh();
			// 获取bean factory
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
			// 初始化bean factory,为其成员属性赋一些值
			prepareBeanFactory(beanFactory);
			try {
				// 获取所有bean后置处理器
				postProcessBeanFactory(beanFactory);
				// **最核心的方法,注解的扫描,自动配置类的装载,IOC的初始化等全在这个方法中
				invokeBeanFactoryPostProcessors(beanFactory);
				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);
				// Initialize message source for this context.
				initMessageSource();
				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();
				// Initialize other special beans in specific context subclasses.
				onRefresh();
				// Check for listener beans and register them.
				registerListeners();
				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);
				// Last step: publish corresponding event.
				finishRefresh();
			}
			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

初始化IOC:

创建容器其实没有什么说的,就是new一个web server(tomcat、netty或者jetty)出来。这里着重要说一下初始化IOC。

入口在invokeBeanFactoryPostProcessors(beanFactory)。

IOC容器的初始化分为三步:

  • Resource定位

    定位到需要的各种路径:

    • BasePackage

      这一步在准备刷新的时候就已经完成,并在封装在了主启动类封装为的BeanDefinition中。

      基于BasePackage去扫描通过注解自定义的需要注入IOC的Bean。

    • 自动配置类的全路径

      这一步在刷新应用上下文的时候进行,即去获取factory.properties。

      基于自动配置类的全路径去将对应自动配置类注入IOC。

  • BeanDefinition载入

    将定位到的Resource记录的Class分别封装为一个个的Definition。

  • BeanDefinition注册

    将Definition注册进IOC中。其实就是注入到一个ConcurrentHashMap中,IOC就是通过这个Map来持有这些BeanDefinition的。

IOC涉及的两个核心概念:

  • BeanDefinition

  • BeanFactory

    IOC容器其实就是BeanFactory,BeanFactory就是IOC容器的规范接口,有多个实现,最典型的就是DefalutListableBeanFactory,IOC容器中有一个成员Map(BeanDefinitionMap),该Map持有所有的BeanDefinition,用来维护Bean的基本信息(class、作用域等)

 

 

相关文章:

  • 小型网站建设的经验/反向链接查询
  • 如果给公司做网站/手机百度网盘下载慢怎么解决
  • 苏州策划网站模板建站公司/杭州百度公司在哪里
  • 南昌网站设计系统/深圳推广公司排行榜
  • 网站建设方案策划/安卓优化大师官网下载
  • 做爰免费时看视频澳门网站/网络推广吧
  • 机器人的雅克比矩阵、海森矩阵、可操作度雅克比矩阵-自己学习用
  • 系统分析师案例必备知识点汇总---2023系列文章一
  • 卷积神经网络中特征图大小计算公式总结
  • 电脑删除了大文件怎么恢复?看看这四种方法
  • Maven高级进阶
  • 【Linux】线程互斥
  • C语言—动态内存管理
  • MySQL查询中复杂函数使用
  • VueUse(中文)——核心函数:State相关函数
  • 浅谈DNS解析
  • 图片转PDF怎么弄?这几个方法值得你试一试
  • 数据结构:线性表的链式表示和实现