SpringBoot启动扩展应用:干预优化+加快启动时间

一、SpringBoot启动配置原理简述

本内容直接查看分析SpringBoot启动配置原理,传送门:

二、SpringBoot启动过程干预

Spring Boot启动过程中我们可以实现以下干预工作:

  • 修改Spring Boot默认的配置属性。使用@ConfigurationProperties@EnableConfigurationProperties注解,可以获取和修改Spring Boot的配置属性。

  • 加载配置文件。Spring Boot会自动加载application.properties或application.yml等配置文件,我们可以在启动时加载其他配置文件。

  • 自定义bean。我们可以通过@Component注解创建自定义的bean以及其他的@SpringBootAnnotation注解,来实现更灵活的配置和自动化初始化。

  • 执行一些初始化逻辑。我们可以对应用程序的数据库、缓存、MQ等进行初始化,例如创建数据源、初始化缓存等,以确保应用程序正常运行,并且可以通过ApplicationRunnerCommandLineRunner等干预代码的方式执行这些初始化逻辑。

  • 执行一些后置操作。在Spring Boot应用程序停止后执行一些清理工作,例如关闭数据源、释放缓存等。

这些干预步骤可以在Spring Boot应用程序启动和停止完成后进行,从而实现更灵活的配置和初始化。

(一)ApplicationContextInitializer扩展

通过实现ApplicationContextInitializer接口,我们可以在ApplicationContext创建之前对其进行一些定制化的修改。这个接口定义了一个initialize方法,接受一个ConfigurableApplicationContext对象作为参数,我们可以在这个方法中对这个对象进行修改和配置。

具体来说,我们可以通过ApplicationContextInitializer实现以下扩展任务:

  • 修改Spring Boot默认的environment属性。使用configurableApplicationContext.getEnvironment()方法获取到environment对象,从而修改环境变量,例如添加自定义配置文件路径。

  • 添加自定义的PropertySource。使用environment.getPropertySources().addLast(propertySource)方法,可以添加自定义的属性源,从而实现更灵活的配置。

  • 注册自定义bean。使用configurableApplicationContext.getBeanFactory().registerSingleton(beanName, bean)方法,可以注册自定义的bean,从而实现更灵活的依赖注入。

可以添加自定义的ApplicationContextInitializer实现类,从而扩展应用程序的初始化逻辑。

总之,通过实现ApplicationContextInitializer接口,可以在Spring Boot应用程序启动之前对应用程序进行一些初始化定制化的操作,从而满足开发者对应用程序的特殊需求。

修改Spring Boot默认的environment属性

修改Spring Boot默认的environment属性,例如添加自定义配置文件路径,可以通过实现ApplicationContextInitializer接口来完成。下面是一个示例代码,演示如何添加自定义配置文件路径:

package org.zyf.javabasic.springextend.runext;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.ResourcePropertySource;import java.io.IOException;/*** @author yanfengzhang* @description 修改Spring Boot默认的environment属性。使用configurableApplicationContext.getEnvironment()方法获取到environment对象,从而修改环境变量,例如添加自定义配置文件路径。*/
public class InterveneApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic void initialize(ConfigurableApplicationContext configurableApplicationContext) {ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();// 添加自定义配置文件路径try {ObjectMapper objectMapper = new ObjectMapper();System.out.println("InterveneApplicationContextInitializer initialize :" + configurableApplicationContext);environment.getPropertySources().addFirst(new ResourcePropertySource("classpath:zyftest.properties"));System.out.println("InterveneApplicationContextInitializer initialize add FirstResourcePropertySource  classpath:zyftest.properties");} catch (IOException e) {e.printStackTrace();}}
}

在上面的代码中,我们通过getEnvironment()方法获取到ConfigurableEnvironment对象,然后通过getPropertySources()方法获取到属性源,使用addFirst()方法将自定义的custom.properties文件的PropertySource添加到属性源的首位。这样,在应用程序启动时,就会首先加载custom.properties文件,从而实现了自定义的配置。

需要注意的是,上述代码中的InterveneApplicationContextInitializer需要被注册才能生效。可以通过在src/main/resources/META-INF/spring.factories文件中指定注册项的方式来注册:

org.springframework.context.ApplicationContextInitializer=
org.zyf.javabasic.springextend.runext.InterveneApplicationContextInitializer

添加自定义的PropertySource

添加自定义的PropertySource,可以通过实现ApplicationContextInitializer接口来完成。下面是一个示例代码,演示如何添加自定义的PropertySource:

package org.zyf.javabasic.springextend.runext;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.ResourcePropertySource;import java.io.IOException;/*** @author yanfengzhang* @description 添加自定义的PropertySource。使用environment.getPropertySources().addLast(propertySource)方法,可以添加自定义的属性源,从而实现更灵活的配置。*/
public class InterveneApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic void initialize(ConfigurableApplicationContext configurableApplicationContext) {// 添加自定义的PropertySourcePropertySource<?> propertySource = new MyPropertySource("myPropertySource");environment.getPropertySources().addLast(propertySource);System.out.println("InterveneApplicationContextInitializer initialize add PropertySource  myPropertySource");}// 自定义PropertySourceprivate static class MyPropertySource extends PropertySource<String> {private static final String MY_PROPERTY_SOURCE_KEY = "my.property.source.key";public MyPropertySource(String name) {super(name);}@Overridepublic Object getProperty(String name) {if (MY_PROPERTY_SOURCE_KEY.equals(name)) {return "myPropertySourceValue";}return null;}}
}

