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

news2024/10/7 10:23:29

一,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,系统变量默认为true
    this.configureHeadlessProperty();
    //(重要🌟)获取spring.factories中的监听器变量,EventPublishingRunListener 
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    //启动运行时监听器
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        // 命令行参数包装为了ApplicationArguments
        ApplicationArguments 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();
    //获取所有的环境后处理器EnvironmentPostProcessor
    Iterator 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);
    //执行所有初始化器 ApplicationContextInitializer
    this.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.coloradmin.cn/o/1539916.html

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

相关文章

STM32之HAL开发——RCC外设CubeMX配置时钟

RCC外设介绍 RCC是Reset and Clock Control (复位和时钟控制)的缩写&#xff0c;它是STM32内部的一个重要外设&#xff0c;负责管理各种时钟源和时钟分频&#xff0c;以及为各个外设提供时钟使能。RCC模块可以通过寄存器操作或者库函数来配置。 RCC是复位和时钟控制模块&#…

强化学习之父Richard Sutton:通往AGI的另一种可能

2019年&#xff0c;强化学习之父、阿尔伯塔大学教授Richard Sutton发表了后来被AI领域奉为经典的The Bitter lesson&#xff0c;这也是OpenAI研究员的必读文章。 在这篇文章中&#xff0c;Richard指出&#xff0c;过去 70 年来&#xff0c;AI 研究的一大教训是过于重视人类既有…

是德科技keysight DSOX3024T示波器

181/2461/8938产品概述&#xff1a; DSOX3024T 示波器 要特性与技术指标 使用电容触摸屏进行简洁的触控操作&#xff1a; •提高调试效率 •触控设计可以简化文档记录 •使用起来就像您喜欢的智能手机或平板电脑一样简单 使用 MegaZoom IV 技术揭示偶发异常&#xff1a; •超快…

区块链技术下的新篇章:DAPP与消费增值的深度融合

随着区块链技术的持续演进&#xff0c;去中心化应用&#xff08;DAPP&#xff09;正逐渐受到人们的瞩目。DAPP&#xff0c;这种在分布式网络上运行的应用&#xff0c;以其去中心化、安全可靠、透明公开的特性&#xff0c;为用户提供了更为便捷和安全的消费体验。近年来&#xf…

ReactNative项目构建分析与思考之RN组件化

传统RN项目对比 ReactNative项目构建分析与思考之react-native-gradle-plugin ReactNative项目构建分析与思考之native_modules.gradle ReactNative项目构建分析与思考之 cli-config 在之前的文章中&#xff0c;已经对RN的默认项目有了一个详细的分析&#xff0c;下面我们来…

K8S之DaemonSet控制器

DaemonSet控制器 概念、原理解读、应用场景概述工作原理典型的应用场景介绍DaemonSet 与 Deployment 的区别 解读资源清单文件实践案例 概念、原理解读、应用场景 概述 DaemonSet控制器能够确保K8S集群所有的节点都分别运行一个相同的pod副本&#xff1b; 当集群中增加node节…

如何打造智慧公厕?发挥数据要素价值构建新型智慧公厕

公共厕所是城市建设和管理中不可或缺的一环。然而&#xff0c;长期以来&#xff0c;公厕的管理难题一直困扰着城市管理者和市民。为了解决这一问题&#xff0c;新时期以信息化为引领的智慧公厕建设应运而生。智慧公厕建设的推进需要技术融合、业务融合和数据融合&#xff0c;以…

C语言与sqlite3入门

c语言与sqlite3入门 1 sqlite3数据类型2 sqlite3指令3 sqlite3的sql语法3.1 创建表create3.2 删除表drop3.3 插入数据insert into3.4 查询select from3.5 where子句3.6 修改数据update3.7 删除数据delete3.8 排序Order By3.9 分组GROUP BY3.10 约束 4 c语言执行sqlite34.1 下载…

jmeter使用方法---自动化测试

HTTP信息头管理器 一个http请求会发送请求到服务器&#xff0c;请求里面包含&#xff1a;请求头、请求正文、请求体&#xff0c;请求头就是信息头Authorization头的主要用作http协议的认证。 Authorization的作用是当客户端访问受口令保护时&#xff0c;服务器端会发送401状态…

Head First Design Patterns -模板方法模式

