SpringBoot启动后加载初始化数据

文章对比了SpringBoot中四种在项目启动后执行操作的方法:@PostConstruct、CommandLineRunner、ApplicationRunner和ApplicationListener。@PostConstruct在Bean初始化时运行,可能影响启动时间和依赖管理。CommandLineRunner和ApplicationRunner在SpringBoot完全初始化后执行,支持自定义参数和顺序。ApplicationListener监听特定事件,如ContextRefreshedEvent,但可能因多容器导致重复执行。文章建议根据需求选择,避免影响启动速度。

我们在开发过程中会有这样的场景:需要在项目启动后执行一些操作,比如:读取配置文件信息,数据库连接,,删除临时文件,清除缓存信息,工厂类初始化,加载活动数据,或者缓存的同步等。我们会用多种实现方式,例如@PostConstruct、CommandLineRunner、ApplicationRunner、ApplicationListener都可以实现在springboot启动后执行我们特定的逻辑,接下来对比他们的区别

一、@PostConstruct


该注解被用来修饰一个非静态的void方法,被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务区执行一次
触发时机:
SpringBoot会把标记了Bean相关的注解(例如@Component、@Service、@Repository等)的类或接口自动初始化全局的单一实例,如果标记了初始化顺序会按照用户标记的顺序,否则按照默认顺序初始化。在初始化的过程中,执行完一个Bean。
spring中bean的创建过程

配置Bean(@Component、@Service、@Controller等注解配置)-->解析为Bean的元数据(Bean容器中的BeanDefinition对象)-->根据Bean的元数据生成Bean(创建Bean)

创建Bean的时候执行顺序
Constructor(构造方法)–>@Autowired(依赖注入)–>@PostConstruct(注释的方法)

优点:
使用简单,在spring容器管理的类中添加此注解即可

缺点:
1.在spring创建Bean的时候触发,此时容器还未完全初始化完毕,如果逻辑中引用了还未完成初始化的bean会导致异常,所以需要考虑加载顺序。
2.如果@PostConstruct方法内的逻辑处理时间较长,就会增加SpringBoot应用初始化Bean的时间,进而增加应用启动的时间。因为只有在Bean初始化完成后,SpringBoot应用才会打开端口提供服务,所以在此之前,应用不可访问。
3.一句话,会影响你程序启动的时间。
 

二、CommandLineRunner和ApplicationRunner

1、执行时机

springboot 完全初始化完毕后

2、SpringApplication.run 方法源码

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   //设置线程启动计时器
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   //配置系统属性:默认缺失外部显示屏等允许启动
   configureHeadlessProperty();
   //获取并启动事件监听器,如果项目中没有其他监听器,则默认只有EventPublishingRunListener
   SpringApplicationRunListeners listeners = getRunListeners(args);
   //将事件广播给listeners
   listeners.starting();
   try {
       //对于实现ApplicationRunner接口,用户设置ApplicationArguments参数进行封装
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
      //配置运行环境:例如激活应用***.yml配置文件      
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
      configureIgnoreBeanInfo(environment);
      //加载配置的banner(gif,txt...),即控制台图样
      Banner printedBanner = printBanner(environment);
      //创建上下文对象,并实例化
      context = createApplicationContext();
      exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
      //配置SPring容器      
      prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
      //刷新Spring上下文,创建bean过程中      
      refreshContext(context);
      //空方法,子类实现
      afterRefresh(context, applicationArguments);
      //停止计时器:计算线程启动共用时间
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass)
               .logStarted(getApplicationLog(), stopWatch);
      }
      //停止事件监听器
      listeners.started(context);
      //开始加载资源
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, listeners, exceptionReporters, ex);
      throw new IllegalStateException(ex);
   }
   listeners.running(context);
   return context;
}

callRunner方法:

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    //将实现ApplicationRunner和CommandLineRunner接口的类,存储到集合中
   List<Object> runners = new ArrayList<>();
   runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
   runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
   //按照加载先后顺序排序
   AnnotationAwareOrderComparator.sort(runners);
   for (Object runner : new LinkedHashSet<>(runners)) {
      if (runner instanceof ApplicationRunner) {
         callRunner((ApplicationRunner) runner, args);
      }
      if (runner instanceof CommandLineRunner) {
         callRunner((CommandLineRunner) runner, args);
      }
   }
}

3、使用

从上面源码可以看到,在springboot完全初始化完毕后,会执行CommandLineRunner和ApplicationRunner,两者唯一的区别是参数不同,但是不会影响,都可以获取到执行参数。


@Component
public class ServerDispatcher implements CommandLineRunner {
    @Override
    public void run(String... args){
        // 逻辑代码
    }
}



@Component
public class ServerDispatcher2 implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args){
        // 逻辑代码
    }
}

4、特点

可以添加 @Order 注解等注解:指定执行的顺序 (数字小的优先)

@Priority 或其他排序的接口也可

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {

	/**
	 * The order value.
	 * <p>Default is {@link Ordered#LOWEST_PRECEDENCE}.
	 * @see Ordered#getOrder()
	 */
	int value() default Ordered.LOWEST_PRECEDENCE;

}


public interface Ordered {
	int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
	int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