在上面的代码中,我们新建了一个名为MyPropertySource的自定义PropertySource,然后在initialize方法中使用environment.getPropertySources().addLast(propertySource)方法将其添加到Spring环境中。

MyPropertySource中实现了一个用于获取属性的getProperty方法,在这个方法中,我们指定了一个名为my.property.source.key的属性及其对应的值,这样就可以通过@Value("${my.property.source.key}")的方式在应用程序中获取到它的值了。

需要注意的是,上述代码中的InterveneApplicationContextInitializer需要被注册才能生效。具体如上部分展示,这里不在展示。

注册自定义bean

注册自定义Bean,可以通过实现ApplicationContextInitializer接口来完成。下面是一个示例代码,演示如何注册自定义Bean:

package org.zyf.javabasic.springextend.runext;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.ResourcePropertySource;import java.io.IOException;/*** @author yanfengzhang* @description 注册自定义bean。使用configurableApplicationContext.getBeanFactory().registerSingleton(beanName, bean)方法,可以注册自定义的bean,从而实现更灵活的依赖注入。*/
public class InterveneApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic void initialize(ConfigurableApplicationContext configurableApplicationContext) {ConfigurableListableBeanFactory beanFactory = configurableApplicationContext.getBeanFactory();// 注册自定义BeanMyBean myBean = new MyBean();beanFactory.registerSingleton("myBean", myBean);System.out.println("InterveneApplicationContextInitializer initialize registerSingleton  myBean");}// 自定义Beanprivate static class MyBean {private String name = "myBean";public String getName() {return name;}}
}

在上面的代码中,我们新建了一个名为MyBean的自定义Bean,然后在initialize方法中使用beanFactory.registerSingleton("myBean", myBean)方法将其注册到Spring应用程序上下文中,"myBean"是注册的bean名称,myBean是实际的类实例对象。

需要注意的是,上述代码中的InterveneApplicationContextInitializer需要被注册才能生效。具体如上部分展示,这里不在展示。

(二)SpringApplicationRunListener扩展

SpringApplicationRunListener是Spring Boot的一个事件监听器,用于在应用程序启动和停止时执行一些操作。可能需要自定义SpringApplicationRunListener来执行某些特定操作。下面是一个示例,演示如何扩展SpringApplicationRunListener以添加自定义操作:

首先,需要实现SpringApplicationRunListener接口:

package org.zyf.javabasic.springextend.runext;import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;/*** @author yanfengzhang* @description SpringApplicationRunListener是Spring Boot的一个事件监听器,用于在应用程序启动和停止时执行一些操作。* 可能需要自定义SpringApplicationRunListener来执行某些特定操作。* 下面是一个示例,演示如何扩展SpringApplicationRunListener以添加自定义操作*/
public class IntervenRunListener implements SpringApplicationRunListener {private final SpringApplication application;private final String[] args;public IntervenRunListener(SpringApplication application, String[] args) {this.application = application;this.args = args;}@Overridepublic void starting() {System.out.println("IntervenRunListener starting");}@Overridepublic void environmentPrepared(ConfigurableEnvironment environment) {System.out.println("IntervenRunListener environmentPrepared");}@Overridepublic void contextPrepared(ConfigurableApplicationContext context) {System.out.println("IntervenRunListener contextPrepared");}@Overridepublic void contextLoaded(ConfigurableApplicationContext context) {System.out.println("IntervenRunListener contextLoaded");}@Overridepublic void started(ConfigurableApplicationContext context) {System.out.println("IntervenRunListener started");}@Overridepublic void running(ConfigurableApplicationContext context) {System.out.println("IntervenRunListener running");}@Overridepublic void failed(ConfigurableApplicationContext context, Throwable exception) {System.out.println("IntervenRunListener failed");}
}

在上述代码中,我们新建了一个名为IntervenRunListener的自定义SpringApplicationRunListener,并在starting()environmentPrepared()contextPrepared()contextLoaded()started()running()failed()方法中添加了自定义操作。

接下来,我们需要告诉Spring Boot使用这个自定义的SpringApplicationRunListener,上述代码中的IntervenRunListener需要被注册才能生效。可以通过在src/main/resources/META-INF/spring.factories文件中指定注册项的方式来注册:

org.springframework.boot.SpringApplicationRunListener=
org.zyf.javabasic.springextend.runext.IntervenRunListener

(三)ApplicationRunner扩展

ApplicationRunner是Spring Boot提供的一种扩展点,它允许在Spring Boot应用程序启动时执行一些预配置操作。这些操作可以包括预热缓存,初始化数据库连接等。以下是一些用途:

  • 数据库初始化: ApplicationRunner可以用于执行数据库初始化操作。例如,我们可以在应用程序启动时创建数据库表格,插入初始数据等操作。这对于确保数据库的正确性和可用性非常有用,以及为整个应用程序提供更好的可维护性。

  • 缓存预热: 缓存在应用程序中非常重要,它可以大大提高应用程序的性能。但是,由于缓存通常是“懒加载”的,所以在应用程序第一次使用它们时,需要花费一些时间来加载它们。使用ApplicationRunner,我们可以在应用程序启动后立即加载缓存,而不是等到应用程序第一次使用它们时加载。这可以大大减少应用程序响应时间,并提高用户体验。

  • 环境检查: 为了确保应用程序能够正常运行,我们需要检查它所在的环境是否满足应用程序的要求。例如,我们可能需要检查数据库是否可用,检查文件系统是否可写等。使用ApplicationRunner,我们可以在应用程序启动时立即执行这些检查,并在应用程序无法正常运行时采取适当的措施,如打印警告或抛出异常。

