ApplicationRunner、InitializingBean、@PostConstruct 执行顺序

Spring Boot提供CommandLineRunner和ApplicationRunner两个启动加载接口,可在容器启动时执行特定内容。文章介绍了二者区别、示例及执行顺序,还分析了实现多个ApplicationRunner时部分接口未执行的原因,同时阐述了InitializingBean接口和@PostConstruct注解的用法及执行顺序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

开发中可能会有这样的场景,需要在容器启动的时候执行一些内容。比如读取配置文件,数据库连接之类的。SpringBoot给我们提供了两个接口来帮助我们实现这种需求。两个启动加载接口分别是:CommandLineRunner和ApplicationRunner。Spring 提供了接口 InitializingBean,jdk提供了@PostConstruct

CommandLineRunner和ApplicationRunner区别

CommandLineRunner和ApplicationRunner的作用是相同的。不同之处在于CommandLineRunner接口的run()方法接收String数组作为参数,即是最原始的参数,没有做任何处理;而ApplicationRunner接口的run()方法接收ApplicationArguments对象作为参数,是对原始参数做了进一步的封装。

当程序启动时,我们传给main()方法的参数可以被实现CommandLineRunner和ApplicationRunner接口的类的run()方法访问,即可接收启动服务时传过来的参数。我们可以创建多个实现CommandLineRunner和ApplicationRunner接口的类。为了使他们按一定顺序执行,可以使用@Order注解或实现Ordered接口。

ApplicationRunner接口的示例

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
@Order(value = 1)
public class JDDRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {

        System.out.println("这个是测试ApplicationRunner接口");
               String strArgs = Arrays.stream(arg0.getSourceArgs()).collect(Collectors.joining("|"));
        System.out.println("Application started with arguments:" + strArgs);
    }
}

启动时候指定参数:java -jar xxxx.jar data1 data2 data3

这个是测试ApplicationRunner接口
Application started with arguments:data1|data2|data3

CommandLineRunner接口示例

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class TestCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("这个是测试CommandLineRunn接口");
         String strArgs = Arrays.stream(args).collect(Collectors.joining("|"));
        System.out.println("Application started with arguments:" + strArgs);
    }
}

启动时候指定参数:java -jar xxxx.jar data1 data2 data3

运行结果:

这个是测试CommandLineRunn接口
Application started with arguments:data1|data2|data3

CommandLineRunner和ApplicationRunner的执行顺序

在spring boot程序中,我们可以使用不止一个实现CommandLineRunner和ApplicationRunner的bean。为了有序执行这些bean的run()方法,可以使用@Order注解或Ordered接口。

@Component
@Order(2)
public class ApplicationRunnerBean1 implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments arg0) throws Exception {
        System.out.println("ApplicationRunnerBean 1");
    }
}
@Component
@Order(4)
public class ApplicationRunnerBean2 implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments arg0) throws Exception {
        System.out.println("ApplicationRunnerBean 2");
    }
}
@Component
@Order(1)
public class CommandLineRunnerBean1 implements CommandLineRunner {
    @Override
    public void run(String... args) {
        System.out.println("CommandLineRunnerBean 1");
    }
}
@Component
@Order(3)
public class CommandLineRunnerBean2 implements CommandLineRunner {
    @Override
    public void run(String... args) {
        System.out.println("CommandLineRunnerBean 2");
    }
}

执行结果:

CommandLineRunnerBean 1 ApplicationRunnerBean 1 CommandLineRunnerBean 2 ApplicationRunnerBean 2

实现多个ApplicationRunner时部分接口未执行

@Component
@Slf4j
public class RunnerTest1 implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {

        while (true) {
            System.out.println("this is RunnerTest1");
            Thread.sleep(100);
        }

    }
}
@Component
@Slf4j
public class RunnerTest2 implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {

        while (true) {
            System.out.println("this is RunnerTest2");
            Thread.sleep(100);
        }

    }
}

只会执行RunnerTest1中方法。

通过分析springboot启动的源码可以发现,在applicationContext容器加载完成之后,会调用SpringApplication类的callRunners方法:

该方法中会获取所有实现了ApplicationRunner和CommandLineRunner的接口bean,然后依次执行对应的run方法,并且是在同一个线程中执行。因此如果有某个实现了ApplicationRunner接口的bean的run方法一直循环不返回的话,后续的代码将不会被执行。

ApplicationRunner、InitializingBean、@PostConstruct执行顺序问题

InitializingBean接口的用法

InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法。注意,实现该接口的最好加上Spring的注解注入,比如@Component

@PostConstruct注解的用法

如果想在生成对象时候完成某些初始化操作,而偏偏这些初始化操作又依赖于依赖注入,那么就无法在构造函数中实现。为此,可以使用@PostConstruct注解一个方法来完成初始化,@PostConstruct注解的方法将会在依赖注入完成后被自动调用。 优先级: Constructor >> @Autowired >> @PostConstruct

@Component
public class Test implements InitializingBean, ApplicationRunner, CommandLineRunner {

    @PostConstruct
    public void init(){
        System.out.println("PostConstruct 方法执行");
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean 方法执行");
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("这个是测试ApplicationRunner接口");

    }

