[Spring Boot] 1. Spring Boot启动过程源码分析

关于Spring Boot,已经有很多介绍其如何使用的文章了,本文从源代码(基于Spring-boot 1.5.6)的角度来看看Spring Boot的启动过程到底是怎么样的,为何以往纷繁复杂的配置到如今可以这么简便。

1. 入口类

package com.example.demo;

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

@SpringBootApplication
public class DemoApplication {
   

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

}

以上的代码就是通过Spring Initializr配置生成的一个最简单的Web项目(只引入了Web功能)的入口方法。这个想必只要是接触过Spring Boot都会很熟悉。简单的方法背后掩藏的是Spring Boot在启动过程中的复杂性,本文的目的就是一探这里面的究竟。

1.1 注解@SpringBootApplication

而在看这个方法的实现之前,需要看看@SpringBootApplication这个注解的功能:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {
  @Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
  // ...
}

很明显的,这个注解就是三个常用在一起的注解@SpringBootConfiguration,@EnableAutoConfiguration以及@ComponentScan的组合,并没有什么高深的地方。

1.1.1 @SpringBootConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
   
}

这个注解实际上和@Configuration有相同的作用,配备了该注解的类就能够以JavaConfig的方式完成一些配置,可以不再使用XML配置。

1.1.2 @ComponentScan

顾名思义,这个注解完成的是自动扫描的功能,相当于Spring XML配置文件中的:

<context:component-scan>

可以使用basePackages属性指定要扫描的包,以及扫描的条件。如果不设置的话默认扫描@ComponentScan注解所在类的同级类和同级目录下的所有类,所以对于一个Spring Boot项目,一般会把入口类放在顶层目录中,这样就能够保证源码目录下的所有类都能够被扫描到。

1.1.3 @EnableAutoConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
   
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

这个注解是让Spring Boot的配置能够如此简化的关键性注解。目前知道这个注解的作用就可以了,关于自动配置不再本文讨论范围内,后面如果有机会另起文章专门分析这个自动配置的实现原理。

2. 入口方法

2.1 SpringApplication的实例化

介绍完了入口类,下面开始分析关键方法:

SpringApplication.run(DemoApplication.class, args);

相应实现:

// 参数对应的就是DemoApplication.class以及main方法中的args
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的实例,然后运行它的run方法:

// 构造实例
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = deduceWebApplicationType();
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

在构造函数中,主要做了4件事情:

2.1.1 推断应用类型是Standard还是Web
private WebApplicationType deduceWebApplicationType() {
    if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
            && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

// 相关常量
private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
        + "web.reactive.DispatcherHandler";
private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
        + "web.servlet.DispatcherServlet";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
        "org.springframework.web.context.ConfigurableWebApplicationContext" };

可能会出现三种结果:

  1. WebApplicationType.REACTIVE - 当类路径中存在REACTIVE_WEB_ENVIRONMENT_CLASS并且不存在MVC_WEB_ENVIRONMENT_CLASS时
  2. WebApplicationType.NONE - 也就是非Web型应用(Standard型),此时类路径中不包含WEB_ENVIRONMENT_CLASSES中定义的任何一个类时
  3. WebApplicationType.SERVLET - 类路径中包含了WEB_ENVIRONMENT_CLASSES中定义的所有类型时
2.1.2 设置初始化器(Initializer)
setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));

这里出现了一个新的概念 - 初始化器。

先来看看代码,再来尝试解释一下它是干嘛的:

private <T> Collect
  • 18
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
`afterRefresh` 是 Spring Boot 启动过程中的一个重要方法,它是在 Spring 应用上下文完成刷新之后被调用的。具体来说,`afterRefresh` 方法是在 `AbstractApplicationContext` 类的 `finishRefresh` 方法中被调用的,代码如下: ```java protected void finishRefresh() { // ... // Initialize lifecycle processor for this context. initLifecycleProcessor(); // Propagate refresh to lifecycle processor first. getLifecycleProcessor().onRefresh(); // Publish the final event. publishEvent(new ContextRefreshedEvent(this)); // Participate in LiveBeansView MBean, if active. if (!isRunning()) { stop(); } // ... // Finally, invoke the afterRefresh callbacks. invokeAfterRefreshCallbacks(); } ``` 在 `afterRefresh` 方法中,Spring Boot 会调用 `ApplicationContext` 中所有实现了 `org.springframework.context.support.ApplicationContextAware` 接口的 Bean 的 `setApplicationContext` 方法,将应用上下文传入这些 Bean 中。这样,这些 Bean 就能够获取到应用上下文,并在需要时使用它。 另外,Spring Boot 还会回调所有实现了 `org.springframework.boot.context.event.ApplicationContextInitializedListener` 接口的 Bean 的 `afterApplicationContextInitialized` 方法,这些 Bean 可以在应用上下文初始化之后进行一些自定义操作。 总之,`afterRefresh` 方法是 Spring Boot 启动过程中非常重要的一个方法,它标志着应用上下文已经完成了初始化,可以进入正常的运行状态了。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值