SpringBoot源码探险 —— SpringBoot启动流程详解

一,SpringBoot启动流程

本人使用的SpringBootParent版本为

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.1</version><relativePath/>
</parent>

前言

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

上面是一段朴实无华的Spring项目的启动函数,看似简简单单的几行代码却实现了无数项目的启动。对于我本人来说最喜欢极简的代码和快速简单的启动,因为一个项目的快速启动可以让初学者更快的融入项目世界,体验项目的乐趣和魅力。话不扯远了,我们一起来深入SpringBoot的启动流程。

1. SpringApplication构建

SpringApplication.run(SpringMainApplication.class, args);
->
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {return (new SpringApplication(primarySources)).run(args);
}

我们给run 方法打上断点之后,会进行如下图所示的一个流程,我们可以清楚的看到一个 new SpringApplication(),很显然这个就是初始化SpringApplication的方法,我们接着往下深入。

① SpringApplication初始化

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {//Spring 应用程序的源this.sources = new LinkedHashSet();  //横幅模式,用于表示应用程序启动时控制台输出横幅的显示方式this.bannerMode = Mode.CONSOLE;		//是否记录应用程序启动信息。默认为 true,表示在应用程序启动时记录启动信息;可以设置为 false 来禁用启动信息的记录。this.logStartupInfo = true;			//是否添加命令行参数。默认为 true,表示将命令行中的参数添加到应用程序的环境属性中;可以设置为 false 来禁用该功能。this.addCommandLineProperties = true; //是否添加类型转换服务。默认为 true,表示在应用程序上下文中添加类型转换服务;可以设置为 false 来禁用该功能。this.addConversionService = true;     //是否为无头应用程序。默认为 true,表示应用程序在没有图形界面的环境下运行;可以设置为 false 来禁用无头模式。this.headless = true;            //是否注册关闭钩子。默认为 true,表示在应用程序关闭时注册一个 JVM 关闭钩子;可以设置为 false 来禁用该功能this.registerShutdownHook = true;//附加的配置文件激活的名称集合。默认为空集合,表示没有附加的配置文件激活。this.additionalProfiles = Collections.emptySet();//是否自定义环境。默认为 false,表示不使用自定义的环境;可以设置为 true 来使用自定义的环境。this.isCustomEnvironment = false;//是否延迟初始化。默认为 false,表示不延迟初始化;可以设置为 true 来启用延迟初始化。this.lazyInitialization = false;//应用程序上下文工厂。默认为 ApplicationContextFactory.DEFAULT,表示使用默认的应用程序上下文工厂。this.applicationContextFactory = ApplicationContextFactory.DEFAULT;//应用程序启动对象。默认为 ApplicationStartup.DEFAULT,表示使用默认的应用程序启动对象。this.applicationStartup = ApplicationStartup.DEFAULT;this.resourceLoader = resourceLoader;//将主启动类设置到集合中this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));//(重要✨) 设置应用类型this.webApplicationType = WebApplicationType.deduceFromClasspath();/** 初始化引导程序,Bootstrappers(引导程序)是一组用于初始化 Spring 应用程序上下文的策略,它们负责在 Spring 应用程序上下文创建之前执行一些初始化任务。	**/this.bootstrappers = new ArrayList(this.getSpringFactoriesInstances(Bootstrapper.class));//(重要✨) 设置初始化器this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));//(重要✨)初始化监听器this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));/**决定主启动类:根据当前的运行堆栈,找到主方法为main的stack,获取当前的启动类类型,例如SpringMainApplication启动,就是例如	SpringMainApplication.class**/this.mainApplicationClass = this.deduceMainApplicationClass();
}

📌参数讲解

  • ResourceLoader:资源加载器,用于加载配贼文件,模板文件等资源。为SpringBoot应用程序加载外部资源提供帮助

可以看到初始化除了对于SpringBoot一些参数的设置以外,还进行了三个阶段:设置应用类型设置初始化器初始化监听器。这些阶段十分重要,接下来我们来花费一些笔墨来讲解一下这三个阶段。

② SpringBoot应用类型设置

public enum WebApplicationType {NONE,SERVLET,REACTIVE;
}

进入WebApplicationType类中,我们可以发现他是一个枚举类,它对应着SpringBoot应用类型:

  • **NONE:**什么都没有,正常流程走,不额外的启动 web容器
  • **SERVLET:**基于 servlet 的web程序,需要启动内嵌的 servlet web容器
  • **REACTIVE:**基于 reactive 的web程序,需要启动内嵌 reactive web容器