总之,ApplicationRunner可以用于执行任何需要在应用程序启动时进行的操作。它为应用程序提供了一种简单的扩展点,可以使我们轻松地实现预配置和初始化操作。

以下从缓存预热和环境检查给出简单的代码示例。

缓存预热

在应用程序启动后立即加载缓存,可以避免在应用程序第一次使用缓存时的延迟。以下是一个简单的示例,演示如何使用ApplicationRunner在应用程序启动时加载缓存:

package org.zyf.javabasic.springextend.runext;import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;/*** @author yanfengzhang* @description 演示如何使用ApplicationRunner在应用程序启动时加载缓存*/
@Component
public class CacheInitiator implements ApplicationRunner {@Overridepublic void run(ApplicationArguments args) throws Exception {System.out.println("CacheInitiator cache deal!");}
}

在应用程序启动时,ApplicationRunner接口的run方法将被自动调用,从而将一些初始数据加载到缓存中。当应用程序需要访问缓存时,它们可以立即从缓存中获取数据,而不必等待它们被“懒加载”的时间。这将大大提高应用程序的性能和用户体验。

环境检查

以下是一个简单的示例,演示如何使用ApplicationRunner在应用程序启动时执行环境检查:

package org.zyf.javabasic.springextend.runext;import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;/*** @author yanfengzhang* @description 演示如何使用ApplicationRunner在应用程序启动时执行环境检查*/
@Component
public class EnvironmentChecker implements ApplicationRunner {@Overridepublic void run(ApplicationArguments args) throws Exception {// 检查数据库是否可用System.out.println("EnvironmentChecker DatabaseConnection checkConnection! ");// 检查文件系统是否可写System.out.println("EnvironmentChecker FileStorage checkWriteAccess! ");}
}

在应用程序启动时,ApplicationRunner接口的run方法将被自动调用,从而执行环境检查操作。当应用程序无法正常运行时,它们将抛出一个运行时异常,包含适当的错误消息,以帮助我们进行故障排除和修复操作。

(四)CommandLineRunner扩展

CommandLineRunner接口用于在应用程序启动时执行一些命令行操作。这在调试应用程序、自动化部署、初始化系统配置等方面非常有用。

在较复杂的业务场景下,我们可以使用CommandLineRunner接口来扩展应用程序。

首先,我们可以将一些常用的业务逻辑封装在命令行工具中,然后在应用程序启动时通过执行这些命令来进行操作。例如,我们可以创建一个名为UserImportCommand的命令行工具,用于导入用户数据到应用程序中。在应用程序启动时,可以执行UserImportCommand来导入用户数据。

其次,我们可以使用CommandLineRunner接口来定制化应用程序的启动过程。例如,我们可以创建一个名为StartupTasks的类,并实现CommandLineRunner接口。在run方法中,我们可以执行任何我们需要在应用程序启动时完成的任务,如加载配置文件、初始化缓存等。

最后,我们可以结合使用ApplicationRunnerCommandLineRunner来完成更复杂的任务。例如,我们可以创建一个名为InitializationRunner的类,实现ApplicationRunnerCommandLineRunner接口,并在其中执行所有初始化任务。这样,在应用程序启动时,不仅可以自动执行初始化任务,还可以通过命令行手动执行这些任务。

综上所述,CommandLineRunner接口在业务扩展方面有着广阔的应用前景,在实际业务场景中,可以根据自己的需求进行灵活应用和扩展。

应用举例一:UserImportCommand命令行工具

以下是一个简单的示例,演示如何使用CommandLineRunner接口创建一个命令行工具,用于导入用户数据到应用程序中。

package org.zyf.javabasic.springextend.runext;import com.google.common.collect.Lists;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.zyf.javabasic.common.User;import java.util.List;/*** @author yanfengzhang* @description 演示如何使用CommandLineRunner接口创建一个名为UserImportCommand的命令行工具,用于导入用户数据到应用程序中。*/
@Component
public class UserImportCommand implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {List<User> users = readUserFromFile("fileName");System.out.println("UserImportCommand readUserFromFile importUsers!");}// 从数据文件中读取用户信息private List<User> readUserFromFile(String fileName) {// 省略代码,从文件中读取用户信息,返回一个User对象列表return Lists.newArrayList();}
}

在应用程序启动时,CommandLineRunner接口的run方法将被自动调用,并将命令行参数作为字符串数组传递给run方法。

上面展示的时候并没有使用入参,当需要导入用户数据时,可以执行如下命令:

java -jar myapp.jar user-import users.txt

其中,myapp.jar是应用程序运行的jar包,user-import是命令行工具的名称,users.txt是要导入的用户数据文件名。

应用举例二:定制化应用程序的启动过程

可以使用CommandLineRunner接口来定制化应用程序的启动过程。下面是一个示例,演示如何使用CommandLineRunner来执行一些自定义启动任务:

package org.zyf.javabasic.springextend.runext;import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;/*** @author yanfengzhang* @description 演示如何使用CommandLineRunner来执行一些自定义启动任务*/
@Component
public class StartupTasks implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {// 加载应用程序配置System.out.println("StartupTasks configService loadConfig");// 初始化缓存System.out.println("StartupTasks cacheService initialize");}
}

