SpringBoot 是如何实现 main 方法启动 Web 项目的?核心原理是什么?

熊猫
22 2 0
Java

传统Web项目启动过程,首选需要将Web项目打包成War包,然后通过Tomcat服务或则其它容器服务进行运行。

1.传统Web项目启动过程

传统Web项目启动过程,首选需要将Web项目打包成War包,然后通过Tomcat服务或则其它容器服务进行运行。

2.SpringBoot启动Web项目流程

SpringBoot是将Tomcat或其它容器服务作为依赖导入,通过java的main方法启动整个应用服务,包括Web容器。

3.SpringBoot启动过程详解

以下就是最简单的SpringBoot项目启动的入口

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

1.初始化与准备

当执行SpringApplication.run()方法后,会创建一个SpringApplication对象

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

下面的代码是SpringApplication()的构造器,会执行以下内容:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {  
	// 是否将命令行参数添加到 `Environment` 中作为属性源。默认启用,表示命令行参数会被解析进 `Spring Environment`
    this.addCommandLineProperties = true;  
    // 用于在配置绑定或属性注入时自动转换类型(如字符串转枚举、数字等)。
    this.addConversionService = true;  
    // 表示应用在没有显示器、键盘、鼠标的环境(如服务器)中运行;
    this.headless = true;  
    this.additionalProfiles = Collections.emptySet();  
    this.isCustomEnvironment = false;  
    /**
	    设置应用上下文工厂(ApplicationContext 的创建方式)。
	    决定使用哪种 ApplicationContext:
        AnnotationConfigServletWebServerApplicationContext(Web)
        ReactiveWebServerApplicationContext
        AnnotationConfigApplicationContext(非Web)
		DEFAULT 会根据类路径自动推断(见后续 `deduceFromClasspath`)。
    */
    this.applicationContextFactory = ApplicationContextFactory.DEFAULT;  
    this.applicationStartup = ApplicationStartup.DEFAULT;  
    this.properties = new ApplicationProperties();  
    this.resourceLoader = resourceLoader;  
    Assert.notNull(primarySources, "PrimarySources must not be null");  
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));  
    this.properties.setWebApplicationType(WebApplicationType.deduceFromClasspath());  
    this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));  
    // 初始化
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));  
    // 设置监听器
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));  
    this.mainApplicationClass = this.deduceMainApplicationClass();  
}

构造器中大致做了以下事情:

- 推断应用类型(是否为 Web 应用),这是重点
- 设置初始化器(Initializers)
- 设置监听器(Listeners)
- 推断并设置 main 方法所在类,这是重点

以下是SpringApplication中run()方法代码

public ConfigurableApplicationContext run(String... args) {  
    Startup startup = SpringApplication.Startup.create();  
    if (this.properties.isRegisterShutdownHook()) {  
        shutdownHook.enableShutdownHookAddition();  
    }  
  
    DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();  
    ConfigurableApplicationContext context = null;  
    this.configureHeadlessProperty();  
    SpringApplicationRunListeners listeners = this.getRunListeners(args);  
    listeners.starting(bootstrapContext, this.mainApplicationClass);  
  
    try {  
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);  
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);  
        Banner printedBanner = this.printBanner(environment);  
        context = this.createApplicationContext();  
        context.setApplicationStartup(this.applicationStartup);  
        this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);  
        this.refreshContext(context);  
        this.afterRefresh(context, applicationArguments);  
        startup.started();  
        if (this.properties.isLogStartupInfo()) {  
            (new StartupInfoLogger(this.mainApplicationClass, environment)).logStarted(this.getApplicationLog(), startup);  
        }  
  
        listeners.started(context, startup.timeTakenToStarted());  
        this.callRunners(context, applicationArguments);  
    } catch (Throwable ex) {  
        throw this.handleRunFailure(context, ex, listeners);  
    }  
  
    try {  
        if (context.isRunning()) {  
            listeners.ready(context, startup.ready());  
        }  
  
        return context;  
    } catch (Throwable ex) {  
        throw this.handleRunFailure(context, ex, (SpringApplicationRunListeners)null);  
    }  
}

run()方法大致做了以下事情:

- 创建并启动计时器
- 获取环境变量参数
- 准备上下文(预处理、加载属性源、应用初始化器等)
- 刷新上下文(这是关键步骤,会实例化所有单例 Bean),重要
- 执行 Runner(实现 CommandLineRunner 或 ApplicationRunner 的 Bean)

4.自动配置机制

SpringBoot最核心的部分是自动配置。它通过@EnableAutoConfiguration 注解(包含在@SpringBootApplication 中)启用。这个注解会导入AutoConfigurationImportSelector,它利用 Spring 的 SPI 机制从 classpath 下的META-INF/spring.factories 文件中读取自动配置类列表。每个自动配置类都用@ConditionalOnXXX 注解进行条件化配置,只有在满足特定条件(如某个类存在、某个 Bean 定义存在或某个属性设置)时才会应用该配置。

总结

谈到SpringBoot最重要的就是以下两点: 1.自动配置 如何实现自动装配的,重点就是SpringApplication注解内部的EnableAutoConfiguration注解,该注解会导入AutoConfigurationImportSelector,然后寻找classpath 下的META-INF/spring.factories 文件,并且读取自动配置类列表,根据@ConditionalOnXXX注解条件性导入。

2.SpringApplication构造器以及run方法干了那些 构造器:判断服务类型(是不是Web服务),推断主方法所在类,设置监听器、设置初始化器 run方法:读取配置信息,准备上下文和刷新上下文(会实例化所有单例 Bean