private static final String[] SERVLET_INDICATOR_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";static WebApplicationType deduceFromClasspath() {//判断是否为REACTIVE程序if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {return REACTIVE;} else {//获取所有SERVLET加载类String[] var0 = SERVLET_INDICATOR_CLASSES;int var1 = var0.length;//不断遍历,如果有其中一个未加载,则为NONE类型for(int var2 = 0; var2 < var1; ++var2) {String className = var0[var2];if (!ClassUtils.isPresent(className, (ClassLoader)null)) {return NONE;}}return SERVLET;}
}

他会根据当前项目加载的类来进行判断你属于哪一个,比如当你导入了spring-boot-starter-web就会发现你导入了DispatcherServlet类,从而选择SERVLET类型。

③ 设置初始化器

什么是初始化器(ApplicationContextInitializer)

ApplicationContextInitalizer他只包含一个void initialize(C var1)方法,ApplicationContextInitializer 接口用于在 Spring 容器刷新之前执行的一个回调函数,通常用于向 SpringBoot 容器中注入属性。通常我们可以实现该接口进行一些自定义的初始化或者属性设置,具体内容就不详细介绍了可以看这篇文章ApplicationContextInitializer的理解和使用

 this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); //获取所有初始化器实例并进行设置
//获取SpringFactories文件中的实例
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {ClassLoader classLoader = this.getClassLoader();	//获取类加载器Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //获取初始化器的全类名称List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); //将初始化器进行实例化AnnotationAwareOrderComparator.sort(instances);	//根据实例的优先级进行排序return instances;
}

初始化器的名称从何而来?

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {/**省略以下代码**/Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");/**省略以下代码**/
}

image

我们通过SpringFactoriesLoader.loadFactoryNames(type, classLoader)代码不断向下就可以发现,其实初始化器的名称存放在SpringBoot源码下的META-INF的spring.factories中,初始化器就从spring.factories中获取

一些初始化器

image

  1. ConfigurationWarningsApplicationContextInitializer:该初始化器用于检测应用程序的配置问题并发出警告。它会扫描应用程序的配置,并检查其中的一些潜在问题,例如未使用的配置属性或不推荐的配置选项。
  2. ContextIdApplicationContextInitializer:为应用程序上下文设置一个唯一的 ID。该 ID 可以通过 ApplicationContext.getId() 方法获取。通常情况下,这个 ID 是应用程序的名称,可以用于在日志中标识不同的应用程序实例。
  3. DelegatingApplicationContextInitializer:该初始化器是一个委托初始化器,它负责委托给其他应用程序上下文初始化器进行处理。
  4. RSocketPortInfoApplicationContextInitializer:该初始化器用于设置 RSocket 通信端口信息。如果应用程序使用 RSocket 进行通信,这个初始化器会设置 RSocket 通信端口的相关信息。
  5. ServerPortInfoApplicationContextInitializer:该初始化器用于设置 Web 服务器的端口信息。它会将 Web 服务器的端口号添加到应用程序的环境属性中,以便应用程序可以获取并使用这些信息。
  6. SharedMetadataReaderFactoryContextInitializer:该初始化器用于共享元数据读取工厂。它确保只有一个元数据读取工厂实例,以提高性能和避免重复初始化。
  7. ConditionEvaluationReportLoggingListener:该初始化器是一个监听器,用于记录条件评估报告。它会记录 Spring Boot 自动配置过程中各种条件的评估结果,以便开发者可以了解自动配置的详细情况和结果。

④ 设置监听器

什么是监听器( ApplicationListener)

用于监听特定的事件(ApplicationEvent),比如IOC容器刷新,容器关闭等等。

this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); //获取所有监听器实例并进行设置

从这里我们可以发现获取监听器实例的函数和获取初始化器是一样的,再次就不多赘述。

一些监听器

image

  1. **EnvironmentPostProcessorApplicationListener:**监听 EnvironmentPostProcessor 的实现类,并在 Spring 环境加载之后调用它们的 postProcessEnvironment 方法,以允许对环境进行后处理。
  2. **AnsiOutputApplicationListener:**监听对 ANSI 输出的相关配置,并根据配置情况设置控制台的 ANSI 输出格式,以支持彩色日志输出。
  3. **LoggingApplicationListener:**配置日志系统,包括设置日志级别、输出格式等。它会根据应用程序的配置文件中的日志配置来初始化日志系统,通常使用 Logback 作为日志实现。
  4. **BackgroundPreinitializer:**在应用程序上下文创建之前异步地进行一些预初始化操作,以提高应用程序的启动速度。这些预初始化操作通常包括加载外部配置文件、准备类路径等。
  5. **DelegatingApplicationListener:**委托给其他应用程序监听器进行处理。它会将 Spring Boot 应用程序上下文中注册的所有监听器作为参数传递给它,并按顺序调用它们。
  6. **ParentContextCloserApplicationListener:**在父应用程序上下文关闭时,关闭所有子应用程序上下文。在 Spring Boot 多模块应用程序中,可能会存在多个应用程序上下文的层次结构,该监听器确保在父上下文关闭时,所有子上下文也会被关闭。
  7. **ClearCachesApplicationListener:**在应用程序上下文刷新之前清除缓存。这包括清除 Spring 的资源缓存和类加载器缓存,以确保在应用程序启动过程中不会使用过期或不正确的缓存数据。
  8. **FileEncodingApplicationListener:**监听文件编码的相关配置,并将配置的文件编码设置为应用程序的默认文件编码。这是为了确保在读写文件时使用正确的编码格式。
  9. **LiquibaseServiceLocatorApplicationListener:**在 Spring Boot 应用程序上下文刷新之前,用于加载 Liquibase 的服务定位器。Liquibase 是一个用于数据库变更管理的工具,该监听器负责在应用程序启动时初始化 Liquibase。