在应用程序启动时,CommandLineRunner接口的run方法将被自动调用,并且我们的自定义启动任务将会被执行。通过使用CommandLineRunner接口,我们可以执行任何自定义的启动任务,以满足特定的应用程序需求。

(五)所有扩展验证

验证以上配置是否生效很简单,只需要启动程序查看打印效果即可,以下给出一些基本的展示效果:

 

30d4f51b010d230f26a2b77b821a1965.png

 

790d25703c38376cc740308d99d7af76.png

三、加快SpringBoot项目启动

公司级大多数SpringBoot 项目在日常开发过程中发现服务启动过程异常缓慢,常常需要4-7分钟才能暴露端口,有的时候甚至在十分钟左右,严重降低开发效率。

 

dafb40f8c73e4af1c6e2f1baa674dbe4.png

若要优化 Spring Boot 程序的启动时间以缩短启动时间,可以考虑以下几个方面的优化措施:

  • 减少依赖项: 评估项目的依赖项,并确保只引入必要的依赖。较多的依赖项可能会增加启动时间,因为它们需要被扫描和初始化。通过删除不需要的依赖项或仅引入必要的模块,可以减少类路径的扫描和初始化时间。

  • 调整自动配置: Spring Boot 的自动配置是一个强大的特性,但有时可能会引入不必要的组件和功能。通过调整自动配置,可以精确地指定所需的配置,避免加载不必要的组件,从而减少启动时间。

  • 启用懒加载: 将一些不常用的组件设置为懒加载,即在需要时才进行初始化。通过懒加载,可以避免在启动阶段初始化不必要的组件,从而加快启动时间。可以使用 Spring Framework 的 @Lazy 注解或在配置类中进行相应的配置。

  • 启用编译时优化: 使用 Spring Boot 2.4 及更高版本,你可以通过启用编译时优化来加快启动时间。通过在 pom.xml 文件中设置 <compilerArgs> 属性,使用 --add-opens 选项来启用编译时优化。这可以减少反射操作的开销,从而提高启动性能。

  • 调整日志级别: Spring Boot 默认启用了相对较高的日志级别,这可能会导致大量的日志输出,从而增加启动时间。通过将日志级别调整为更低的级别,如将 INFO 调整为 WARN,可以减少日志输出,从而缩短启动时间。

  • 使用缓存: Spring Boot 在启动过程中会进行多个步骤的扫描和初始化。通过使用缓存机制,可以缓存一些元数据和初始化结果,避免重复的扫描和初始化操作,从而提高启动性能。可以使用 Spring Boot 的缓存机制或其他缓存库来实现。

这些是一些常见的优化措施,可以帮助缩短 Spring Boot 程序的启动时间。然而,优化的效果取决于具体的应用程序和场景,因此建议根据实际情况进行评估和测试。

(一)减少依赖项以缩短启动时间

减少依赖项具体的分析和说明

  • 评估依赖项: 首先,需要仔细评估项目的依赖项。查看项目的 pom.xml(如果使用Maven)或 build.gradle(如果使用Gradle)文件,以了解所有引入的依赖项。检查每个依赖项的用途和必要性。

  • 删除不需要的依赖项: 确定哪些依赖项是不需要的或没有被项目使用的。这些无用的依赖项可能会增加类路径的扫描和初始化时间,从而拖慢启动过程。可以通过移除或注释掉不需要的依赖项来减少不必要的加载和初始化。

  • 仅引入必要的模块: 对于一些大型的依赖项,例如Spring Boot的模块,可以根据项目的需求仅引入必要的模块。Spring Boot提供了模块化的方式,允许你选择性地引入只需要的模块。通过仅引入必要的模块,可以减少初始化过程中的扫描和加载时间。

  • 排除不必要的传递依赖项: 一些依赖项会引入其他的传递依赖项。如果这些传递依赖项不是项目所需的,可以通过在依赖项的配置中排除它们来减少类路径的扫描和初始化。这可以通过在项目的构建文件中配置<exclusions>标签来实现。

  • 使用更轻量级的替代方案: 有时,某个依赖项可能有较重的启动开销。在评估依赖项时,可以考虑使用更轻量级的替代方案,以减少启动时间。例如,对于某个功能或工具库,可能有多个不同的实现可供选择,可以选择具有较轻量级的实现。

总的来说,通过评估和优化项目的依赖项,可以减少不必要的加载和初始化过程,从而减少启动时间。这需要仔细分析每个依赖项的用途,并确保只引入必要的依赖项和模块。同时,需要保持项目的功能完整性和正确性,确保删除的依赖项不会影响项目的正常运行。

减少依赖项案例分析

当涉及到减少依赖项以缩短启动时间的案例分析,我们以一个简单的Web应用为例,假设我们有一个基于Spring Boot的Web应用,该应用使用了以下依赖项:

  • spring-boot-starter-web:用于构建Web应用程序的基本依赖项。

  • spring-boot-starter-data-jpa:用于与数据库进行交互的依赖项。

  • spring-boot-starter-security:用于添加安全性功能的依赖项。

  • spring-boot-starter-test:用于测试的依赖项。

在评估这些依赖项后,我们注意到以下情况:

  • 项目中并没有使用与数据库交互的功能,因此 spring-boot-starter-data-jpa 可能是不必要的依赖项。

  • 在项目中并没有实现任何安全性功能,因此 spring-boot-starter-security 也可能是不必要的依赖项。