    @Override
    public void run(String... args) throws Exception {
        System.out.println("这个是测试CommandLineRunn接口");
    }
}
PostConstruct 方法执行 InitializingBean 方法执行
这个是测试ApplicationRunner接口 这个是测试CommandLineRunn接口

由此可知: @PostConstruct>InitializingBean>ApplicationRunner>CommandLineRunner

<think>好的,用户想了解如何实现缓存预热,特别是最佳实践和示例代码。我需要先整理一下缓存预热的基本概念和常用方法。根据提供的引用内容,缓存预热主要在系统启动时加载热点数据到缓存中,避免冷启动问题。Spring Boot中有几种实现方式,比如使用启动监听事件、@PostConstruct注解、CommandLineRunner或ApplicationRunner,以及InitializingBean接口。 首先,用户可能需要一个结构清晰的回答,分点说明每种方法,并附上示例代码。需要确保每个方法的描述准确,代码示例正确,并且引用对应的引用来源。同时,用户可能关心最佳实践是什么,可能需要比较不同方法的优缺点,然后推荐合适的方式。 接下来,检查每个引用内容。引用[1]提到了四种方法,引用[2]提供了@PostConstruct的代码示例,引用[3]是关于Redis的缓存预热步骤,引用[4]解释了缓存预热的目的和冷启动的问题。需要将这些信息整合起来,确保覆盖所有方法,并正确标注引用。 需要注意的是,用户可能希望示例代码使用JavaSpring Boot,所以应提供相关的代码片段,比如使用@PostConstruct或CommandLineRunner的例子。同时,最佳实践部分可能需要结合启动顺序和错误处理,比如推荐使用事件监听或CommandLineRunner,并考虑异步加载和分布式环境。 最后,生成相关问题,确保问题与缓存预热的实现、优化和应用场景相关。需要确保问题有针对性,帮助用户进一步了解该主题。</think>### 缓存预热实现方法与最佳实践 缓存预热通过系统启动时主动加载热点数据到缓存,可有效提升初始阶段缓存命中率,避免冷启动导致的性能问题。以下是Spring Boot中常见的实现方式及示例: --- #### 一、实现方法 1. **@PostConstruct注解** 在初始化方法上添加注解,容器创建Bean后自动执行: ```java @Component public class CacheWarmup { @Autowired private RedisTemplate<String, Object> redisTemplate; @PostConstruct public void initCache() { List<Product> hotProducts = productService.getHotProducts(); hotProducts.forEach(p -> redisTemplate.opsForValue().set("product:" + p.getId(), p)); } } ``` 特点:简单直接,但依赖Bean加载顺序[^2]。 2. **CommandLineRunner/ApplicationRunner** 实现接口并重写`run`方法,在应用启动后执行: ```java @Component public class CacheWarmupRunner implements CommandLineRunner { @Override public void run(String... args) { List<News> hotNews = newsService.getTop100News(); hotNews.forEach(n -> redisTemplate.opsForValue().set("news:" + n.getId(), n)); } } ``` 特点:控制启动阶段执行顺序,支持多任务优先级[^1]。 3. **事件监听机制** 监听`ApplicationReadyEvent`事件,确保应用完全就绪后执行: ```java @Component public class CacheListener { @EventListener(ApplicationReadyEvent.class) public void warmupCache() { userService.loadActiveUsersToCache(); } } ``` 特点:避免Bean未初始化完成的问题。 4. **InitializingBean接口** 通过重写`afterPropertiesSet`方法实现: ```java @Component public class CategoryCache implements InitializingBean { @Override public void afterPropertiesSet() { List<Category> categories = categoryService.getAll(); categories.forEach(c -> redisTemplate.opsForHash().put("categories", c.getId(), c)); } } ``` 特点:与Bean生命周期紧密绑定[^1]。 --- #### 二、最佳实践 1. **数据选择策略** - 加载高频访问数据(如首页商品、热门新闻) - 结合历史访问日志筛选热点数据[^3] 2. **执行顺序优化** - 使用`@Order`注解控制多个预热任务的顺序 - 优先加载核心业务数据,例如: ```java @Component @Order(1) public class PriorityCacheWarmup implements CommandLineRunner { ... } ``` 3. **异常处理与日志** ```java try { // 数据加载逻辑 } catch (Exception e) { log.error("缓存预热失败: {}", e.getMessage()); // 降级处理或重试机制 } ``` 4. **分布式环境适配** - 通过Redis分布式锁确保集群中仅单节点执行预热 - 结合定时任务定期刷新热点数据 --- #### 三、完整示例(结合Spring Boot + Redis) ```java @Component public class ComprehensiveCacheWarmup implements CommandLineRunner { @Autowired private ProductService productService; @Autowired private RedisTemplate<String, Product> redisTemplate; @Override @Async // 异步执行避免阻塞主线程 public void run(String... args) { List<Product> products = productService.getTop1000Products(); products.parallelStream().forEach(p -> redisTemplate.opsForValue().set("product:v2:" + p.getSku(), p, 30, TimeUnit.MINUTES) ); } } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值