	int getOrder();
}

顺序

在这里插入图片描述

  • 先根据 @Order 注解的值,@Order 的 value 值越小,则优先,没有该注解则默认最后执行。
  • @Order 值相同时,ApplicationRunner 优先于 CommandLineRunner。
  • @Order 值和继承的接口类型都相同时,按照注入容器的顺序(应该是按照类的名称)。
     

三、ApplicationListener 接口

1、执行时机

在 IOC 容器的启动过程,当所有的 bean 都已经处理完成之后,spring 的 ioc 容器会有一个发布 ContextRefreshedEvent 事件的动作。

2、使用

//使用示例:监听 ContextRefreshedEvent
@Component
public class ServerDispatcher implements ApplicationListener<ContextRefreshedEvent> {
    
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        // 逻辑代码
    }
}

3、注意

系统会存在两个容器,一个是 root application context , 另一个就是我们自己的 projectName-servlet context(作为root application context的子容器)
​ 这种情况下,就会造成 onApplicationEvent 方法被执行两次。为了避免上面提到的问题,我们可以只在 root application context 初始化完成后调用逻辑代码,其他的容器的初始化完成,则不做任何处理

​ 加入判断即可:if (event.getApplicationContext().getParent() == null)
 

@Component
public class ServerDispatcher implements ApplicationListener<ContextRefreshedEvent> {
    
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        // 当 spring 容器初始化完成后就会执行该方法
        if (event.getApplicationContext().getParent() == null) { 
            //逻辑代码
        }
    }
}

监听事件的类型
a、ContextRefreshedEvent:ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生。此处的初始化是指:所有的 Bean 被成功装载,后处理 Bean 被检测并激活,所有 Singleton Bean 被预实例化,ApplicationContext 容器已就绪可用。


b、ContextStartedEvent:当使用 ConfigurableApplicationContext (ApplicationContext子接口)接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。


c、ContextStoppedEvent:当使用 ConfigurableApplicationContext 接口中的 stop() 停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作。


d、ContextClosedEvent:当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。


e、RequestHandledEvent:这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。只能应用于使用DispatcherServlet的Web应用。在使用Spring作为前端的MVC控制器时,当Spring处理用户请求结束后,系统会自动触发该事件。
 

四、小结

1、@PostConstruct 不建议用来初始化数据。是在spring启动完成之前执行的,除非必要,不要用这两个去加载初始化数据。

2、如果刚好可以在一个方法内执行 Spring 启动后的动作,可以让 SpringBoot 的启动类继承上述的某个接口,然后重写相应的方法即可。

3、在数据初始化层面,不推荐@PostConstruct和ApplicationListener,原因是两者都会影响程序的启动,如果执行逻辑耗时很长,启动服务时间就很长。

4、建议使用CommandLintRunner、ApplicationRunner的方式,不会影响服务的启动速度,处理起来也比较简单。

### Spring Boot 启动初始化 Redis 连接 在 Spring Boot 中,可以通过配置文件 `application.properties` 或者 `application.yml` 来设置 Redis 的连接参数,并通过自定义逻辑实现应用启动时自动初始化 Redis 配置。以下是具体方法: #### 1. 添加依赖项 为了支持 Redis,在项目的构建工具(Maven 或 Gradle)中添加以下依赖项: ```xml <!-- Maven --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> // Gradle implementation 'org.springframework.boot:spring-boot-starter-data-redis' ``` #### 2. 配置 Redis 参数 在 `application.properties` 文件中指定 Redis 的基本连接信息: ```properties spring.redis.host=localhost spring.redis.port=6379 spring.redis.password= spring.redis.database=0 spring.redis.timeout=6000ms ``` 上述配置指定了 Redis 主机地址、端口、密码以及超时时间等基本信息[^5]。 #### 3. 自定义初始化逻辑 如果需要执行额外的初始化操作,可以创建一个监听器类来捕获 Spring Boot 应用启动完成事件 (`ApplicationReadyEvent`) 并在此处编写 Redis 初始化代码。例如: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; @Component public class RedisInitializer implements ApplicationListener<ContextRefreshedEvent> { @Autowired private StringRedisTemplate redisTemplate; @Override public void onApplicationEvent(ContextRefreshedEvent event) { // 执行 Redis 初始数据加载或其他操作 redisTemplate.opsForValue().set("key", "value"); System.out.println("Redis connection initialized and data loaded."); } } ``` 此代码片段展示了如何利用 `StringRedisTemplate` 对象向 Redis 数据库插入初始键值对[^6]。 #### 4. 使用 JMX 监控 Redis 性能 (可选) 如果希望监控 Redis 的性能指标,则可通过启用 JMX 功能并将其集成到应用程序中。JMX 设置如下所示: ```properties spring.jmx.enabled=true spring.jmx.default-domain=com.example.myapp ``` 这些属性允许开发者暴露管理 Bean 至 JMX 域名下以便远程管理和调试[^4]。 --- ### 注意事项 尽管提供了多个功能模块的支持,但实际开发过程中应根据项目需求合理选用组件和服务。对于简单的场景,默认配置可能已经足够;而对于复杂业务则需考虑扩展性和维护成本等因素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员一博

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值