基于以上分析,我们可以采取以下措施来优化启动时间:

  • 删除不需要的依赖项: 从项目的构建文件(如pom.xml)中删除不需要的依赖项,即 spring-boot-starter-data-jpa 和 spring-boot-starter-security

  • 清理类路径扫描和初始化: 由于删除了不必要的依赖项,应用程序在启动时不再需要扫描和初始化与数据库和安全性相关的组件,从而减少启动时间。

  • 进行相关测试: 在进行以上更改后,确保对应用程序进行全面的测试,以确保没有因删除依赖项而引起的不可预料的问题。

通过这样的优化措施,我们可以减少应用程序的启动时间,特别是在大型项目中,当引入了大量不必要的依赖项时,效果会更为显著。然而,需要注意,每个项目都有其特定的依赖项和功能需求,因此优化策略可能会有所不同。

(二)调整自动配置以缩短启动时间

调整自动配置具体分析说明

  • 了解自动配置机制: 首先,了解 Spring Boot 的自动配置机制是很重要的。Spring Boot 使用条件化配置来根据项目的依赖和配置来自动决定哪些组件应该被加载和配置。条件注解(如 @ConditionalOnClass@ConditionalOnProperty 等)用于根据特定的条件来决定组件是否应该被自动配置。

  • 检查自动配置类: 查看自动配置类,了解每个自动配置类所做的配置和加载的组件。可以在 Spring Boot 的官方文档或源代码中找到自动配置类的详细信息。对于不必要的组件,可以尝试找到对应的自动配置类并进行分析。

  • 排除不必要的自动配置: 通过使用 @EnableAutoConfiguration 注解的 exclude 属性,可以排除不需要的自动配置类。这可以在主应用程序类上进行配置。通过排除不必要的自动配置类,可以避免加载不需要的组件和功能,从而减少启动时间。

  • 自定义自动配置: 如果发现某个自动配置类加载了不必要的组件,但又需要其他配置,可以自定义自动配置类。通过创建一个带有 @Configuration 注解的配置类,并在其中指定所需的配置,可以覆盖默认的自动配置。这样可以精确地指定所需的配置,避免加载不必要的组件。

  • 配置条件: 有些自动配置类提供了条件注解,可以使用这些条件注解来根据项目的需求进行配置。例如,使用 @ConditionalOnProperty 注解可以根据配置属性的值来决定是否应用该自动配置。通过合理配置这些条件,可以避免加载不必要的组件。

通过调整自动配置,可以精确地指定所需的配置,避免加载不必要的组件和功能,从而减少启动时间。需要注意的是,在调整自动配置时,确保对应用程序进行全面的测试,以确保所需的功能和组件仍然正常工作。

调整自动配置案例分析

假设我们有一个基于Spring Boot的Web应用,该应用使用了以下自动配置:

  • spring-boot-starter-web:用于构建Web应用程序的基本自动配置。

  • spring-boot-starter-data-jpa:用于与数据库进行交互的自动配置。

  • spring-boot-starter-security:用于添加安全性功能的自动配置。

在评估这些自动配置后,我们注意到以下情况:

  • 项目中并没有使用与数据库交互的功能,因此 spring-boot-starter-data-jpa 可能是不必要的自动配置。

  • 在项目中并没有实现任何安全性功能,因此 spring-boot-starter-security 也可能是不必要的自动配置。

基于以上分析,我们可以采取以下措施来优化启动时间:

排除不必要的自动配置:在主应用程序类上使用 @EnableAutoConfiguration 注解的 exclude 属性,排除 spring-boot-starter-data-jpa 和 spring-boot-starter-security 的自动配置。

@SpringBootApplication(exclude = {DataJpaAutoConfiguration.class, SecurityAutoConfiguration.class})
public class MyApplication {// ...
}

自定义自动配置:如果需要其他的配置,可以创建一个自定义的自动配置类,并在其中指定所需的配置。例如,对于与数据库交互的功能,可以创建一个自定义的配置类,仅包含必要的配置项。

@Configuration
public class CustomDataJpaAutoConfiguration {// ...
}

通过以上优化措施,我们排除了不必要的自动配置,并自定义了所需的配置,避免加载不必要的组件和功能,从而减少启动时间。这种方式可以根据项目的实际需求灵活调整自动配置,以提高启动性能。然而,需要注意,在进行以上更改后,确保对应用程序进行全面的测试,以确保所需的功能和组件仍然正常工作。

(三)启用懒加载以缩短启动时间

启用懒加载具体分析说明

启用懒加载是一种有效的方法来减少启动时间,只有在需要时才初始化不常用的组件。以下是具体分析说明:

  • 确定需要懒加载的组件: 仔细评估项目中的组件,并确定哪些组件是不常用的,可以延迟加载。这些组件可能包括大型的第三方库、复杂的初始化过程或与特定功能相关的组件。

  • 使用 @Lazy 注解: 在需要懒加载的组件上使用 Spring Framework 的 @Lazy 注解。将 @Lazy 注解应用于组件的声明处,以指示该组件应该在需要时才进行初始化。例如:

@Component
@Lazy
public class MyLazyComponent {// ...
}
  • 在配置类中进行配置: 如果你使用的是配置类来进行组件的配置,你可以在配置类的方法上使用 @Lazy 注解,将需要懒加载的组件进行标记。例如:

@Configuration
public class MyConfig {@Bean@Lazypublic MyLazyComponent myLazyComponent() {return new MyLazyComponent();}
}
  • 重新构建和运行应用程序: 保存更改后,重新构建并运行应用程序。在启动过程中,被标记为懒加载的组件将不会立即初始化,只有在首次访问时才会进行初始化。这样可以避免启动阶段初始化不必要的组件,从而加快启动时间。