什么是模板方法模式 在一个方法中定义一个算法的骨架&#xff0c;而把一些步骤延迟到子类。模板方法使得子类可以在不改变算法结构的情况下&#xff0c;重新定义算法的某些步骤。 这些算法步骤中的一个或者多个被定义为抽象的&#xff0c;由子类实现。 类图 代码 书中用泡茶和…

鸿蒙开发实战:网络请求库【axios】

简介 [Axios] &#xff0c;是一个基于 promise 的网络请求库&#xff0c;可以运行 node.js 和浏览器中。本库基于[Axios]原库v1.3.4版本进行适配&#xff0c;使其可以运行在 OpenHarmony&#xff0c;并沿用其现有用法和特性。 http 请求Promise APIrequest 和 response 拦截器…

分布式ID生成方案总结

分布式场景下&#xff0c;由于通常是分库分表&#xff0c;所以通常无法仅仅使用数据库的自增Id。需要使用其他方案生成唯一的id。目前业界主流的是基于雪花算法或者雪花算法的改进版本。 UUID 有什么特点&#xff1f; 足够的简单&#xff0c;java原生自带。本地生成具有唯一性…

kubernetes负载均衡-service

一、service的概念 1、什么是service 在Kubernetes中&#xff0c;pod是应用程序的载体&#xff0c;当我们需要访问这个应用时&#xff0c;可以通过Pod的IP进行访问&#xff0c;但是这里有两个问题:1、Pod的IP地址不固定&#xff0c;一旦Pod异常退出、节点故障&#xff0c;则会…

java的IO之NIO

NIO是一种同步非阻塞的I/O模型&#xff0c;在Java 1.4中引入了NIO框架&#xff0c;对应java.nio包&#xff0c;提供了channel、selector、buffer等。 NIO中的N可以理解为Non-blocking不在单纯是New&#xff0c;它支持面向缓冲的&#xff0c;基于通道的I/O操作方法。NIO提供了与…

SCI一区 | Matlab实现WOA-TCN-BiGRU-Attention鲸鱼算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测

SCI一区 | Matlab实现WOA-TCN-BiGRU-Attention鲸鱼算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测 目录 SCI一区 | Matlab实现WOA-TCN-BiGRU-Attention鲸鱼算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测预测效果基本介绍模型描述程序…

微信小程序 - picker-viewer实现省市选择器

简介 本文会基于微信小程序picker viewer组件实现省市选择器的功能。 实现效果 实现代码 布局 <picker-view value"{{value}}" bindchange"bindChange" indicator-style"height: 50px;" style"width: 100%; height: 300px;" &…

使用Intellij idea编写Spark应用程序(Scala+Maven)

使用Intellij idea编写Spark应用程序(ScalaMaven) 对Scala代码进行打包编译时&#xff0c;可以采用Maven&#xff0c;也可以采用sbt&#xff0c;相对而言&#xff0c;业界更多使用sbt。这里介绍IntelliJ IDEA和Maven的组合使用方法。IntelliJ IDEA和SBT的组合使用方法&#xf…

牛客题霸-SQL篇(刷题记录二)

本文基于前段时间学习总结的 MySQL 相关的查询语法&#xff0c;在牛客网找了相应的 MySQL 题目进行练习&#xff0c;以便加强对于 MySQL 查询语法的理解和应用。 由于涉及到的数据库表较多&#xff0c;因此本文不再展示&#xff0c;只提供 MySQL 代码与示例输出。 以下内容是…

Xilink 简单双口ram ip的读写仿真

简单双口RAM有两个端口Port A和port B,其中Port A用于写数据&#xff0c;Port B用于读数据&#xff0c;读写接口可以独立时钟工作。这一点和真双口RAM是有区别的&#xff0c;真双口RAM的A B两个Port都可以进行读写操作。 RAM是FPGA中重要的数据结构&#xff0c;可用于数…

EI级!高创新原创未发表!VMD-TCN-BiGRU-MATT变分模态分解卷积神经网络双向门控循环单元融合多头注意力机制多变量时间序列预测(Matlab)

EI级&#xff01;高创新原创未发表&#xff01;VMD-TCN-BiGRU-MATT变分模态分解卷积神经网络双向门控循环单元融合多头注意力机制多变量时间序列预测&#xff08;Matlab&#xff09; 目录 EI级&#xff01;高创新原创未发表&#xff01;VMD-TCN-BiGRU-MATT变分模态分解卷积神经…