值得注意的一点是,初始化器和监听器,必须在run之前添加入Spring环境中,或者提前写在spring.factories文件中,否制无法生效

流程图

image

2. 执行run方法

public ConfigurableApplicationContext run(String... args) {//一个计时器,用于计时代码执行的耗时StopWatch stopWatch = new StopWatch();stopWatch.start();//启动引导程序,也就是之前的BootStrappers,构建Bootsrap上下文容器(如果没有设置一般BootStrappers为空)DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();ConfigurableApplicationContext context = null;//java.awt.headless是J2SE的一种模式用于在缺少显示屏、键盘或者鼠标时的系统配置,很多监控工具如jconsole 需要将该值设置为true,系统变量默认为truethis.configureHeadlessProperty();//(重要🌟)获取spring.factories中的监听器变量,EventPublishingRunListener SpringApplicationRunListeners listeners = this.getRunListeners(args);//启动运行时监听器listeners.starting(bootstrapContext, this.mainApplicationClass);try {// 命令行参数包装为了ApplicationArgumentsApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//(重要🌟)准备好应用的ENV环境ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);this.configureIgnoreBeanInfo(environment);// 打印Spring Boot的大大的Logo (水印😅Banner printedBanner = this.printBanner(environment);//(重要🌟)创建IOC容器context = this.createApplicationContext();//setApplicationStartup 方法将指定的 ApplicationStartup 对象设置到应用程序的上下文环境中,以便在应用程序启动时使用。这样一来,应用程序就可以使用指定的 ApplicationStartup 对象来记录启动事件和性能指标,以便进行后续的分析和优化。context.setApplicationStartup(this.applicationStartup);//(重要🌟)IOC容器前置处理this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);//(重要🌟)IOC容器刷新操作this.refreshContext(context);//(重要🌟)IOC容器后置操作this.afterRefresh(context, applicationArguments);//停止计时并且输出初始化耗时时间stopWatch.stop();if (this.logStartupInfo) {(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);}//listeners发布started事件消息listeners.started(context);//执行所有的Runner方法this.callRunners(context, applicationArguments);} catch (Throwable var10) {this.handleRunFailure(context, var10, listeners);throw new IllegalStateException(var10);}try {//listeners发布running事件消息listeners.running(context);return context;} catch (Throwable var9) {this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);throw new IllegalStateException(var9);}
}

