SpringBoot启动流程

大致流程

关于SpringBoot的启动流程,大致是这样的

  1. 加载启动类
    启动类是使用了@SpringBootApplication注解标注的类,该注解包含了@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解的功能。SpringBoot通过扫描启动类所在的包及子包,自动配置相应的Bean

  2. 加载配置文件
    SpringBoot程序默认从applicaiton.properties或application.yml中加载配置,也可以通过在启动类上标注@PropertySource来引入其他的配置文件

  3. 创建Spring容器
    SpringBoot使用SpringBootApplication类创建Spring容器,SpringApplication类是SpringBoot的核心类,它提供了配置和管理Bean的方法。如果是Web应用,SpringApplication会创建一个内置的Web服务器

  4. 加载自动配置
    SpringBoot通过@EnableAutoConfiguration来完成自动配置,根据starter依赖中的Configuration和Bean的装配情况,自动装配相应的Bean

  5. 运行SpringBoot应用程序
    当一切准备就绪后,SpringBoot就会启动应用程序,如果是Web应用,就会启动内置的Web服务器,如果使用的是Web服务器,可以将应用程序打包成一个可以直接运行的jar文件

源码解读

当我们执行启动类中的SpringApplication.run()方法后,
SpringBoot的动作实际上可以大致分为两部分:实例化SpringApplication和运行SpringApplication

参考文章
9千字长文带你了解SpringBoot启动过程–史上最详细 SpringBoot启动流程-图文并茂_Fly丶X的博客-CSDN博客
spring boot 启动流程分析 - 掘金
https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6ba8bf5c8177430b8f462f35948d1c74~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp?

图片来自以上两篇文章

实例化SpringApplication

我们查看源码
当执行了静态方法SpringApplcation.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);  
}

进入到SpringApplication的构造方法,在实例化中干了哪些事情?

  
public SpringApplication(Class<?>... primarySources) {  
    this((ResourceLoader)null, primarySources);  
}  
  
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {  
    this.sources = new LinkedHashSet();  
    this.bannerMode = Mode.CONSOLE;  
    this.logStartupInfo = true;  
    this.addCommandLineProperties = true;  
    this.addConversionService = true;  
    this.headless = true;  
    this.registerShutdownHook = true;  
    this.additionalProfiles = Collections.emptySet();  
    this.isCustomEnvironment = false;  
    this.lazyInitialization = false;  
    this.applicationContextFactory = ApplicationContextFactory.DEFAULT;  
    this.applicationStartup = ApplicationStartup.DEFAULT;  
    this.resourceLoader = resourceLoader;  
    Assert.notNull(primarySources, "PrimarySources must not be null");  
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));  
    // 1. 判断此应用是否是Web应用
    this.webApplicationType = WebApplicationType.deduceFromClasspath();  
    // 2.加载类加载器
    this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));  
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));  
    // 3. 加载监听器
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));  
    // 4. 设置主类
    this.mainApplicationClass = this.deduceMainApplicationClass();  
}
1. 判断应用类型

通过一个枚举类WebApplicationType的静态方法来判断应用类型

package org.springframework.boot;  
  
import org.springframework.util.ClassUtils;  
  
public enum WebApplicationType {  
    NONE,  
    SERVLET,  
    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 WebApplicationType() {  
    }  
  
    static WebApplicationType deduceFromClasspath() {  
        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 {  
            String[] var0 = SERVLET_INDICATOR_CLASSES;  
            int var1 = var0.length;  
  
            for(int var2 = 0; var2 < var1; ++var2) {  
                String className = var0[var2];  
                if (!ClassUtils.isPresent(className, (ClassLoader)null)) {  
                    return NONE;  
                }  
            }  
  
            return SERVLET;  
        }  
    }  
}

在此方法中,通过扫描Classpath中是否有预设的几个Web应用相关的类,来判断此应用是否是一个Web应用。
此处提供了几个Web关键类的全限定名,拿到了全限定名,就可以利用反射的机制来判断是否存在这些类。
此枚举类有三个类型:

  • NONE
  • SERVLET
  • REACTIVE
    其中REACTIVE的意思就是标志这个SpringBoot应用程序就是一个普通的boot项目
