背景
随着公司业务越来越多,代码越来越多,jar启动越来越慢,啥搞?
莫慌,接下来手把手教你去优化,优化前对spring boot启动配置原理不熟的可以查看我的历史文章 spring boot的启动原理解读
spring boot 启动过程干预
在spring boot启动过程中,我们可以从以下几个方面进行干预优化:使用@ConfigurationProperties
和@EnableConfigurationProperties
注解,我们可以获取和修改spring boot的默认配置属性
spring boot启动的时候会自动加载bootstarp.yml和application.yml文件,我们也可以让启动时加载其它配置文件
我们可以通过@Component
注解让spring boot创建自定义的bean和其它的@springBootAnnotation注解
我们可以通过ApplicationRunner
和CommandLineRunner
等干预代码的方式去执行一些初始化操作,比如数据库、缓存和mQ等等,让程序能正常启动。
我们也可以通过干预代码,通过后置操作在程序停止后做一个清理工作,如关闭资源,释放缓存等等。
这些干预步骤可以在Spring Boot应用程序的启动和关闭过程中执行,以实现更灵活的配置和初始化。
一
ApplicationContextInitializer扩展
实现 ApplicationContextInitializer接口,可以在 ApplicationContext创建之前做一些自定义的调整,这个接口有一个 init方法,参数是 ConfigurableApplicationContext作为对象,可以在这个方法对该对象进行调整
常见的调整的内容:
1.修改environment属性 通过configurableApplicationContext.getEnvironment()方法获取到environment对象去配置调整环境变量,如:自定义配置文件路径
package com.nsy.fms.config;
import lombok.SneakyThrows;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.support.ResourcePropertySource;
/**
* @author limingfa
* @date 2024/4/22 10:29
*/
public class AppContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@SneakyThrows
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment configurableEnvironment=applicationContext.getEnvironment();
//添加自定义的配置文件路径
System.out.println("AppContextInitializer initialize :" + applicationContext);
configurableEnvironment.getPropertySources().addFirst(new ResourcePropertySource("classpath:lmf.properties"));
System.out.println("AppContextInitializer initialize add FirstResourcePropertySource classpath:lmf.properties");
}
}
我们通过获取 ConfigurableEnvironment对象,通过 getPropertySources()方法获取到属性源,使用 addFirst()方法将自定义的 lmf.properties文件的 PropertySource添加到属性源的首位。这样,在应用程序启动时,就会首先加载 lmf.properties文件,从而实现了自定义的配置注入,我们自定义的ApplicationContextInitialize,需要在META-INF/spring.factories文件中指定文件的全限定类名
2.添加自定义的 PropertySource ,通过environment.getPropertySources().addLast(propertySource)
方法,可以添加自定义的属性源,从而实现更灵活的配置。
package com.nsy.fms.config;
import lombok.SneakyThrows;
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;
/**
* @author limingfa
* @date 2024/4/22 10:29
*/
public class AppContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@SneakyThrows
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment configurableEnvironment=applicationContext.getEnvironment();
//添加自定义的配置文件路径
System.out.println("AppContextInitializer initialize :" + applicationContext);
configurableEnvironment.getPropertySources().addFirst(new ResourcePropertySource("classpath:lmf.properties"));
// 添加自定义的PropertySource
PropertySource<?> propertySource = new MyPorpertySource("myPropertySource");
configurableEnvironment.getPropertySources().addLast(propertySource);
System.out.println("AppContextInitializer initialize add FirstResourcePropertySource classpath:lmf.properties");
}
private static class MyPorpertySource extends PropertySource<String>{
private static final String MY_PROPERTY_SOURCE_KEY = "my.property.source.key";
public MyPorpertySource(String name) {
super(name);
}
@Override
public Object getProperty(String name) {
if (MY_PROPERTY_SOURCE_KEY.equals(name)) {
return "myPropertySourceValue";
}
return null;
}
}
}
我们自定义一个PropertySource,然后实现获取属性的方法,在获取属性的方法设置指定key对应的值,这样子我们后续在程序就可以借助@Value("${my.property.source.key}")的方式进行获取配置。
3.向spring 容器注册自定义bean,使用configurableApplicationContext.getBeanFactory().registerSingleton(beanName, bean)
方法进行注入。
package com.nsy.fms.config;
import lombok.SneakyThrows;
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;
/**
* @author limingfa
* @date 2024/4/22 10:29
*/
public class AppContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@SneakyThrows
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment configurableEnvironment=applicationContext.getEnvironment();
//添加自定义的配置文件路径
System.out.println("AppContextInitializer initialize :" + applicationContext);
configurableEnvironment.getPropertySources().addFirst(new ResourcePropertySource("classpath:lmf.properties"));
// 添加自定义的PropertySource
PropertySource<?> propertySource = new MyPorpertySource("myPropertySource");
configurableEnvironment.getPropertySources().addLast(propertySource);
//获取bean的工厂类
ConfigurableListableBeanFactory configurableListableBeanFactory= applicationContext.getBeanFactory();
configurableListableBeanFactory.registerSingleton("codeUtil",new CodeUtil());
System.out.println("AppContextInitializer initialize add FirstResourcePropertySource classpath:lmf.properties");
}
private static class MyPorpertySource extends PropertySource<String>{
private static final String MY_PROPERTY_SOURCE_KEY = "my.property.source.key";
public MyPorpertySource(String name) {
super(name);
}
@Override
public Object getProperty(String name) {
if (MY_PROPERTY_SOURCE_KEY.equals(name)) {
return "myPropertySourceValue";
}
return null;
}
}
private static class CodeUtil{
private String name="codeUtil";
public String getName() {
return name;
}
}
}
configurableListableBeanFactory.registerSingleton("codeUtil",new CodeUtil()) ,把我们自已的bean注册到spring容器中,后续我们就可以在程序使用该类。
二 SpringApplicationRunListener扩展
SpringApplicationRunListener用于监听Spring Boot应用程序的启动过程。它提供了一种扩展机制,允许开发人员在应用程序启动的不同阶段插入自定义逻辑。
主要作用和使用场景包括:
- 监听应用程序启动过程:SpringApplicationRunListener接口定义了一系列方法,可以在应用程序的不同阶段监听事件,如应用程序启动前、启动后、失败时等。
- 自定义初始化逻辑:通过实现SpringApplicationRunListener接口,可以在应用程序启动时执行自定义的初始化逻辑,例如加载配置、初始化资源、注册Bean等。
- 自定义日志输出:可以利用SpringApplicationRunListener来自定义应用程序启动过程中的日志输出,以便更好地监控和调试应用程序的启动过程。
- 扩展Spring Boot的功能:通过SpringApplicationRunListener可以扩展Spring Boot的功能,实现更灵活的配置和定制化需求,使应用程序更加适应特定的业务场景。
package com.nsy.fms.config;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
/**
* @author limingfa
* @date 2024/4/22 11:09
*/
public class AppRunListener implements SpringApplicationRunListener {
private final SpringApplication application;
private final String[] args;
public AppRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
}
//启动之前调用
@Override
public void starting(ConfigurableBootstrapContext context) {
System.out.println("AppRunListener starting");
}
//配置环境之前调用
@Override
public void environmentPrepared(ConfigurableBootstrapContext context,ConfigurableEnvironment environment) {
System.out.println("AppRunListener environmentPrepared");
}
//在spring 准备上下文之前使用
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("AppRunListener contextPrepared");
}
//在spring 上下文加载之后使用
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("AppRunListener contextLoaded");
}
//在Spring Boot应用启动之后调用
@Override
public void started(ConfigurableApplicationContext context) {
System.out.println("AppRunListener started");
}
//在Spring Boot应用运行之前调用
@Override
public void running(ConfigurableApplicationContext context) {
System.out.println("AppRunListener running");
}
//在Spring Boot应用启动失败时调用
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("AppRunListener failed");
}
}
当程序需要做一些特殊初始化,可以实现SpringApplicationRunListener,实现它对应的方法进行重写,然后在 src/main/resources/META-INF/spring.factories文件中指定注册项的方式来注册
三 ApplicationRunner扩展
ApplicationRunner是Spring Boot中的一个接口,用于在Spring Boot应用程序启动后执行特定的业务逻辑。它提供了一种方式来在应用程序完全启动后运行一些特定的任务或逻辑。
主要作用和常用使用场景包括:
-
运行初始化逻辑:定义一个 run()方法,可以在应用程序启动后执行初始化逻辑,例如数据库初始化、设置环境变量等。
-
执行定时任务:可以利用ApplicationRunner来执行一些定时任务或后台任务,确保这些任务在应用程序完全启动后才会执行。
-
缓存预热:我们可以提前加载一些数据缓存,而不是等到应用程序使用时才加载,这样可以减少程序响应时间,提高用户体验和程序的性能。
- 环境检查:执行应用程序启动后的验证逻辑,确保应用程序启动成功并处于正确的状态,当程序无法正常运行,可以通过抛异常来提示我们。
//做缓存初始化
@Slf4j
@Component
public class CategoryCachePreload implements ApplicationRunner {
@Autowired
private ICategoryService categoryService;
@Override
public void run(ApplicationArguments args) throws Exception {
categoryService.rebuildCache();
}
}
四 CommandLineRunner 扩展
CommandLineRunner是Spring Boot中的一个接口,用于在Spring Boot应用程序启动后执行特定的业务逻辑。它提供了一种方式来在应用程序完全启动后运行一些特定的任务或逻辑,类似于ApplicationRunner。
CommandLineRunner接口定义了一个run方法,该方法在Spring Boot应用程序启动后被调用。
使用场景:
-
初始化数据:在应用程序启动后加载初始数据或执行数据库初始化操作。
-
执行定时任务:在应用程序完全启动后执行定时任务或计划任务。
-
处理命令行参数:处理应用程序启动时传递的命令行参数,执行相应的逻辑。
-
与外部系统交互:在应用程序启动后与外部系统进行交互或执行特定操作。
-
执行一次性任务:执行一次性的任务或逻辑,这些任务在应用程序启动后只需要执行一次。
我们可以结合ApplicationRunner和CommandLineRunner来完成一些复杂的操作,这样在应用程序启动时,不仅可以自动执行初始化任务,还可以通过命令行手动执行这些任务。
case 1: 用户数据初始化命令行工具
package com.nsy.fms.config;
import com.google.common.collect.Lists;
import com.nsy.fms.repository.entity.transaction.BaseAccountEntity;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author limingfa
* @date 2024/4/22 11:34
*/
@Component
public class UserCommand implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
List<BaseAccountEntity> user=readUserFromFile("");
}
// 从数据文件中读取用户信息
private List<BaseAccountEntity> readUserFromFile(String fileName) {
// 省略代码,从文件中读取用户信息,返回一个User对象列表
return Lists.newArrayList();
}
}
当我们在启动项目就可以通过以下命令去执行初始化数据操作
java -jar lmf.jar user users.txt
lmf.jar是应用程序运行的jar包,user是命令行工具的名称,users.txt是要导入的用户数据文件名。
高效优化提高启动速度秘方