Spring Boot自动装配和启动过程

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

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.Xmavan构建的便于源码阅读。本文以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中将一类场景要用到的依赖封装成一个starterspring-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 servertomcat、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容器其实就是BeanFactoryBeanFactory就是IOC容器的规范接口有多个实现最典型的就是DefalutListableBeanFactoryIOC容器中有一个成员MapBeanDefinitionMap该Map持有所有的BeanDefinition用来维护Bean的基本信息class、作用域等

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Spring