MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Spring Boot的启动原理与优化技巧

2023-09-307.5k 阅读

Spring Boot 的启动原理

Spring Boot 以其快速开发、自动配置等特性,成为了Java 开发者构建应用的热门选择。深入了解其启动原理,有助于开发者更好地优化和定制应用。

SpringApplication 类

Spring Boot 的启动入口是 SpringApplication 类。当我们在 main 方法中创建 SpringApplication 实例并调用 run 方法时,一系列复杂的启动流程就开始了。

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

SpringApplication 的构造函数中,它会对传入的主配置类进行分析。它会检测该类是否是 Web 应用(基于 WebApplicationType 枚举),以及初始化各种初始化器和监听器。

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 = WebApplicationType.deduceFromClasspath();
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

启动流程

  1. 初始化阶段

    • 推断应用类型WebApplicationType 用于判断应用是 SERVLETREACTIVE 还是 NONE 类型。它通过检查类路径中是否存在特定的类(如 javax.servlet.Servletorg.springframework.web.reactive.DispatcherHandler)来做出判断。
    • 加载初始化器和监听器SpringApplicationMETA-INF/spring.factories 文件中加载 ApplicationContextInitializerApplicationListener。这些初始化器和监听器可以在应用上下文创建前后执行特定的逻辑。例如,ConfigFileApplicationListener 就是一个重要的初始化器,它负责加载 application.propertiesapplication.yml 等配置文件。
  2. 创建应用上下文阶段

    • 创建上下文实例:根据推断出的应用类型,SpringApplication 创建相应类型的 ApplicationContext。对于 SERVLET 应用,通常是 AnnotationConfigServletWebServerApplicationContext;对于 REACTIVE 应用,是 AnnotationConfigReactiveWebServerApplicationContext
    • 应用初始化器:在上下文实例创建后,SpringApplication 会调用所有已加载的 ApplicationContextInitializerinitialize 方法。这些初始化器可以对上下文进行进一步的配置,比如添加属性源等。
  3. 刷新应用上下文阶段

    • 加载 bean 定义:上下文刷新过程中,Spring 会扫描主配置类及其相关的配置类,加载所有的 bean 定义。这包括通过 @ComponentScan 扫描到的组件,以及使用 @Bean 注解定义的 bean。
    • 实例化和初始化 bean:Spring 根据 bean 定义,实例化并初始化所有的 bean。在这个过程中,会处理 bean 的依赖注入、生命周期回调等。例如,如果一个 bean 依赖另一个 bean,Spring 会确保先实例化和初始化被依赖的 bean,然后再注入到依赖它的 bean 中。
  4. 启动阶段

    • 启动 web 服务器:对于 SERVLETREACTIVE 类型的应用,上下文刷新完成后,会启动相应的 web 服务器(如 Tomcat、Jetty 或 Undertow)。服务器启动后,应用就可以接收外部请求了。
    • 发布事件:整个启动过程中,SpringApplication 会发布各种 ApplicationEvent,如 ApplicationStartingEventApplicationReadyEvent 等。监听器可以监听这些事件,执行特定的逻辑,比如在应用启动完成后打印启动日志等。

Spring Boot 的优化技巧

了解了 Spring Boot 的启动原理后,我们可以从多个方面对应用进行优化,以提高性能和启动速度。

减少不必要的依赖

  1. 分析依赖树pom.xml(Maven 项目)或 build.gradle(Gradle 项目)中,我们可以使用工具来分析项目的依赖树。对于 Maven,可以使用 mvn dependency:tree 命令。这个命令会打印出项目所有依赖的树形结构,我们可以从中找出不必要的依赖。
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

假设项目中引入了 spring-boot-starter-web,它会自动引入一系列依赖,包括 Tomcat 作为默认的 web 服务器。如果项目不需要 web 功能,就可以移除这个依赖。

  1. 排除传递依赖 有时候,一个依赖会传递引入一些我们不需要的依赖。我们可以通过在依赖中使用 exclusions 标签(Maven)或 exclude 方法(Gradle)来排除这些传递依赖。
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    <exclusions>
        <exclusion>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </exclusion>
    </exclusions>
</dependency>

在上述例子中,spring-boot-starter-jdbc 可能会传递引入 h2 数据库依赖。如果项目不使用 h2,就可以通过这种方式排除。

优化配置文件加载

  1. 合理组织配置文件 Spring Boot 支持多种配置文件格式,如 propertiesyml。在大型项目中,将配置文件按功能模块进行拆分,可以提高配置的可读性和维护性。例如,可以创建 application - common.yml 存放通用配置,application - dev.yml 存放开发环境配置,application - prod.yml 存放生产环境配置。