通过启用懒加载,可以延迟初始化不常用的组件,减少启动时间。这在项目中特别适用于那些具有复杂初始化过程或与特定功能相关的组件。需要注意的是,懒加载的组件在第一次访问时可能会引入额外的延迟,因此在评估和标记组件时要权衡性能和实际需求。

总结起来,通过使用 Spring Framework 的 @Lazy 注解或在配置类中进行相应的配置,可以启用懒加载,从而在需要时才初始化不常用的组件,加快应用程序的启动时间。

启用懒加载案例分析

假设我们有一个基于 Spring Boot 的电子商务应用程序,其中包含一些复杂的服务和组件。我们注意到启动时间较长,并且发现其中一个原因是某些组件的初始化过程相对较慢。

在进一步分析后,我们确定以下组件是不常用的,并且可以通过懒加载来延迟初始化:

  • PaymentService:用于处理支付的服务组件。

  • SearchService:用于执行商品搜索的服务组件。

这两个组件在启动时不是必需的,因为它们只在用户执行特定操作时才会被使用。

我们可以使用 Spring Framework 的 @Lazy 注解来标记这些组件,将它们设置为懒加载。

@Service
@Lazy
public class PaymentService {// ...
}@Service
@Lazy
public class SearchService {// ...
}

通过在组件的声明处添加 @Lazy 注解告诉 Spring 框架在需要使用这些组件时才进行初始化。

重新构建并运行应用程序后,启动阶段将不会立即初始化 PaymentService 和 SearchService 组件。只有当用户执行相关操作时,才会触发它们的初始化。这样可以减少启动时间,并且在应用程序启动后,只有真正需要时才会占用资源。

需要注意的是,在标记组件为懒加载时,确保仔细评估其对应用程序功能的影响。确保懒加载的组件在实际使用时能够正确地被初始化,并且不会导致功能上的问题。

(四)启用编译时优化以缩短启动时间

启用编译时优化是一种有效的方法来加快 Spring Boot 应用程序的启动时间,特别是在使用 Spring Boot 2.4 及更高版本时。以下是具体的分析和说明:

1.确认使用的 Spring Boot 版本: 确保你的项目使用的是 Spring Boot 2.4 或更高版本。编译时优化功能是在 Spring Boot 2.4 中引入的。

2.配置 pom.xml 文件: 在项目的 pom.xml 文件中,找到 <build> 元素,并在其中的 <plugins> 元素下添加 Maven Compiler 插件配置。在 Maven Compiler 插件配置中,使用 <compilerArgs> 属性来设置编译器选项。

<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><compilerArgs><arg>--add-opens</arg><arg>java.base/java.lang=ALL-UNNAMED</arg><arg>--add-opens</arg><arg>java.base/java.util=ALL-UNNAMED</arg><!-- 添加其他需要的 --add-opens 选项 --></compilerArgs></configuration></plugin></plugins>
</build>

在 <compilerArgs> 属性中,我们使用 --add-opens 选项来指定需要开放的包和模块。上述示例中,我们设置了两个 --add-opens 选项,分别是 java.lang 和 java.util 包。你还可以根据需要添加其他的 --add-opens 选项,以开放其他需要的包和模块。

3.重新构建应用程序: 保存更改后,重新构建应用程序。在编译过程中,编译器将使用指定的编译器选项,启用编译时优化。

启用编译时优化可以减少反射操作的开销,从而提高应用程序的启动性能。它通过使用 --add-opens 选项来开放特定的包和模块,使得一些反射操作可以直接访问,而无需通过反射 API。

需要注意的是,编译时优化可能会因为项目的具体情况而有所不同。在应用该优化时,需要进行测试和评估,确保应用程序在启用编译时优化后仍然能够正常运行。

总结起来,通过在 pom.xml 文件中配置 Maven Compiler 插件,使用 <compilerArgs> 属性设置 --add-opens 选项,可以启用编译时优化来加快 Spring Boot 应用程序的启动时间。根据项目的需求,你可以添加其他需要的 --add-opens 选项,以满足特定的优化需求。

(五)调整日志级别以缩短启动时间

调整日志级别是一种简单而有效的方法来减少启动时间,特别是当应用程序的日志输出量较大时。以下是具体的分析和说明:

1.确定适当的日志级别: 仔细评估你的应用程序,并确定适当的日志级别。通常,将日志级别从较高的级别(如 DEBUG 或 INFO)调整为较低的级别(如 WARN 或 ERROR)是一种常见的优化策略。较低的日志级别会减少输出的日志消息数量。

2.配置日志级别: 在 Spring Boot 应用程序的配置文件(如 application.properties 或 application.yml)中,找到与日志相关的配置项。根据你使用的日志框架(如 Logback、Log4j2 或 JUL),进行相应的配置。例如,对于 Logback,你可以在 application.properties 文件中添加以下配置:

logging.level.root=WARN

或者在 application.yml 文件中添加以下配置:

logging:level:root: WARN

这将将根日志记录器的级别设置为 WARN,从而只输出 WARN 级别及以上的日志消息。

3.重新构建和运行应用程序: 保存更改后,重新构建并运行应用程序。在启动过程中,日志框架将根据配置的日志级别来决定是否输出特定级别的日志消息。通过将日志级别调整为较低的级别,可以减少输出的日志消息数量,从而加快启动时间。

