【Java知识体系】SpringBoot启动原理探究,源码解读

大家好!我是未来村村长,就是那个“请你跟我这样做,我就跟你这样做!”的村长👨‍🌾!

||To Up||

未来村村长正推出一系列【To Up】文章,该系列文章重要是对Java开发知识体系的梳理,关注底层原理和知识重点。”天下苦八股文久矣?吾甚哀,若学而作苦,此门无缘,望去之。“该系列与八股文不同,重点在于对知识体系的构建和原理的探究。

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can
“just run”.

Spring被称为 J2EE的春天,是一个开源的轻量级的Java开发框架,具有控制反转(IOC)面向切面(AOP)两大核心。但是使用Spring开发,我们还是需要配置Beans.xml等各种文件,如果是开发Web应用,我们还需要配置web.xml、更多的Beans.xml以及Tomcat服务器,其中存在许多固定的配置套路。

SpringBoot便是为了简化Java开发流程而诞生的。根据官方介绍,Spring Boot使您可以轻松地创建独立的、基于产品级的Spring应用程序,你只需要"run"即可。这里的run就是SpringApplication的run()方法,这是整个SpringBoot项目的启动入口,也是SpringBoot简化开发的思想体现。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class xxxApplication {
    public static void main(String[] args) {
        SpringApplication.run(xxxApplication.class, args);
    }
}

​ 我们将从这个Main类为出发点,分析SpringBoot的自动配置原理,设计框架,启动流程等核心原理。

一、自动配置

1、底层注解解析

​ 在讲解xxxApplication的核心注解前,得先回顾或学习新的注解@Configuration、@Import、@Conditional。

(1)@Configuration

@Configuration:作用在类上,告诉SpringBoot这是一个配置类,相当于Spring中的xml配置文件(可替换xml配置文件),本质上也是一个Component。

@Bean:作用在方法上,用于创建一个Bean对象,该Bean对象会在IoC容器启动时初始化。给容器中添加组件,相当于Spring中xml配置文件中的<bean>标签。这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。

@Component: 标注Spring管理的Bean,使用@Component注解在一个类上,表示将此类标记为Spring容器中的一个Bean。

​ SpringIOC 容器管理一个或者多个bean,这些bean都需要在@Configuration注解下进行创建,在一个方法上使用@Bean注解就表明这个方法需要交给Spring进行管理,用于容器初始化时通过依赖注入生成相应的Bean对象。

​ 我们来看@Configuration的代码,我们知道Configuration实际上也是一个Component注解,该注解的属性proxyBeanMethods()的属性默认为true,意为是否通过CGLIB代理@Bean方法以强制执行bean的生命周期行为,被代理的bean将是单例化创建,即容器中只存在一个bean。虽然在Component下使用@Bean进行Bean的注册也是默认单例,但是该注册方法不会通过代理完成。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;
}
  • @Configuration(proxyBeanMethods = true):Full模式(全模式),保证每个@Bean方法被调用多少次返回的组件都是单实例的
  • @Configuration(proxyBeanMethods = false):Lite模式(轻量级模式),每个@Bean方法被调用多少次返回的组件都是新创建的
(2)@Import

Provides functionality equivalent to the {@code } element in Spring XML.Allows for importing {@code @Configuration} classes, {@link ImportSelector} and{@link ImportBeanDefinitionRegistrar} implementations, as well as regular component classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).

​ Import注解对应的是配置文件中的<import>标签,在Spring的beans.xml文件中,我们可以使用import将不同的beans文件导入到一个文件中。

<import resource="bean1.xml"/>
①@Configuration类或普通类

​ @Import注解可以实现@Configuration类,ImportSelector和ImportBeanDefinitionRegistrar接口的实现类的导入。在Spring版本4.2以后,Import也可以导入普通类,将其注册成为Spring Bean。Import导入@Configuration类,如同将不同的beans文件导入到一个文件中。

②ImportBeanDefinitionRegistrar接口的实现类

​ 导入ImportBeanDefinitionRegistrar接口的实现类主要用于手动注册bean到容器。

public class MyBean implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        //指定bean定义信息(包括bean的类型、作用域等)
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(xxxClass.class);
        //注册一个bean指定bean名字(id)
        beanDefinitionRegistry.registerBeanDefinition("TestDemo4444",rootBeanDefinition);
    }
}
③ImportSelector接口实现类