2. 加载Initializer初始化构造器

此处是加载Spring-boot中自带的初始化器,并不是第三方的starter中的初始化器。
会扫描spring-boot自动的jar中META-INF/spring.factories文件中定义的配置类和相关Bean
可以利用此机制,实现自己的初始化器。

3. 加载应用监听器Listener

同样,仍然是在spring-boot自带的jar中的META-INF/spring.factories文件中加载。
此监听器的类型是ApplicationListener,也就是对整个应用程序的监听器。

4. 设置主类

我们的启动类,就是主类,使用了@SpringBootApplication注解标注,并且包含main方法的类。
通过deduceMainApplicationClass()方法来推断主类所在的位置,确定主类的位置,为后面的包扫描提供条件。

执行SpringApplication的run()

实例化完成SpringApplication后,就会执行SpringApplication实例的run()方法
在该方法中,大致完成了以下几件事情:

  1. 启动应用监听器
  2. 准备Environment
  3. 发布事件
  4. 创建上下文对象、bean
  5. 刷新refresh()上下文对象
  6. 结束
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {  
    return (new SpringApplication(primarySources)).run(args);  
}

此方法会返回一个ConfigurableApplicationContext的实例,我们可以在启动类中拿到这个返回值。
看run()方法的源码

public ConfigurableApplicationContext run(String... args) {  
	// 1. 开启计时
    long startTime = System.nanoTime();  
    DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();  
    // Spring上下文对象
    ConfigurableApplicationContext context = null;  
    this.configureHeadlessProperty();  
    // 2. 实例化应用监听器并封装
    SpringApplicationRunListeners listeners = this.getRunListeners(args);  
    // 启动应用监听器
    listeners.starting(bootstrapContext, this.mainApplicationClass);  
  
    try {  
	    // 3.准备环境参数以及初始化环境
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);  
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);  
        this.configureIgnoreBeanInfo(environment);  
        // 4. 打印Banner,无所吊用
        Banner printedBanner = this.printBanner(environment);  
        // 5. 实例化Spring上下文对象
        context = this.createApplicationContext();  
        context.setApplicationStartup(this.applicationStartup);  
        // 6.Spring容器初始化
        this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);  
        // 7. 刷新容器
        this.refreshContext(context);  
        this.afterRefresh(context, applicationArguments);  
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);  
        if (this.logStartupInfo) {  
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);  
        }  
  
        listeners.started(context, timeTakenToStartup);  
        this.callRunners(context, applicationArguments);  
    } catch (Throwable var12) {  
        this.handleRunFailure(context, var12, listeners);  
        throw new IllegalStateException(var12);  
    }  
  
    try {  
        Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);  
        listeners.ready(context, timeTakenToReady);  
        return context;  
    } catch (Throwable var11) {  
        this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);  
        throw new IllegalStateException(var11);  
    }  
}
1. 启动计时器

我现在使用的Springboot版本是2.7.10,发现取消了计时器这个类,而是直接通过来获取启动耗时

System.nanoTime()

如果你是低版本的Springboot,会发现有这样的几行代码,这就是启动计时器

// 实例化计时器
StopWatch stopWatch = new StopWatch(); 
// 开始计时
stopWatch.start();

不论是否使用了计时器,目的都是来记录SpringBoot的启动流程。

2.启动应用监听器

通过这几行代码

// 获取所有的应用监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);  
// 启动
listeners.starting(bootstrapContext, this.mainApplicationClass);

在getRunListener()方法中

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

通过一个Class类型的数组来封装所有的监听器
再看SpringApplicationRunListeners的构造方法

class SpringApplicationRunListeners {  
    private final Log log;  
    private final List<SpringApplicationRunListener> listeners;  
    private final ApplicationStartup applicationStartup;  
  
    SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners, ApplicationStartup applicationStartup) {  
        this.log = log;  
        this.listeners = new ArrayList(listeners);  
        this.applicationStartup = applicationStartup;  
    }
}