# application - common.yml
server:
  port: 8080

# application - dev.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/dev_db
    username: dev_user
    password: dev_password

# application - prod.yml
spring:
  datasource:
    url: jdbc:mysql://prod - db - server:3306/prod_db
    username: prod_user
    password: prod_password
  1. 减少配置文件加载时间 Spring Boot 启动时会加载多个配置文件,包括默认的 application.propertiesapplication.yml。可以通过设置 spring.config.location 属性来指定配置文件的位置,避免不必要的文件搜索。
public class Application {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.setDefaultProperties(Collections.singletonMap("spring.config.location", "classpath:/config/,file:./config/"));
        app.run(args);
    }
}

这样配置后,Spring Boot 会优先从指定的位置加载配置文件,减少了在整个类路径下搜索配置文件的时间。

优化 bean 的加载和初始化

  1. 懒加载 bean 对于一些不急于使用的 bean,可以使用懒加载。在 Spring Boot 中,只需在 @Component@Bean 注解的类或方法上添加 @Lazy 注解即可。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
@Lazy
public class LazyLoadedBean {
    public LazyLoadedBean() {
        System.out.println("LazyLoadedBean is initialized");
    }
}

LazyLoadedBean 被标记为懒加载后,它不会在应用启动时立即初始化,而是在第一次被使用时才初始化,从而加快了应用的启动速度。

  1. 优化 bean 的依赖关系 尽量减少 bean 之间的复杂依赖关系。复杂的依赖关系可能导致 bean 的初始化顺序难以控制,增加启动时间。例如,如果一个 bean A 依赖于 bean B、C 和 D,而 bean B 又依赖于 bean E 和 F,这种多层依赖关系可能会使启动过程变得复杂。可以通过重构代码,将一些依赖关系进行简化,比如将部分功能封装到一个新的 bean 中,减少依赖层次。

优化 web 服务器配置

  1. 选择合适的 web 服务器 Spring Boot 默认使用 Tomcat 作为 web 服务器。对于一些轻量级应用,可以考虑使用 Jetty 或 Undertow。Jetty 以其轻量级和高性能著称,而 Undertow 则在处理高并发场景下表现出色。

要切换到 Jetty,可以在 pom.xml 中添加以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
  1. 调整 web 服务器参数 对于 Tomcat,可以通过在 application.propertiesapplication.yml 中配置参数来优化性能。例如,可以调整最大线程数、连接超时时间等。
server:
  tomcat:
    max - threads: 200
    min - spare - threads: 10
    connection - timeout: 5000

通过合理调整这些参数,可以提高 web 服务器在高并发场景下的处理能力,同时也可以在一定程度上优化启动时间。

使用 AOT 编译

  1. 什么是 AOT 编译 AOT(Ahead - Of - Time)编译是 Spring Boot 2.6 引入的新特性。传统的 Spring 应用在启动时会进行大量的反射操作来加载和初始化 bean。AOT 编译则在构建阶段将这些反射操作提前处理,生成字节码,从而加快应用的启动速度。

  2. 启用 AOT 编译 要启用 AOT 编译,首先需要在项目中添加 spring - boot - aot - gradle - pluginspring - boot - aot - maven - plugin

对于 Gradle 项目,在 build.gradle 中添加:

plugins {
    id 'org.springframework.boot' version '2.6.0'
    id 'io.spring.aot' version '0.11.0'
}

然后运行 ./gradlew bootAotBuild 命令,Gradle 会在构建过程中生成 AOT 相关的代码和资源。在应用启动时,这些预编译的代码会被使用,大大加快启动速度。

优化日志配置

  1. 减少日志输出级别 在开发环境中,通常会将日志级别设置为 DEBUGTRACE 以方便调试。但在生产环境中,过高的日志级别会增加系统开销,影响性能。可以将日志级别调整为 INFOWARN

application.yml 中配置:

logging:
  level:
    root: INFO
    org.springframework: INFO
  1. 选择合适的日志框架 Spring Boot 默认支持多种日志框架,如 Logback、Log4j2。Log4j2 在性能方面表现较好,特别是在高并发场景下。如果项目对性能要求较高,可以考虑切换到 Log4j2。

pom.xml 中排除默认的 Logback 依赖,添加 Log4j2 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter - logging</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

通过以上这些优化技巧,我们可以在深入理解 Spring Boot 启动原理的基础上,对应用进行全面的优化,提高应用的性能和启动速度,使其更适合不同的生产环境和业务需求。