需要注意的是,调整日志级别可能会导致在应用程序运行时缺少某些重要的日志信息。因此,在进行日志级别调整时,应权衡性能和需要的日志信息,并确保不会影响应用程序的正常运行和故障排查。

总结起来,通过将日志级别调整为较低的级别,如将 INFO 调整为 WARN,可以减少日志输出量,从而缩短应用程序的启动时间。在调整日志级别时,需要确保仍能获取到足够的日志信息以满足应用程序的需求。

(六)使用缓存以缩短启动时间

使用缓存是一种有效的方法来提高 Spring Boot 应用程序的启动性能。通过缓存一些元数据和初始化结果,可以避免重复的扫描和初始化操作,从而减少启动时间。以下是具体的分析和说明:

1.使用 Spring Boot 的缓存机制: Spring Boot 提供了内置的缓存机制,可以通过使用 @EnableCaching 注解来启用缓存功能。在需要缓存的组件或方法上添加 @Cacheable 注解,以指示该组件或方法的结果应该被缓存。

@EnableCaching
@SpringBootApplication
public class MyApplication {// ...
}@Service
public class MyService {@Cacheable("myCache")public MyData getData(String key) {// 从数据库或其他地方获取数据// ...}
}

在上述示例中,我们通过在 MyService 的 getData() 方法上添加 @Cacheable("myCache") 注解,指定该方法的结果应该被缓存,并使用名为 "myCache" 的缓存区域。通过缓存结果,当多次调用 getData() 方法时,如果相同的参数值被传递进来,将会直接从缓存中获取结果,而不需要重复的数据获取操作。

2.使用其他缓存库: 除了使用 Spring Boot 的缓存机制外,你还可以选择使用其他的缓存库,如 Ehcache、Redis 或 Caffeine。这些缓存库提供了更高级的缓存功能和配置选项,可以满足更复杂的需求。例如,使用 Ehcache 作为缓存库,你可以在应用程序的配置文件中配置 Ehcache 缓存:

<dependency><groupId>org.ehcache</groupId><artifactId>ehcache</artifactId>
</dependency>

然后,在需要缓存的方法上使用 Ehcache 注解,如 @org.ehcache.Cacheable。使用其他缓存库时,需要根据库的文档和配置方式来进行相应的设置和使用。

无论是使用 Spring Boot 的缓存机制还是其他缓存库,通过缓存一些元数据和初始化结果,可以避免重复的扫描和初始化操作,从而减少启动时间。但需要注意的是,在使用缓存时,要确保缓存的数据和结果的一致性,并考虑缓存的过期策略和清理机制,以避免缓存数据过时或占用过多内存。

总结起来,使用缓存是一种优化启动时间的有效方法。可以使用 Spring Boot 的缓存机制或其他缓存库来实现缓存功能,并根据具体需求和库的使用方式进行配置和使用。通过缓存一些元数据和初始化结果,可以避免重复操作,提高应用程序的启动性能。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/717999.shtml

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

相关文章

python celery beat实现定时任务

在Celery在python中的应用除了实现异步任务&#xff08;async task)外也可以执行定时任务(beat) 1.Celery定时任务是什么&#xff1f; Celery默认任务单元由任务生产者触发,但有时可能需要其自动触发, 而beat进程正是负责此类任务,能够自动触发定时/周期性任务. 只需要在配置…

吴恩达deeplearning.ai:学习曲线决定下一步怎么做

以下内容有任何不理解可以翻看我之前的博客哦&#xff1a;吴恩达deeplearning.ai专栏 学习曲线是一种图形表示方法&#xff0c;用于展示模型在训练过程中的学习表现&#xff0c;即模型的训练集和验证集上的性能如何随着训练时间的增加而变化。可以帮助我们了解模型的学习进度。…

制作耳机壳的UV树脂和塑料材质哪一个成本更高一些?

总体来说&#xff0c;制作耳机壳的UV树脂的成本可能会略高于塑料材质。 原材料成本&#xff1a;UV树脂通常是通过复杂的合成过程制成的。这些过程不仅需要大量的能源投入&#xff0c;还需要较高水平的技术和设备支持&#xff0c;因此原材料成本较高。相比之下&#xff0c;塑料…

04-prometheus服务的动态发现

一、概述 目前&#xff0c;我们每增加一个被监控的节点&#xff0c;就需要修改prometheus的配置文件&#xff0c;然后重新加载prometheus服务&#xff0c;这种方式比较繁琐&#xff0c;每次新增、删除被监控节点都需要重新操作一遍&#xff0c;不适合生产环境的大规模监控架构&…

Go-zero中分布式事务的实现(DTM分布式事务管理器,在一个APi中如何调用两个不同服务的rpc层,并保证两个不同服务之间的业务逻辑同时成功)

涉及到的相关技术 1.DTM分布式事务管理器,解决跨数据库、跨服务、跨语言栈更新数据的一致性问题。 2.SAGA事务模式,SAGA事务模式是DTM中常用的一种模式,简单易上手.(当然还有其它更多的事务模式,这里采用的SAGA只不过是其中一种较为简单的方法) 3.Go-zero框架,ETCD服务注册... …

Windows 2012 设置 nginx 开机自启动(适用于windows2012/10)

Windows 2012 设置 nginx 开机自启动&#xff08;适用于windows2012/10&#xff09;https://www.cnblogs.com/xuegqcto/articles/7521483.html 在windows server 2012上安装nginx&#xff0c;同时配置开机自启动服务&#xff08;推荐使用“Windows Service Wrapper”工具&…

【Linux】线程概念|线程理解|线程控制

文章目录 线程概念Linux中线程是否存在的讨论线程创建和线程控制线程的终止和等待&#xff08;三种终止方式 pthread_join()的void**retval&#xff09; 线程概念 线程就是进程内部的一个执行流&#xff0c;线程在进程内运行&#xff0c;线程在进程的地址空间内运行&#xff0…

LeetCode-第14题-最长公共前缀

1.题目描述 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 ""。 2.样例描述 3.思路描述 按字符串数组每个数组的长度&#xff0c;将字符串数组从小到大排序&#xff1b;他们的公共前缀一定小于或等于最长元素长度…

2024年智能驾驶年度策略:自动驾驶开始由创造型行业转向工程型行业

感知模块技术路径已趋于收敛&#xff0c;自动驾驶从创造型行业迈向工程型行业。在特斯拉的引领下&#xff0c;国内主机厂2022年以来纷纷跟随特斯拉相继提出“重感知、轻地图”技术方案&#xff0c;全球自动驾驶行业感知模块技术路径从百花齐放开始走向收敛。我们认为主机厂智能…

2023.3.3周报

目录 摘要 一、文献阅读 1、题目 2、摘要 3、模型架构 4、文献解读 一、Introduction 二、实验 三、结论 二、PINN 一、PINN比传统数值方法有哪些优势 二、PINN方法 三、正问题与反问题 三、PINN实验 一、数学方程 二、模型搭建 总结 摘要 本周我阅读了一篇…

Postman上传文件的操作方法

前言 调用某个接口&#xff0c;测试上传文件功能。一时间不知如何上传文件&#xff0c;本文做个操作记录&#xff0c;期望与你有益。 步骤一、设置Headers key:Content-Type value:multipart/form-data 步骤二、设置Body 选择form-data key:file下拉框选择file类型value&…

STM32(8)NVIC编程

中断源由部分片上外设产生 在misc.h中找&#xff0c;杂项 配置NVIC GPIO和AFIO不能产生中断源&#xff0c;但能通过EXTI&#xff0c;由EXTI产生中断源 NVIC不需要开启时钟&#xff0c;因为NVIC模块位于内核内部&#xff0c;芯片一上电就能工作。 中断响应函数 中断向量表在启…

Java:JVM基础

文章目录 参考JVM内存区域程序计数器虚拟机栈本地方法栈堆方法区符号引用与直接引用运行时常量池字符串常量池直接内存 参考 JavaGuide JVM内存区域 程序计数器 程序计数器是一块较小的内存空间&#xff0c;可以看做是当前线程所执行的字节码的行号指示器&#xff0c;各线程…

Unity 常用的4种灯光、制作镜子、灯光的调用修改数值、

创建灯光时&#xff0c;一般用4种&#xff1a;定向光、点光源、聚光、区域光、 定向光&#xff1a;太阳 点光源&#xff1a;灯泡 聚光灯&#xff1a;手电筒 区域光&#xff1a;烘焙-贴图 灯光选择已烘焙 需要先选择被烘焙的物体&#xff0c;然后再选择Contribute GI 等待进…

java中的set

Set Set集合概述和特点 不可以存储重复元素 没有索引,不能使用普通for循环遍历 哈希值 哈希值简介 是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值 如何获取哈希值 Object类中的public int hashCode()&#xff1a;返回对象的哈希码值。 哈希值的特点 同一个…

分布式ID生成算法|雪花算法 Snowflake | Go实现

写在前面 在分布式领域中&#xff0c;不可避免的需要生成一个全局唯一ID。而在近几年的发展中有许多分布式ID生成算法&#xff0c;比较经典的就是 Twitter 的雪花算法(Snowflake Algorithm)。当然国内也有美团的基于snowflake改进的Leaf算法。那么今天我们就来介绍一下雪花算法…

计算机视觉基础知识(二)---数字图像

像素 像素是分辨率的单位;构成位图图像的最基本单元;每个像素都有自己的颜色; 图像分辨率 单位英寸内的像素点数;单位为PPI(Pixels Per Inch),为像素每英寸;PPI表示每英寸对角线上所拥有的像素数目:,x:长度像素数目,y:宽度像素数目,Z:屏幕大小;屏幕尺寸(大小)指的是对角线长…

springer模板参考文献不显示

Spring期刊模板网站&#xff0c;我的问题是23年12月的版本 https://www.springernature.com/gp/authors/campaigns/latex-author-support/see-where-our-services-will-take-you/18782940 参考文献显示问好&#xff0c;在sn-article.tex文件中&#xff0c;这个sn-mathphys-num…

数据结构c版(3)——排序算法

本章我们来学习一下数据结构的排序算法&#xff01; 目录 1.排序的概念及其运用 1.1排序的概念 1.2 常见的排序算法 2.常见排序算法的实现 2.1 插入排序 2.1.1基本思想&#xff1a; 2.1.2直接插入排序&#xff1a; 2.1.3 希尔排序( 缩小增量排序 ) 2.2 选择排序 2.2…

rtt的io设备框架面向对象学习-io设备管理层

目录 1.设备基类2.rtt基类2.1 rtt基类定义2.2 对象容器定义2.3 rtt基类构造函数 3.io设备管理接口4.总结 这层我的理解就是rtt基类和设备基类所在&#xff0c;所以抽离出来好点&#xff0c;不然每个设备类都要重复它。 1.设备基类 /include/rtdef.h中定义了设备基类struct rt_…