​ ImportSelector是配置类导入选择器,ImportSelector接口源码如下。ImportSelector决定可以引入哪些@Configuration类。该接口提供了selectImports方法,该方法可根据具体实现决定返回哪些配置类的全限定名,结果以字符串数组返回。

​ 当在@Configuration标注的Class上使用@Import引入了一个ImportSelector实现类后,会把实现类中返回的Class名称都定义为bean。

public interface ImportSelector {
	String[] selectImports(AnnotationMetadata importingClassMetadata);
}

​ 一个ImportSelector实现类通常也可能会实现各种Aware接口,如果实现了这些Aware接口,这些接口方法的调用会发生在selectImports之前。比如:EnvironmentAware、BeanFactoryAware、BeanClassLoaderAware、ResourceLoaderAware。

(3)@Conditional

@Conditional(Conditions):根据是否满足某个特定的条件创建一个特定的Bean。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
	/**
	 * All {@link Condition} classes that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();
}

除Conditional以外,Spring还提供了以下指定条件类型的衍生Conditional:

  • @ConditionalOnClass:该注解的参数对应的类必须存在,否则不解析该注解修饰的配置类;
  • @ConditionalOnMissingBean:该注解表示,如果存在它修饰的类的bean,则不需要再创建这个bean;
  • @ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean;
  • @ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean;
  • @ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean;

2、自动配置核心注解

​ 绕了一大圈,我们再来看启动类的注解。

@SpringBootApplication

​ 该注解是一个组合注解,可以用以下注解进行代替。

@ComponentScan
@SpringBootConfiguration
@EnableAutoConfiguration
(1)@ComponentScan

​ 用来指定包的扫描范围,加了包扫描@ComponentScan注解后,只要标注了@Controller、@Service、@Repository、@Component注解中的任何一个,其组件都会被自动扫描,加入到容器中。

(2)@SpringBootConfiguration

​ SpringBootConfiguration也是@Configuration注解,说明被注解的类是一个配置类。当我们使用@SpringBootConfiguration标记一个类时,这意味着该类提供了@Bean定义方法。 Spring容器处理配置类以为我们的应用实例化和配置bean。

(3)@EnableAutoConfiguration

​ @EnableAutoConfiguration的主要功能是启动Spring应用程序上下文时进行自动配置,它会尝试猜测并配置项目可能需要的Bean。自动配置通常基于项目classpath中引入的类和已定义的Bean来实现的,被自动配置的组件来自项目依赖的jar包。

​ 详情见下节。

3、EnableAutoConfiguration核心原理

(1)@EnableAutoConfiguration

​ @EnableAutoConfiguration源码如下,其关键功能是通过@Import注解导入的ImportSelector来完成的,是自动配置的核心实现。

//启用SpringApplicationContext的自动配置,尝试猜测和配置您可能需要的bean。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    //根据类排除指定的自动配置
	Class<?>[] exclude() default {};
    //根据类名排除指定的自动配置
	String[] excludeName() default {};
}
(2)AutoConfigurationImportSelector

​ 从其继承的接口我们可以看到,除了继承DeferredImportSelector接口以外,还继承了一系列的Aware接口。

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {}

​ 当使用Import注解引入AutoConfigurationImportSelector类时,其selectImport()方法会被调用执行其实现的自动装配逻辑。在这之前,会先调用实现的Aware接口的方法。selectImports是ImportSelector接口的唯一函数方法。

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    	//检查自动配置功能是否开启
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
    	//封装被引入的自动配置信息
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    	//返回满足条件的配置类的全限定名数组
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

​ selectImports中又调用了getAutoConfigurationEntry()方法,给容器批量导入相应的组件。

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
    	//加载META-INF目录下的spting.factories文件中的自动配置类
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    	//对获得的类进行去重处理
		configurations = removeDuplicates(configurations);
    	//获得注解中被exclude和excludeName所排除的类集合
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
    	//从自动配置类集合中去除被排除的类
		configurations.removeAll(exclusions);
    	//将筛选完成的配置类和排查的配置类构建为事件类,并传入监听器
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
}

​ getCandidateConfigurations()方法通过SpringFactoriesLoader的loadFactoryNames()方法加载类路径中META-INF目录下spring.factories文件中针对EnableAutoConfiguration的注册配置类。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
}
(3)SpringFactoriesLoader
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

​ SpringFactoriesLoader中的loadFactoryNames()方法又会调用loadSpringFactories()方法,加载得到所有的组件。该方法调用getResources()方法传入参数FACTORIES_RESOURCE_LOCATION ,该参数是字符串常量其值为**“META-INF/spring.factories”**。key为接口的全类名,value是对应配置值的List集合。

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
            //获取资源文件的位置
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

​ 虽然127个组件自动配置启动的时候默认全部加载,但按照条件装配规则(@Conditional及其衍生注解),最终会按需配置,例如AOP组件中有@ConditionalOnProperty、@ConditionalOnClass、@ConditionalOnMissingClass等条件注解,其中@ConditionalOnClass(Advice.class)就是判断是否存在Advice类,如果没有则对应组件不会被加载。

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(Advice.class)
	static class AspectJAutoProxyingConfiguration {

		@Configuration(proxyBeanMethods = false)
		@EnableAspectJAutoProxy(proxyTargetClass = false)
		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
		static class JdkDynamicAutoProxyConfiguration {

		}

		@Configuration(proxyBeanMethods = false)
		@EnableAspectJAutoProxy(proxyTargetClass = true)
		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
				matchIfMissing = true)
		static class CglibAutoProxyConfiguration {

		}

	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
			matchIfMissing = true)
	static class ClassProxyingConfiguration {

		@Bean
		static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() {
			return (beanFactory) -> {
				if (beanFactory instanceof BeanDefinitionRegistry) {
					BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
					AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
					AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
				}
			};
		}

	}

}

4、自动配置流程总结

(1)注解组成

在这里插入图片描述

​ @SpringBootApplication注解由@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三者组成。

  • @SpringBootConfiguration说明该类是一个配置类,类似于一个xml配置文件。
  • @ComponentScan用于包的扫描,其扫描带有@Component、@Controller、@Repository、@Service的类,将其注册为Bean。
  • @EnableAutoConfiguration是实现自动配置的核心注解,其加载META-INF下spring.factories中注册的各种AutoConfiguration类,当该AutoConfiguration类满足相应的@conditional及其衍生注解的条件时,才会实例化该AutoConfigutation中定义的Bean,并注入到IoC容器中。
(2)自动配置流程

在这里插入图片描述

  • @EnableAutoConfiguration注解的核心注解是Import注解,其传入ImportSelector实现类时会调用该实现类的selectImports方法,该方法会调用getAutoConfigutationEntry()方法。
  • 在这方法中通过调用SpringFactoriesLoader的LoadFactoryNames()方法,该方法又调用了loadSpringFactories()方法去加载类路径中META-INF目录下的spring.factories文件中的自动配置类。
  • getAutoConfigutationEntry()对获得的配置类通过exculde或exculdeName方法进行排除,去除被排除的类。
  • 最终被加载的类又会经过@conditional及其衍生注解的条件筛选才实例化相应的Bean,并注入到IoC容器中

二、SpringApplication实例化

Class that can be used to bootstrap and launch a Spring application from a Java main method.

​ 说完了Main类的注解,我们就得看Main方法中的短短一行代码是如何执行的了。

SpringApplication.run(xxxApplication.class, args);

​ 我们来看run方法的一系列调用情况,我们发现之前的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);
}

​ 所以代码我们可以写成下列这种形式,我们可以看到实际上原来的代码分为两步执行,先传入primarySources参数即带有@SpringBootApplication注解的xxxApplication.class对SpringApplication进行实例化,再调用run方法来启动程序。

new SpringApplication(xxxApplication.class).run(args);

​ SpringApplication的作用就是该节开头引入的那句官方注解,用于从Main方法引导和启动Spring应用程序,具体的启动方法就是run方法。

1、构造方法

​ 我们通过SpringApplication构造方法就能知道相应的实例化流程。

public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}

@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
    	//主要的Bean来源
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    	//Web应用类型推断
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
    	//获取系统配置引导信息【自定义】
		this.bootstrapRegistryInitializers = new ArrayList<>(
				getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    	//加载并初始化ApplicationInitializers及相关实现类
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    	//加载并初始化ApplicationListener及相关实现类
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
}

​ 根据源码,SpringApplication实例化需要经过以下流程:赋值resourceLoader和primarySources成员变量、推断Web应用类型、加载并初始化ApplicationInitializer及相关实现类、加载并初始化ApplicationListener及相关实现类、推断Main方法。

​ 传入的参数有两个ResourceLoader和Class<?>,前者为资源加载的接口,后者为可变参数,需要传入一个Class对象。但只有传入带有@EnableAutoConfiguration标注的Class对象才能开启自动配置。

2、Web应用类型推断

​ 在变量赋值完成后,进行Web应用类型的推断,这里调用了WebApplicationType的deduceFromClasspath方法进行Web应用类型的推断。该方法的推断逻辑如下:

  • REACTIVE类型:当DispatcherHandler存在,但DispatcherServlet和ServletContainer都不存在时,说明当前应用为REACTIVE Web应用。
  • NOME类型:非Web应用,当SERVLET或ConfigurationWebApplicationContext任何一个不存在时,说明当前应用非Web应用。
  • SERVLET类型:当SERVLET或ConfigurationWebApplicationContext都存在时,为SERVLET Web应用。

3、ApplicationContextInitializer加载

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

​ ApplicationContextInitializer是IoC容器的一个回调接口,主要目的是允许用户在ConfigurableApplicationContext使用refresh()方法初始化前,对IoC容器实例做进一步设置或处理。initialize的作用为初始化给定的应用程序上下文,传入的参数限定为ConfigurableApplicationContext的子类。

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
	void initialize(C applicationContext);
}

​ 构造器会调用getSpringFactoriesInstances方法来获取SpringFactories实例,这里传入的参数是ApplicationContextInitializer的class对象。我们从该方法的源码中可以发现,该方法调用了SpringFactoriesLoader的loadFactoryNames()来进一步调用loadSpringFactories()从而获得META-INF/spring.factories文件中注册的对应配置,这里的对应配置是ApplicationContextInitializer的实现类。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
}

​ ApplicationContext初始化器的加载就执行完毕。

4、ApplicationListener加载

​ 接下来进行ApplicationListener的加载。

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

​ ApplicationListener是由应用程序事件监听器实现的接口,用于在ApplicationContext管理Bean生命周期过程中,监听相应的ApplicationEvent事件,当事件发生时ApplicationListener会对事件进行具体的操作。

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
	void onApplicationEvent(E event);
}

​ ApplicationListener的加载同ApplicationContextInitializer一致,调用getSpringFactoriesInstances方法,传入ApplicationListener的Class对象。最终通过调用了SpringFactoriesLoader的loadFactoryNames()来进一步调用loadSpringFactories()从而获得META-INF/spring.factories文件中注册的对应配置,从而完成ApplicationListener的加载。

​ 该监听器可通过传入不同的事件泛型(如ApplicationStartingEvent)从而达到对应用启动的各个流程之间进行操作增强。

5、总结

(1)各组件的作用
  • SpringApplication:用于从Main方法引导和启动Spring应用程序。
  • ApplicationContextInitializer:允许用户在ConfigurableApplicationContext使用refresh()方法初始化前,对IoC容器实例做进一步设置或处理。
  • ApplicationListener:用于在ApplicationContext管理Bean生命周期过程中,监听相应的ApplicationEvent事件,当事件发生时ApplicationListener会对事件进行具体的操作。
(2)SpringApplication实例化流程

在这里插入图片描述

三、run()执行过程

Run the Spring application, creating and refreshing a new {@link ApplicationContext}.

​ run方法主要做了4件事:获取监听器和参数配置、打印Banner信息、创建并初始化容器、监听器发送通知。我们直接上源码。

public ConfigurableApplicationContext run(String... args) {
    	//计时器
		long startTime = System.nanoTime();
    	//系统配置引导信息对应的上下文对象
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
    	//设置系统配置信息headless:模拟输入输出,防止系统报错
		configureHeadlessProperty();
    	//获得SpringApplicationRunListener数组,封装在对象listeners中:获取了当前注册的所有监听器
		SpringApplicationRunListeners listeners = getRunListeners(args);
    	//启动监听
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
            //创建ApplicationArguments对象,获取args参数
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //加载环境属性配置
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
            //打印Banner
			Banner printedBanner = printBanner(environment);
            //根据当前Spring项目类型来创建容器
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
            //准备容器
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            //刷新容器
			refreshContext(context);
            //刷新操作后的执行,默认为空
			afterRefresh(context, applicationArguments);
			Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
			if (this.logStartupInfo) {
                //输出启动信息日志
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
			}
            //通知监听器:容器启动完成
			listeners.started(context, timeTakenToStartup);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}
		try {
			Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
            //通知监听器:容器准备完毕
			listeners.ready(context, timeTakenToReady);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
    	//返回容器
		return context;
	}

1、SpringApplicationRunListener监听器

SpringApplicationRunListeners listeners = getRunListeners(args);

​ SpringApplicationRunListener监听器从字面就可以了解,该监听器用于对SpringApplication对run方法的执行过程进行监听。我们可以自定义SpringApplicationRunListener来执行相关的操作,然后可以在resources目录下创建META-INF,在该目录下创建spring.factories写入自定义SpringApplicationRunListener全限定名。

​ 我们来看该监听器是如何获取的,getRunListeners通过getSpringFactoriesInstances方法进行对SpringApplicationRunListener的获取。

private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger,
				getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
				this.applicationStartup);
}

​ getSpringFactoriesInstances()虽然不能算上很熟悉,但是我们已经见过很多次了,向其传入SpringApplicationRunListener,然后通过SpringFactoriesLoader的loadFactoryNames进一步调用loadSpringFactories()从而获得META-INF/spring.factories文件中注册的对应配置。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
}

​ SpringApplicationRunListener相应的监听类型/方法如下,会在围绕bootstrapContext和ConfigurableApplicationContext生命周期进行事件监听。

//"spring.boot.application.starting"
void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) 

//"spring.boot.application.environment-prepared"
void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment)

//"spring.boot.application.context-prepared"
void contextPrepared(ConfigurableApplicationContext context)

//"spring.boot.application.context-loaded"
void contextLoaded(ConfigurableApplicationContext context)
    
//"spring.boot.application.started"
void started(ConfigurableApplicationContext context, Duration timeTaken)
 
//"spring.boot.application.ready"
void ready(ConfigurableApplicationContext context, Duration timeTaken)
    
//"spring.boot.application.failed"
void failed(ConfigurableApplicationContext context, Throwable exception)

2、初始化ApplicationArguments

​ ApplicationArguments是应用参数,用于提供访问允许SpringApplication时的参数。

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

​ 这里传入的args来自main方法,即将main方法传入的参数封装成为Source对象,然后封装成为ApplicationArguments对象。

3、初始化ConfigutableEnvironment

​ 完成ApplicationArguments参数准备后,就开始准备可配置环境ConfigutableEnvironment,该接口的主要作用是提供当前运行环境的公开接口。

ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

4、Spring应用上下文创建、准备加载与刷新

(1)创建

​ ApplicationContext的创建通过createApplicationContext()方法完成。

context = createApplicationContext();

​ 该方法会根据webApplicationType,即Spring应用类型不同来进行创建。

protected ConfigurableApplicationContext createApplicationContext() {
	return this.applicationContextFactory.create(this.webApplicationType);
}

​ 这里的类型是我们在SpringApplication实例化的过程中通过deduceFromClasspath()进行判断赋值的。

this.webApplicationType = WebApplicationType.deduceFromClasspath();
  • Web应用对应AnnotationConfigServletWebServerApplicationCaontext

  • 普通应用对应AnnotationConfigApplicationContext

(2)准备和加载

​ 创建完context后就需要对容器进行准备和加载工作,通过prepareContext()来完成。

prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

​ prepareContext()源码如下。

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
    	//设置上下文配置环境
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
    	//在context刷新前,执行ApplicationcontextInitializer,初始化context
		applyInitializers(context);
    	//通知监听器,context准备完成
		listeners.contextPrepared(context);
		bootstrapContext.close(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		//获得ConfigutableListableBeanFactory并注册单例对象
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
			((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
			if (beanFactory instanceof DefaultListableBeanFactory) {
				((DefaultListableBeanFactory) beanFactory)
						.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
			}
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		//获取全部配置源,其中包含primarySource【自动配置实现注册】和sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
    	//将sources中的Bean加载到context中
		load(context, sources.toArray(new Object[0]));
    	//通知监听器context加载完成
		listeners.contextLoaded(context);
}

准备过程分为三步:

  • 对context设置environment
  • 应用上下文后置处理
  • ApplicationContextInitializer初始化Context:这里的ApplicationContextInitializer就是在SpringApplication实例化过程中通过SpringFactoriesLoader加载的META-INF下的spring.factories对应配置,也可以通过自定义实现。

加载过程分为五步:

  • 打印日志和Profile的设置
  • 设置是否允许覆盖注册
  • 获取全部配置源
  • 将配置源加载入上下文
  • 通知监控器context加载完成

(3)刷新
refreshContext(context);

​ 这里的refresh主要调用的是AbstractApplicationContext的refresh()方法,该方法通过Resource定位、BeanDefinition载入和注册来完成IoC容器的初始化。

private void refreshContext(ConfigurableApplicationContext context) {
		if (this.registerShutdownHook) {
			shutdownHook.registerApplicationContext(context);
		}
		refresh(context);
}

protected void refresh(ConfigurableApplicationContext applicationContext) {
		applicationContext.refresh();
}

​ 至此,Spring上下文开启,Spring Boot正式开始运行。

四、总结

在这里插入图片描述

SpringBoot的启动可以分为三个部分:

  • @springBootApplication注解:@springBootApplication注解其实是三个注解的组合,即@ComponentScan、@EnableAutoConfiguration、@SpringBootConfiguration
    • @ComponentScan:用于包的扫描,我们定义的@Controller、@Service、@Repository、@Component都通过该注解进行注册
    • @SpringBootConfiguration:是一个Configuration注解,其代表我们的启动类是一个配置类
    • @EnableAutoConfiguration:是自动配置的核心,其组合了@Import注解,该注解传入了一个AutoConfigurationImportSelector类的对象,会在容器时调用该类的selectImport()方法,该方法最终会调用SpringFactoriesLoader的loadSpringFactories(),该方法加载了META-INF下的spring.factories,该文件包含了需要自动配置的各种bean组件,组件类中又通过Conditianal注解进行选择性配置。
  • SpringApplication实例化:
    • resourceLoader和primarySource的赋值,这里的primarySource就是我们被@EnableAutoConfiguration注解的启动类对象
    • 应用类型的判断和赋值:通过deducaFromClasspath()来判断该SpringBoot应用是什么类型,该参数用于确定创建什么类型的IoC容器。Web应用对应AnnotationConfigServletWebServerApplicationCaontext,普通应用对应AnnotationConfigApplicationContext
    • ApplicationInitializer和ApplicationListener的加载:这里也会通过getSpringFactoriesInstances()方法去通过SpringFactoriesLoader的loadSpringFactories()加载META-INF下的spring.factories,得到相应的Initializer和Listener。我们也可以自定义Initializer和Listener,并写到spring.factories中。Initializer用于对ConfigurableApplicationContext使用refresh()方法初始化前,对IoC容器实例做进一步设置或处理。Listener用于在run方法执行的各个阶段,根据不同的事件增强不同的行为。
  • run方法的执行:run方法执行可分为两部分,容器创建前的准备工作和容器创建工作。
    • 准备工作
      • 加载计时器,用于记录SpringBoot的启动时间
      • 加载监听器,加载了所有默认的和定义了的监听器,在容器创建过程中的不同阶段事件进行执行
      • 加载Arguments参数和环境参数
      • 打印Banner
    • 容器创建工作
      • 根据之前的webApplicationType来创建对应的容器
      • 通过prepareContext()进行容器的准备工作,该方法会调用applyInitializers(),启动之前加载的Initializer对IoC容器实例做进一步设置或处理
      • refreshContext()会启动容器的refresh()工作,该方法会对实例化的容器进行刷新,会经过Resource定位、BeanDefinition载入和注册完成容器的初始化
      • 初始化完成后会调用afterRefresh进行初始化结束后的一些操作执行
      • 最后会通过return返回创建的容器
  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未来村村长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值