在这个SpringApplicationRunListeners内部,通过一个List集合来封装所有的应用监听器,以此来达到统一管理所有的应用监听器

3.准备Environment

通过以下代码

// 准备应用参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);  
// 准备Environment
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);

Environment接口是对程序运行环境的抽象,是保存系统配置的中心
来打开prepareEnvironment()方法

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) { 
// 1. 创建Environment实例,自动根据环境的不同,创建对应的Environment实例
    ConfigurableEnvironment environment = this.getOrCreateEnvironment();  
// 2. 设置启动参数到Environment实例中
    this.configureEnvironment(environment, applicationArguments.getSourceArgs());  
// 3. 更新参数
    ConfigurationPropertySources.attach(environment);  
// 4. 通过应用监听器来发布事件    
    listeners.environmentPrepared(bootstrapContext, environment);  
    DefaultPropertiesPropertySource.moveToEnd(environment);  
    Assert.state(!environment.containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");  
// 5. 绑定主类
    this.bindToSpringApplication(environment);  
    if (!this.isCustomEnvironment) {  
        EnvironmentConverter environmentConverter = new EnvironmentConverter(this.getClassLoader());  
        environment = environmentConverter.convertEnvironmentIfNecessary(environment, this.deduceEnvironmentClass());  
    }  

    ConfigurationPropertySources.attach(environment);  
    return environment;  
}
4. 实例化SpringContext对象
// 实例化SpringContext对象
context = this.createApplicationContext();  
context.setApplicationStartup(this.applicationStartup);  
// 设置SpringContext参数
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);  

在prepareContext()方法中,

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {  
// 1. 绑定环境Enviroment
    context.setEnvironment(environment);  
// 2. 如果Application有设置BeanName、resourceLoader等,
// 就将其注入到Context中
    this.postProcessApplicationContext(context);  
    this.applyInitializers(context);  
// 3. 发布ApplicationContextInitializer事件    
    listeners.contextPrepared(context);  
    bootstrapContext.close(context);  
    if (this.logStartupInfo) {  
        this.logStartupInfo(context.getParent() == null);  
        this.logStartupProfileInfo(context);  
    }
5. 刷新容器

对Context做出了一系列设置后,刷新容器

// 刷新SpringContext容器
this.refreshContext(context);
  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SpringBoot启动流程可以分为以下几个步骤: 1. 确定应用程序类型。在启动SpringBoot时,首先需要确定应用程序的类型。这可以通过设置启动类的注解来实现,比如使用@SpringBootApplication注解。 2. 创建SpringBoot应用程序上下文。在确定应用程序类型后,SpringBoot会创建一个应用程序上下文(ApplicationContext)对象。这个上下文对象是整个应用程序的核心,包含了所有的配置信息和Bean定义。 3. 加载配置文件。SpringBoot会自动加载并解析应用程序的配置文件,包括application.properties或application.yml等。这些配置文件可以用来配置应用程序的各种属性,如数据库连接、端口号等。 4. 扫描和注册Bean。SpringBoot会扫描应用程序中的所有类,并将符合条件的类注册为Bean。这可以通过@ComponentScan注解来实现,它会扫描指定包及其子包中的所有类。 5. 执行Bean的初始化和依赖注入。在注册Bean后,SpringBoot会执行Bean的初始化操作,并将其依赖的其他Bean注入到其中。这可以通过使用@Autowired注解来实现。 6. 启动应用程序。在完成上述步骤后,SpringBoot会启动应用程序。这将导致应用程序开始监听指定的端口,并处理来自客户端的请求。 总而言之,SpringBoot启动流程包括确定应用程序类型、创建应用程序上下文、加载配置文件、扫描和注册Bean、执行Bean的初始化和依赖注入,最后启动应用程序。 <span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [9千字长文带你了解SpringBoot启动过程--史上最详细 SpringBoot启动流程-图文并茂](https://blog.csdn.net/weixin_44947701/article/details/124055713)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值