大家好!我是未来村村长,就是那个“请你跟我这样做,我就跟你这样做!”的村长👨🌾!
||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返回创建的容器
- 准备工作