① 启动引导程序(可略过

private DefaultBootstrapContext createBootstrapContext() {DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();this.bootstrappers.forEach((initializer) -> {initializer.intitialize(bootstrapContext);});return bootstrapContext;
}

循环启动引导程序,并初始化bootstrapContext容器,但是一般程序员不自己设置,基本上是没有bootstrapper的,所以可以直接略过

② 启动运行时监听器

image

在上面代码中,我们可以看到有一个SpringApplicationRunListeners,这个监听器和我们初始化时的监听器不同,这个是运行时监听器,用于监听应用程序启动过程的,我们可以通过debug得知他注入了一个EventPublishingRunListener,我们现在来看看这个EventPublishingRunListener是什么。

public EventPublishingRunListener(SpringApplication application, String[] args) {this.application = application;this.args = args;//事件广播器this.initialMulticaster = new SimpleApplicationEventMulticaster();//获取当前应用所有的监听器,使用迭代器模式Iterator var3 = application.getListeners().iterator();while(var3.hasNext()) {ApplicationListener<?> listener = (ApplicationListener)var3.next();//将监听器添加至事件广播器中this.initialMulticaster.addApplicationListener(listener);}}
什么是事件广播器(SimpleApplicationEventMulticaster)

SimpleApplicationEventMulticaster 是 Spring Framework 提供的一个简单的应用事件多播器,用于管理和分发应用程序中的事件。在 Spring 应用程序中,事件是通过 ApplicationEvent 及其子类表示的,而事件的发布和监听是通过事件多播器来实现的。

  • addApplicationListener:注册事件监听器
  • removeApplicationListener:移除事件监听器
  • multicastEvent:分发监听事件给事件监听器,使其接收并处理事务(他是支持异步广播的)
EventPublishingRunListener 启动
public void starting(ConfigurableBootstrapContext bootstrapContext) {this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);Executor executor = this.getTaskExecutor();	//可以用线程池进行异步广播Iterator var5 = this.getApplicationListeners(event, type).iterator();//遍历Listener交给Listener去处理该事件while(var5.hasNext()) {ApplicationListener<?> listener = (ApplicationListener)var5.next();//如果有任务线程池if (executor != null) {//异步处理事件executor.execute(() -> {this.invokeListener(listener, event);});} else {//处理事件this.invokeListener(listener, event);}}
}
------->
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {try {//Listener处理事件listener.onApplicationEvent(event);} catch (ClassCastException var6) {//省略一堆代码}
}

在这里我们知道,启动EventPublishingRunListener 监听器实际上是进行了一次ApplicationStartingEvent事件的广播,在事件广播器中找到对应的Listener去处理该事件,虽然我们在这里看到的是while来交给所有Listener处理事件,但其实不同Listener的onApplicationEvent()会对事件进行判断然后再进行处理。

处理ApplicationStartingEvent的监听器

具体干了些什么的可以看上面监视器的功能

  • AnsiOutputApplicationListener
  • LoggingApplicationListener
  • BackgroundPreinitializer

③ 准备运行环境

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {//environment是一个单例模式,根据你的应用类型获取对应的运行环境。ConfigurableEnvironment environment = this.getOrCreateEnvironment();//这一步会将系统环境中的属性加载到应用程序的环境中,使得应用程序能够使用系统环境中的变量。this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());ConfigurationPropertySources.attach((Environment)environment);//运行时监听器发布环境已准备通知listeners.environmentPrepared(bootstrapContext, (ConfigurableEnvironment)environment);//将默认属性源移到属性源列表的末尾。默认属性源包含了应用程序的默认配置信息,通过将其移到末尾,可以确保其他属性源的优先级更高。DefaultPropertiesPropertySource.moveToEnd((ConfigurableEnvironment)environment);this.configureAdditionalProfiles((ConfigurableEnvironment)environment);//将环境绑定到 Spring 应用程序。这一步确保应用程序能够使用正确的环境配置进行初始化和运行。this.bindToSpringApplication((ConfigurableEnvironment)environment);//如果是自定义环境,将环境转换为自定义环境。这一步确保应用程序使用正确类型的环境进行初始化和运行。if (!this.isCustomEnvironment) {environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());}ConfigurationPropertySources.attach((Environment)environment);return (ConfigurableEnvironment)environment;
}
根据类型获取环境
  • SERVLET:StandardServletEnvironment
  • REACTIVE:StandardReactiveWebEnvironment
  • 其他环境:StandardEnvironment
加载并设置环境参数
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {//addConversionService如果为true,添加类型转换服务if (this.addConversionService) {ConversionService conversionService = ApplicationConversionService.getSharedInstance();environment.setConversionService((ConfigurableConversionService)conversionService);}//获取系统环境参数this.configurePropertySources(environment, args);//这个函数里面什么都没有this.configureProfiles(environment, args);
}

获取系统环境参数

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {//获取各种系统环境参数MutablePropertySources sources = environment.getPropertySources();
}

image

运行时监听器发布环境已准备通知
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}

该方法会发布一个 ApplicationEnvironmentPreparedEvent 事件,这是启动之后发布的第二个事件EnvironmentPostProcessorApplicationListener 会接受到该任务并进行处理。

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {ConfigurableEnvironment environment = event.getEnvironment();SpringApplication application = event.getSpringApplication();//获取所有的环境后处理器EnvironmentPostProcessorIterator var4 = this.getEnvironmentPostProcessors(event.getBootstrapContext()).iterator();while(var4.hasNext()) {EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var4.next();//进行环境处理器处理postProcessor.postProcessEnvironment(environment, application);}
}

加载的环境处理器

  1. RandomValuePropertySourceEnvironmentPostProcessor:该后处理器负责添加一个随机属性源 (RandomValuePropertySource) 到应用程序的环境中。这个属性源包含了一些随机生成的属性,例如随机端口号等。这些随机属性可以在应用程序中用于配置或其他目的。
  2. SystemEnvironmentPropertySourceEnvironmentPostProcessor:该后处理器负责添加系统环境属性源 (SystemEnvironmentPropertySource) 到应用程序的环境中。这个属性源包含了操作系统的环境变量,可以在应用程序中使用这些环境变量进行配置。
  3. SpringApplicationJsonEnvironmentPostProcessor:该后处理器负责加载 Spring Boot 的配置文件(application.json)并将其转换为属性源添加到应用程序的环境中。这个后处理器使得开发者可以使用 JSON 格式的配置文件来配置应用程序。
  4. CloudFoundryVcapEnvironmentPostProcessor:该后处理器负责解析 Cloud Foundry 的环境变量并将其添加到应用程序的环境中。Cloud Foundry 是一个流行的云平台,这个后处理器可以使得应用程序能够在 Cloud Foundry 上运行,并与 Cloud Foundry 的服务进行集成。
  5. ConfigDataEnvironmentPostProcessor:该后处理器负责加载外部配置数据 (ConfigData) 并将其合并到应用程序的环境中。从例如application.properties或者application.yaml中获取属性
  6. DebugAgentEnvironmentPostProcessor:该后处理器负责启动 Reactor 调试代理。Reactor 是一个用于构建反应式应用程序的框架,这个后处理器允许开发者在应用程序运行时连接到 Reactor 调试代理进行调试。

application.yaml文件加载(待补充)

从上面的处理器可知,application.yml文件中的属性加载是由ConfigDataEnvironmentPostProcessor进行的,那我们来看看其中的代码

void postProcessEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles) {try {this.logger.trace("Post-processing environment to add config data");ResourceLoader resourceLoader = resourceLoader != null ? resourceLoader : new DefaultResourceLoader();//获取ConfigDataEnviroment并进行处理和应用this.getConfigDataEnvironment(environment, (ResourceLoader)resourceLoader, additionalProfiles).processAndApply();} catch (UseLegacyConfigProcessingException var5) {this.logger.debug(LogMessage.format("Switching to legacy config file processing [%s]", var5.getConfigurationProperty()));this.postProcessUsingLegacyApplicationListener(environment, resourceLoader);}}

④ 创建IOC容器

context = this.createApplicationContext();
->
protected ConfigurableApplicationContext createApplicationContext() {return this.applicationContextFactory.create(this.webApplicationType);
}
->
ApplicationContextFactory DEFAULT = (webApplicationType) -> {try {//根据不同的web应用类型创建不同的IOC容器switch (webApplicationType) {case SERVLET:return new AnnotationConfigServletWebServerApplicationContext();case REACTIVE:return new AnnotationConfigReactiveWebServerApplicationContext();default:return new AnnotationConfigApplicationContext();}} catch (Exception var2) {throw new IllegalStateException("Unable create a default ApplicationContext instance, you may need a custom ApplicationContextFactory", var2);}
};
->
public AnnotationConfigServletWebServerApplicationContext() {this.annotatedClasses = new LinkedHashSet();//注解Bean读取类this.reader = new AnnotatedBeanDefinitionReader(this);//路径Bean扫描类this.scanner = new ClassPathBeanDefinitionScanner(this);
}

IOC的创建实则是由ApplicationContextFactory创建的,他负责创建对应类型的IOC容器,AnnotationConfigServletWebServerApplicationContext他会包含一个注解Bean读取类和路径Bean扫描类

⑤ 前置IOC容器操作

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {//设置容器环境,自定义配置和系统配置context.setEnvironment(environment);/**执行后置处理操作1.查看是否配置了beanNameGenerator,如果配置了类名生成器则将其作为bean注入到容器中2.查看资源加载器是否为空(一般为空),不为空则为IOC进行资源加载器设置3.根据addConversionService,添加对象类型转换服务ConversionService**/this.postProcessApplicationContext(context);//执行所有初始化器 ApplicationContextInitializerthis.applyInitializers(context);//广播容器准备完成工作,触发监听器listeners.contextPrepared(context);bootstrapContext.close(context);if (this.logStartupInfo) {this.logStartupInfo(context.getParent() == null);this.logStartupProfileInfo(context);}ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();//将启动参数作为Bean注入到容器中beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {//将Banner作为Bean注入到容器中beanFactory.registerSingleton("springBootBanner", printedBanner);}if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}if (this.lazyInitialization) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}//获取启动类指定的参数Set<Object> sources = this.getAllSources();Assert.notEmpty(sources, "Sources must not be empty");//将启动类注入至IOC容器中this.load(context, sources.toArray(new Object[0]));//广播context-loaded事件,即context容器已经被加载完毕listeners.contextLoaded(context);
}
执行所有初始化器
protected void applyInitializers(ConfigurableApplicationContext context) {Iterator var2 = this.getInitializers().iterator();while(var2.hasNext()) {ApplicationContextInitializer initializer = (ApplicationContextInitializer)var2.next();initializer.initialize(context);}}

没啥好说的看代码能看的懂,就是for循环获取initializer然后进行初始化

启动类注入IOC
private void load(Class<?> source) {//springBoot会优先选择groovy加载方式,找不到再选用java方式。if (this.isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {GroovyBeanDefinitionSource loader = (GroovyBeanDefinitionSource)BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);((GroovyBeanDefinitionReader)this.groovyReader).beans(loader.getBeans());}//如果符合加载条件,就会将该类型注册到 annotatedReader 中。这里的 annotatedReader 可能是一个 BeanDefinitionReader,用于读取和解析注解标注的 Bean 定义。if (this.isEligible(source)) {this.annotatedReader.register(new Class[]{source});}}

此时SpringBoot项目的第一个由用户注入的类诞生了,他就是你的启动类,他被注入到了 beanDefinitionMap

image

什么是beanDefinitionMap

是一个 Spring 中用于保存 Bean 定义的数据结构,它是一个 Map<String, BeanDefinition>,其中键是 Bean 的名称,值是对应的 BeanDefinition 对象。在 Spring 应用程序上下文中,会维护一个全局的 beanDefinitionMap,用于保存所有注册的 Bean 定义。这个 Map 会在 Spring 容器启动时被初始化,并在容器运行期间被动态地更新。

Applictaion-Prepared事件广播

listeners.contextLoaded(context);该代码会进行一次context-load步骤广播它对应的事件类型是Applictaion-Prepared,此时一些Listener会进行一些操作

  • 给IOC容器注册一些日志类,日志系统,日志组
private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {ConfigurableListableBeanFactory beanFactory = event.getApplicationContext().getBeanFactory();if (!beanFactory.containsBean("springBootLoggingSystem")) {beanFactory.registerSingleton("springBootLoggingSystem", this.loggingSystem);}if (this.logFile != null && !beanFactory.containsBean("springBootLogFile")) {beanFactory.registerSingleton("springBootLogFile", this.logFile);}if (this.loggerGroups != null && !beanFactory.containsBean("springBootLoggerGroups")) {beanFactory.registerSingleton("springBootLoggerGroups", this.loggerGroups);}}
  • EnvironmentPostProcessorListener执行finish方法()

⑥ 刷新容器操作

public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {/*** 刷新上下文环境*/prepareRefresh();/*** 初始化BeanFactory,解析XML,相当于之前的XmlBeanFactory的操作,*/ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();/*** 为上下文准备BeanFactory,即对BeanFactory的各种功能进行填充,如常用的注解@Autowired @Qualifier等* 设置SPEL表达式#{key}的解析器* 设置资源编辑注册器,如PerpertyEditorSupper的支持* 添加ApplicationContextAwareProcessor处理器* 在依赖注入忽略实现*Aware的接口,如EnvironmentAware、ApplicationEventPublisherAware等* 注册依赖,如一个bean的属性中含有ApplicationEventPublisher(beanFactory),则会将beanFactory的实例注入进去*/prepareBeanFactory(beanFactory);try {/*** 提供子类覆盖的额外处理,即子类处理自定义的BeanFactoryPostProcess*/postProcessBeanFactory(beanFactory);/*** 激活各种BeanFactory处理器,包括BeanDefinitionRegistryBeanFactoryPostProcessor和普通
的BeanFactoryPostProcessor* 执行对应的postProcessBeanDefinitionRegistry方法 和 postProcessBeanFactory方法*/invokeBeanFactoryPostProcessors(beanFactory);/*** 注册拦截Bean创建的Bean处理器,即注册BeanPostProcessor,不是
BeanFactoryPostProcessor,注意两者的区别* 注意,这里仅仅是注册,并不会执行对应的方法,将在bean的实例化时执行对应的方法*/registerBeanPostProcessors(beanFactory);/*** 初始化上下文中的资源文件,如国际化文件的处理等*/initMessageSource();/*** 初始化上下文事件广播器,并放入applicatioEventMulticaster,如
ApplicationEventPublisher*/initApplicationEventMulticaster();/*** 给子类扩展初始化其他Bean*/onRefresh();/*** 在所有bean中查找listener bean,然后注册到广播器中*/registerListeners();/*** 设置转换器* 注册一个默认的属性值解析器* 冻结所有的bean定义,说明注册的bean定义将不能被修改或进一步的处理* 初始化剩余的非惰性的bean,即初始化非延迟加载的bean*/finishBeanFactoryInitialization(beanFactory);/*** 通过spring的事件发布机制发布ContextRefreshedEvent事件,以保证对应的监听器做进一步的
处理* 即对那种在spring启动后需要处理的一些类,这些类实现了
ApplicationListener<ContextRefreshedEvent>,* 这里就是要触发这些类的执行(执行onApplicationEvent方法)* 另外,spring的内置Event有ContextClosedEvent、ContextRefreshedEvent、
ContextStartedEvent、ContextStoppedEvent、RequestHandleEvent* 完成初始化,通知生命周期处理器lifeCycleProcessor刷新过程,同时发出
ContextRefreshEvent通知其他人*/finishRefresh();}
finally {resetCommonCaches();}}
}

这个地方过于复杂,就给个注解稍微讲一下,之后会在IOC容器源码中介绍。

⑦ IOC容器的后置处理

//等待用户重写和扩展
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}

⑧ 发布started事件

public void started(ConfigurableApplicationContext context) {context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
}

我们可以看到这一次的事件发布处理是不同的,这里是在IOC容器中发布事件,并进行处理的,也就是说在之后通过注解被注入Listener也可以进行事件消费,在之前的事件都只能由spring.factors文件中注册的Listener来处理。

执行Runners

private void callRunners(ApplicationContext context, ApplicationArguments args) {List<Object> runners = new ArrayList();//获取容器中所有的Runner,遍历执行runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());AnnotationAwareOrderComparator.sort(runners);Iterator var4 = (new LinkedHashSet(runners)).iterator();while(var4.hasNext()) {Object runner = var4.next();if (runner instanceof ApplicationRunner) {this.callRunner((ApplicationRunner)runner, args);}if (runner instanceof CommandLineRunner) {this.callRunner((CommandLineRunner)runner, args);}}
}

流程图

image

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/766616.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vsto worksheet中查找关键字【关键字】获取对应的整列 union成一个range

要在 VSTO 中的工作表中查找包含特定关键字的单元格&#xff0c;并将这些单元格所在列合并为一个范围&#xff0c;可以使用以下代码&#xff1a;csharp using Excel Microsoft.Office.Interop.Excel;// 在工作表中查找包含特定关键字的单元格&#xff0c;并返回这些单元格所在…

HTML世界之标签Ⅶ

目录 一、source 标签 二、span 标签 三、strong 标签 四、style 标签 五、sub 标签 六、summary 标签 七、sup 标签 八、textarea 标签 九、template 标签 十、time 标签 十一、title 标签 十二、track 标签 十三、video 标签 十四、wbr 标签 一、source 标签 …

计算机网络——26通用转发和SDN

通用转发和SDN 网络层功能&#xff1a; 转发&#xff1a; 对于从某个端口 到来的分组转发到合适的 输出端口路由&#xff1a; 决定分组从源端 到目标端的路径 网络层 传统路由器的功能 每个路由器(Per Route)的控制平面 &#xff08;传统&#xff09; 每个路由器上都有实…

Oracle数据库如果出现乱码,需要查看是否时字符集不一致导致乱码,这样解决

1、如果出现乱码&#xff0c;需要查看是否时字符集不一致导致乱码 以修改为ZHS16GBK字符集为例&#xff0c;具体字符集需要sql查询。 Oracle查看字符集 SELECT * FROM NLS_DATABASE_PARAMETERS p where p.PARAMETERNLS_CHARACTERSET; SELECT USERENV(language) FROM DUAL; 1.…

uni-app从零开始快速入门

教程介绍 跨端框架uni-app作为新起之秀&#xff0c;在不到两年的时间内&#xff0c;迅速被广大开发者青睐和推崇&#xff0c;得益于它颠覆性的优势“快”&#xff0c;快到可以节省7套代码。本课程由uni-app开发者团队成员亲授&#xff0c;带领大家无障碍快速掌握完整的uni-app…

STM32 CubeMx创建Lwip+FreeRtos时出现ping不通的问题

STM32 CubeMx创建LwipFreeRtos时出现ping不通 1、配置ETH&#xff0c;使用中断 2、配置Lwip&#xff08;使用静态ip&#xff09;&#xff0c;其余什么都不用管 3、配置FreeRtos&#xff08;选择V2版本&#xff09;&#xff0c;其余什么都不用管 4、创建代码 5、查看自动生…

目标检测预测框可视化python代码实现--OpenCV

import numpy as np import cv2 import colorsys from PIL import Image, ImageDraw, ImageFontdef puttext_cn(img, text, pt, color(255,0,0), size16):if (isinstance(img, np.ndarray)): # 判断是否OpenCV图片类型img Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2…

注册中心的基础知识

什么是注册中心 当服务启动时,将服务信息服务名称/IP/端口写入注册中心.注册中心接收服务端信息时保存服务信息,并且维护服务列表数据当服务消费者启动时会通过IP:端口(注册中心)远程链接注册中心. 获取服务列表信息.缓存到本地 当消费者调用服务时,查找缓存到本地的服务列表…

XSS一-WEB攻防-XSS跨站反射型存储型DOM型标签闭合输入输出JS代码解析

演示案例&#xff1a; XSS跨站-输入输出-原理&分类&闭合XSS跨站-分类测试-反射&存储&DOM #XSS跨站-输入输出-原理&分类&闭合 漏洞原理&#xff1a;接受输入数据&#xff0c;输出显示数据后解析执行 基础类型&#xff1a;反射(非持续)&#xff0c;存储(…

LinuxYUMVimg++/gccgdbGit使用

前言 大家好&#xff0c;我是jiantaoyab&#xff0c;前面的文章给大家介绍了Linux的基础命令和权限&#xff0c;学会了命令行的模式使用Linux&#xff0c;今后要开始在Linux上写代码了&#xff0c;在这篇文章将介绍YUM、vim、gdb、git等常用的工具。 先来看看Linux如何安装软…

怎么拆解台式电脑风扇CPU风扇的拆卸步骤-怎么挑

今天我就跟大家分享一下如何选购电脑风扇的知识。 我也会解释一下机箱散热风扇一般用多少转。 如果它恰好解决了您现在面临的问题&#xff0c;请不要忘记关注本站并立即开始&#xff01; 文章目录列表&#xff1a;大家一般机箱散热风扇都用多少转&#xff1f; 机箱散热风扇选择…

linux centos 安装jenkins,并构建spring boot项目

首先安装jenkins&#xff0c;使用war包安装&#xff0c;比较简单&#xff0c;注意看下载的版本需要的JDK版本&#xff0c;官网下载https://www.jenkins.io/download/ 把下载好的war包放到服务器上&#xff0c;然后运行&#xff0c;注意8080端口的放行 # 前台运行并指定端口 ja…

脉冲变压器电感的工艺结构原理及选型参数总结

🏡《总目录》 目录 1,概述2,工作原理3,结构特点3.1,铁心结构3.2,铁心材料3.3,绕组4,工艺流程4.1,准备铁芯4.2,绕制线圈4.3,安装线圈4.4,固定线圈4.5,绝缘处理4.6,高压脉冲引出

rtt的IO设备框架面向对象学习-oopc实现特点

网上oopc实现方式都能搜得到&#xff0c;如oopc参考文章&#xff0c;rtt的oopc也是基本一样。大家好像都有个共识了: &#xff08;1&#xff09;定义类都用struct——这一过程就是抽象封装的过程&#xff0c;把属性和方法封装到struct里面&#xff0c;方法用函数指针变量表示&a…

前端向后端传入json 后台怎么接收(params呢)

目录 一、使用POJO若前端传递过来的数据刚好和我们的bean实体对象属性一致&#xff0c;则可以使用对象的形式接收。后端实体类 二、使用Map接收后台Controller 三、使用RequestParams 1&#xff0c;params传参 2.地址拼接传参 当前端传来json数据时&#xff0c;后端有多种…

dji esdk开发(2)订阅实时视频流

文章目录 1、主要接口介绍1.1、订阅码流服务状态1.2、初始化、开始、和结束订阅码流1.2.1、订阅码流初始化1.2.2、开始码流传输1.2.3、停止码流传输1.3、设置负载相机码流源2、测试2.1、获取RGB图像2.1.1、解码部分2.1.2、完整测试代码如下2.2、目标检测并显示2.2.1、解码部分2…

数据结构 - 二叉树非递归遍历

文章目录 前言一、前序二、中序三、后序 前言 本文实现二叉树的前中后的非递归遍历&#xff0c;使用栈来模拟递归。 文字有点简略&#xff0c;需要看图和代码理解 树节点&#xff1a; typedef char DATA; //树节点 typedef struct Node {DATA data; //数据struct Node* left…

嵌入式仿真平台

嵌入式仿真实验教学平台 (puliedu.com) 这个平台可以写代码&#xff0c;元件拖一下就行&#xff0c;但是就是用的是标准库&#xff0c;自己一般写的hal库程序用不了&#xff0c;但是新手用还是可以的

I2C系列(三):软件模拟I2C读写24C04

一.目标 PC 端的串口调试软件通过 RS-485 与单片机通信&#xff0c;控制单片机利用软件模拟 I2C 总线对 EEPROM&#xff08;24C04&#xff09; 进行任意读写。 二.RS-485简述 在工业控制领域&#xff0c;传输距离越长&#xff0c;要求抗干扰能力也越强。由于 RS-232 无法消除…

快速入门Kotlin①基本语法

前言 23年底读了一遍“Kotlin官方文档”&#xff0c;官方文档大而全&#xff0c;阅读下来&#xff0c;大有裨益。 此系列文章的目的是记录学习进程&#xff0c;同时&#xff0c;若能让读者迅速掌握重点内容并快速上手&#xff0c;那就再好不过了。 函数 带有两个 Int 参数、…