引言

Spring Boot 提供了许多便捷的功能和特性,使得开发者可以更加轻松地构建强大、高效的应用程序。然而,在应用程序启动时执行一些初始化操作是至关重要的,它可以确保应用程序在启动后处于预期的状态,从而提供更好的用户体验和稳定性。

在应用程序启动时执行初始化操作有许多好处。首先,它可以确保应用程序在启动后的初始状态是正确的,避免了在应用程序运行时出现意外情况。其次,它可以在应用程序准备好接受请求之前完成一些必要的设置,例如加载配置、建立数据库连接、缓存预热等。总的来说,执行初始化操作可以确保应用程序以正确的方式启动,并为后续操作提供一个稳定的基础。

监听 ApplicationContext事件

Spring Boot应用程序启动时执行初始化操作的方法是通过监听ApplicationContext事件。ContextRefreshedEvent事件表示ApplicationContext被初始化或刷新时触发的事件。通过监听这个事件,开发者可以在应用程序启动后执行一些必要的初始化操作。

示例:

@Component
public class MyContextRefreshedListener implements ApplicationListener<ContextRefreshedEvent> { @Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("监听到ContextRefreshedEvent事件,开始初始化操作。。。。。。。");
}
}

这种方式适合以下场景:

  1. 执行一次性初始化操作: 当应用程序启动时,可能需要执行一些只需在应用程序初始化阶段执行一次的操作,例如加载基础数据、建立连接等。通过监听 ContextRefreshedEvent 事件,可以确保这些初始化操作在应用程序启动后立即执行。

  2. 初始化缓存或缓存刷新: 如果应用程序使用了缓存,可能需要在应用程序启动时初始化缓存或定期刷新缓存。通过监听 ContextRefreshedEvent 事件,可以在应用程序启动后立即执行缓存初始化或刷新操作,确保缓存数据是最新的。

  3. 执行与外部系统的交互: 在应用程序启动时,可能需要与外部系统进行交互,例如检查外部系统的可用性、加载配置信息等。通过监听 ContextRefreshedEvent 事件,可以在应用程序启动后立即执行与外部系统的交互操作,确保应用程序在启动后处于正常工作状态。

  4. 执行与 Spring Bean 相关的初始化操作: 在应用程序启动时,可能需要执行一些与 Spring Bean 相关的初始化操作,例如在数据库连接池初始化后执行数据库迁移、在消息队列连接初始化后执行订阅操作等。通过监听 ContextRefreshedEvent 事件,可以确保这些初始化操作在 Spring Bean 初始化完成后立即执行

这种方式能够确保在 ApplicationContext 被完全初始化或刷新后执行初始化操作,可以在这个时机执行一些需要ApplicationContext完全准备好的操作。但是需要注意的是,ContextRefreshedEvent 事件可能会在应用程序的刷新周期内多次触发,因此在处理这个事件时需要谨慎处理,避免重复执行初始化逻辑。

实现CommandLineRunner接口

CommandLineRunner是Spring Boot提供的一个接口,它有一个run方法,当Spring Boot应用上下文初始化完成后,会自动查找并执行所有实现了CommandLineRunner接口的Bean的run方法。CommandLineRunner接口实际上是Spring Boot对Spring框架生命周期管理的一个扩展,通过对接口的实现,我们可以在Spring Boot应用启动后的特定阶段执行自定义的初始化逻辑。

示例:

@Component
public class MyCommandLineRunner implements CommandLineRunner { @Override
public void run(String... args) throws Exception {
System.out.println("MyCommandLineRunner.run()方法执行了");
}
}

使用场景:

  1. 命令行参数处理CommandLineRunner接口常用于处理从命令行传入的参数,例如运行不同模式下的任务(如dev模式、prod模式)、读取配置项等。
  2. 应用启动后的一次性操作:在应用启动后,可能需要进行一些一次性执行的任务,如数据库表结构检查、初始化缓存、发送通知邮件等。

使用CommandLineRunner接口这种方式是,我们只需要实现接口,无需关注容器的生命周期事件或手动注册监听器。但是如果是多个CommandLineRunner之间的执行顺序无法保证,可能会带来不确定性(如果是不关心顺序,那就不是缺点了)。另外,我们不应该在``

run方法中实现过多或较为复杂的任务。

实现ApplicationRunner接口

ApplicationRunner是Spring Boot提供的另一个接口,它也有一个run方法,与CommandLineRunner接口非常相似。当Spring Boot应用启动并且ApplicationContext初始化完成后,Spring Boot会查找并执行所有实现了ApplicationRunner接口的Bean的run方法。

ApplicationRunner的主要特点是其run方法接收一个ApplicationArguments参数,它可以更好地解析和处理命令行参数,包括选项参数(键值对)和非选项参数。

示例:

@Component
public class ApplicationArgumentProcessor implements ApplicationRunner { @Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationArgumentProcessor.run()方法执行了");
}
}

使用场景:

  1. 命令行参数解析:由于ApplicationArguments提供了丰富的参数解析能力,因此更适合处理带有键值对形式的命令行参数,如--server-port=8080,然后根据这些参数执行不同的初始化操作。
@Component
public class ApplicationArgumentProcessor implements ApplicationRunner { @Override
public void run(ApplicationArguments args) throws Exception {
Optional<Integer> port = args.getOptionValues("server-port").stream()
.map(Integer::parseInt)
.findFirst();
if (port.isPresent()) {
// 根据端口号进行特定的初始化操作
}
}
}
  1. 启动时初始化:同CommandLineRunner,也可用于执行启动后的一次性操作,例如读取配置、初始化缓存、检查系统资源等,同时可以根据解析的命令行参数决定初始化的具体内容。

相比较于CommandLineRunnerApplicationRunner提供了更强大的命令行参数解析功能,可以轻松处理各种类型的参数。可以根据命令行参数灵活调整启动时的初始化逻辑。但是其缺点同CommandLineRunner

ApplicationRunnerCommandLineRunner都可以用来在Spring Boot启动时执行特定代码,两者在应用场景上略有差异,具体选择哪种取决于项目的实际需求和命令行参数的复杂程度。

使用@PostConstruct注解

@PostConstruct注解是JSR-250规范的一部分,Spring框架对此提供了支持。当Spring容器管理的Bean完成依赖注入后,会自动调用标注有@PostConstruct的方法。这个注解应用于无参或void返回值的方法上,表明该方法应在依赖注入完成后,但在Bean实例正式投入使用之前调用。

在Spring Boot启动时,当Spring容器初始化并创建Bean时,如果发现某个Bean上有@PostConstruct注解的方法,则会在Bean的生命周期的初始化阶段调用这个方法。

@Service
public class UserService { @Autowired
private UserRepository userRepository; @PostConstruct
public void init() {
// 在依赖注入完成后,执行初始化操作
System.out.println("UserService初始化...");
// 初始化数据库连接、缓存或者其他内部状态
}
}

使用场景:

  1. 单个Bean初始化:对于某个特定的Bean,在其所有依赖项注入完成后,需要执行一些特定的初始化操作,例如数据库连接初始化、缓存预热、初始化内部状态等。
  2. 资源初始化:对于一些公共资源,如线程池、数据库连接池等,可以在对应的配置类或服务类中使用@PostConstruct来完成初始化设置。

@PostConstruct注解只需要在需要执行初始化操作的方法上加上即可,无需额外实现接口或关注Spring容器的生命周期事件。并且针对性强,仅针对单个Bean进行初始化操作,有助于提高代码的模块化和复用性。

但是如果有多个具有@PostConstruct注解的方法,它们之间没有明确的执行顺序,除非通过Bean间的依赖关系隐式确定顺序。并且针对单个Bean进行初始化操作,所以他并不适合做全局性初始化操作。

@Bean注解中指定初始化方法

@Bean注解在Spring框架中用于定义一个Bean的实例化逻辑,通常在配置类中使用。通过在@Bean注解中指定initMethod属性,可以设置一个在Bean实例化并完成依赖注入后执行的方法。当Spring容器创建并注入完所有依赖关系后,会自动调用该Bean上指定的初始化方法。

@Configuration
public class PrePostConfig {
/**
* 指定初始化init
* @return
*/
@Bean(initMethod = "init")
BeanWayService beanWayService(){
return new BeanWayService();
}
} public class BeanWayService { public void init() {
System.out.println("@Bean-init-method");
} public BeanWayService(){
super();
System.out.println("初始化构造函数-BeanWayService");
}
}

适用场景:

  1. 资源初始化:例如,初始化数据库连接、网络连接、线程池等资源。
  2. Bean状态设置:在Bean实例化后,对其进行额外的状态设定或配置。
  3. 缓存预热:在服务启动时预先加载部分数据至缓存中。

Bean实例上定义初始化方法,与Bean紧密关联,可以精确地控制Bean在何时执行初始化操作,与Spring容器的生命周期绑定,尤其适用于那些需要在Bean实例化后立即执行的操作。。但是如果多个Bean都有初始化方法,它们之间的执行顺序难以控制,除非依赖于Spring容器中Bean的依赖注入顺序。

实现InitializingBean接口

InitializingBean是Spring框架中的一个接口,它包含一个方法afterPropertiesSet()。当Spring容器完成了对一个Bean的所有必要属性的依赖注入后,如果该Bean实现了InitializingBean接口,Spring会自动调用其afterPropertiesSet()方法。

@Component
public class MyService implements InitializingBean { @Autowired
private Dependency dependency; @Override
public void afterPropertiesSet() throws Exception {
// 在所有依赖注入完成后执行的初始化逻辑
System.out.println("MyService初始化...");
// 初始化资源、设置状态或执行其他操作
} // 其他业务方法...
}

适用场景:

  1. 资源初始化:如初始化数据库连接、网络连接、线程池等资源。
  2. Bean状态设置:在依赖注入完成后,设置Bean的初始状态或执行特定的配置操作。

afterPropertiesSet()方法会在所有属性注入完成后执行,确保Bean在使用前完成初始化。不需要额外的注解,只需实现接口就可以定义初始化逻辑。但是其要求Bean实现特定接口,增加了类的耦合度,同时也不符合Spring倡导的基于注解的编程风格。并且需要显式抛出异常。

相比较于@PostConstruct@PostConstruct注解更具语义化且不强制类实现接口,降低了耦合度。推荐优先考虑使用@PostConstruct注解进行初始化逻辑的编写。

@EventListener注解

@EventListener 注解在Spring应用程序中定义事件监听器。通过监听 ApplicationReadyEvent事件,我们可以确保在应用程序完全启动并准备好接受请求时执行初始化逻辑。通过在监听器方法上添加 @EventListener 注解,并指定要监听的事件类型,可以在事件发生时执行相应的初始化操作。

@Component
public class StartupEventListener { @EventListener(ApplicationReadyEvent.class)
public void onApplicationReadyEvent(ApplicationReadyEvent event) {
System.out.println("Spring Boot应用已启动并准备就绪,开始执行初始化操作...");
// 在这里执行需要在应用启动后进行的初始化代码
}
}

适用场景:

  1. 应用启动后执行一次性操作:如数据初始化、缓存预热、统计信息收集等。
  2. 等待所有Bean初始化后再执行:当需要确保所有Bean都已经初始化完毕再执行某些操作时。

通过事件驱动的方式,将初始化逻辑与Bean的创建逻辑解耦开来,并且可以监听多种事件类型(例如:ContextRefreshedEvent),不仅仅是应用启动事件,还可用于其他业务场景。相比于@PostConstructCommandLineRunnerApplicationRunner等机制,@EventListener监听的ApplicationReadyEvent在Spring Boot启动流程中的执行时机较晚,所有Bean都已经初始化并准备就绪后才会触发。

总结

本文全面探讨了Spring Boot启动阶段执行初始化操作的几种常见方法,包括监听事件、实现接口以及使用注解等多种策略,具体如下:

  1. 监听ApplicationContext事件:通过实现ApplicationListener<ContextRefreshedEvent>接口,监听ContextRefreshedEvent事件,可在Spring容器初始化完成后执行初始化逻辑。这种方式适用于需要在所有Bean加载完毕后进行全局性初始化操作的场景。

  2. 实现CommandLineRunner接口:Spring Boot启动后,会自动调用实现了CommandLineRunner接口的Bean的run方法,该方法可以处理命令行参数并执行启动时的特定操作。适用于需要根据命令行参数执行初始化逻辑或进行启动后一次性任务的情况。

  3. 实现ApplicationRunner接口:与CommandLineRunner类似,ApplicationRunner也在Spring Boot启动后执行其run方法,但其参数为ApplicationArguments,提供了更强大的命令行参数解析功能。适合处理键值对形式的命令行参数并据此执行初始化任务。

  4. 使用@PostConstruct注解:在Bean的方法上添加@PostConstruct注解,Spring会在该Bean的所有依赖注入完成后调用该方法进行初始化。这种方法用于单个Bean初始化完成后的特定逻辑,增强了代码的模块化和可维护性。

  5. @Bean注解中指定初始化方法:通过@Bean注解中的initMethod属性指定Bean的初始化方法,该方法在Bean实例化并完成注入后由Spring容器调用。这种方法适用于需要对特定Bean进行精细化初始化管理的场景。

  6. 实现InitializingBean接口:Bean实现InitializingBean接口并重写afterPropertiesSet方法,也能实现在依赖注入完成后执行初始化逻辑。虽然传统但不如使用@PostConstruct注解优雅,且增加了类的耦合度。

  7. 使用@EventListener注解:通过监听ApplicationReadyEvent等事件,可以在Spring Boot应用启动并准备就绪后执行初始化任务。这种方式延迟执行,适用于在所有Bean初始化完毕且应用已经完全启动后才需要进行的操作。

每种方法均有其适用场景和优缺点,我们应根据项目需求和具体情况选择最适合的初始化方式。通过熟练掌握和灵活运用这些方法,能够有效地管理和优化Spring Boot应用的启动流程,确保应用程序在启动之初即进入正常运作状态。

本文已收录于我的个人博客:码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等

京东一面:如何在SpringBoot启动时执行特定代码?有哪些方式?的更多相关文章

  1. 2-Qt关闭子窗口时执行特定代码

    https://blog.csdn.net/naibozhuan3744/article/details/82689434 本文主要总结在关闭qt的QWidget子窗口瞬间,执行特定代码.由于主窗口关 ...

  2. SpringBoot程序启动时执行初始化代码

    因项目集成了Redis缓存部分数据,需要在程序启动时将数据加载到Redis中,即初始化数据到Redis. 在SpringBoot项目下,即在容器初始化完毕后执行我们自己的初始化代码. 第一步:创建实现 ...

  3. springboot启动时执行任务CommandLineRunner

    # SpringBoot中CommandLineRunner的作用> 平常开发中有可能需要实现在项目启动后执行的功能,SpringBoot提供的一种简单的实现方案就是添加一个model并实现Co ...

  4. Spring Boot学习--项目启动时执行特定方法

    Springboot给我们提供了两种"开机启动"某些方法的方式:ApplicationRunner和CommandLineRunner. 这两种方法提供的目的是为了满足,在项目启动 ...

  5. springboot 启动时执行方法

    Springboot提供了两种“开机启动”某些方法的方式:ApplicationRunner和CommandLineRunner.下面简单介绍下ApplicationRunner 1.创建个Tests ...

  6. 在web项目启动时执行某个方法

    在web项目中有很多时候需要在项目启动时就执行一些方法,而且只需要执行一次,比如:加载解析自定义的配置文件.初始化数据库信息等等,在项目启动时就直接执行一些方法,可以减少很多繁琐的操作. 在工作中遇到 ...

  7. 详解如何在 Linux 启动时自动执行命令或脚本

    我一直很好奇,在启动 Linux 系统并登录的过程中到底发生了什么事情.按下开机键或启动一个虚拟机,你就启动了一系列事件,之后会进入到一个功能完备的系统中,有时,这个过程不到一分钟.当你注销或者关机时 ...

  8. Servlet的init()方法如何才会在服务器启动时执行

    如果要想让 servlet 的 init () 方法在服务器启动 时就被执行,则需要在 web.xml 中相应的 servlet 下配置 <servlet > <servlet -n ...

  9. Linux开机启动时执行脚本的方法

    方法 1 – 使用 rc.local利用 /etc/ 中的 rc.local 文件在启动时执行脚本与命令.我们在文件中加上一行来执行脚本,这样每次启动系统时,都会执行该脚本.不过我们首先需要为 /et ...

  10. Spring在Web容器启动时执行初始化方法

    需求:在tomcat启动时开启一个定时任务. 想法:容器启动时执行方法,最容易想到的就是servlet中可以配置load-on-startup,设置一个正整数也就可以随容器一起启动. 问题:上面的方法 ...

随机推荐

  1. Java 常用类 String的常用方法(2)

    1 /** 2 * String 常用方法(2) 3 * boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束 4 * boolean startsWith ...

  2. VMware workstation虚拟机联网的方法

    注意:我的虚拟机所安装的系统是Centos 方法一: 第一步:先设置VMware的编辑--虚拟网络编辑器,启用VMnet8,NAT模式,如下图所示. 其实就是VMware默认的设置,无须更改,如果不小 ...

  3. RabbitMQ 快速复习

    目录 RabbitMQ学习笔记 1.消息队列概述 1.1 为什么学习消息队列 1.2 什么是消息中间件 1.3 消息队列应用场景 1.3.1 异步处理 1.3.2 解耦服务 1.3.3 流量削峰 1. ...

  4. vscode 格式化 vue 等文件的 配置 eslint vetur prettier Beautify

    需求 自动格式化需求 多行回车 合并一行,去分号 最后一个逗号,自动删除,符合eslint 结果 虽然能用了,但是 百度好几个方案,也不知道哪个对哪个,太忙没时间弄了. 配置文件记录 eslint 得 ...

  5. C#使用Stateless和箭头控件实现状态机的控制及显示

    之前开发一个小工具,内部实现一个状态机,并显示状态机当前状态及状态间的转移过程.我使用了Stateless开源类库及一个开源自定义箭头控件.自定义箭头控件是HZHControls其中一个控件,我单独把 ...

  6. [置顶] spring巧用继承解决bean的id相同的问题

    先感叹一下:最近的项目真的很奇葩!!! 需求是这样的:我们的项目中引用了两个jar包,这两个jar包是其他项目组提供的,不能修改! 奇葩的是:这两个jar中都需要引用方提供一个相同id的bean,而b ...

  7. github 上不去

    win:C:\WINDOWS\system32\drivers\etc linux:/etc/hosts 在这个网址查询每个网址对应的ip:https://www.ipaddress.com/ 202 ...

  8. 两个int变量交换

    两个变量int a,int b,不用临时变量过渡,两种方法: 第一种: a= a+b; b= a-b; a= a-b; 第二种:异或的方法,也就是位运算,两个相同的数异或是为0的. a= a^b; b ...

  9. 3DCAT实时云渲染助力广府庙会元宇宙焕新亮相,开启线上奇趣之旅!

    超 400 万人次打卡,商圈营业额逾 3.6 亿元,2023 年广府庙会于2023年2月11日圆满落幕. 活动期间,佳境美如画,融合VR.AR.虚拟直播等技术的广府庙会元宇宙焕新亮相,群众只需点击一个 ...

  10. Asp-Net-Core开发笔记:实现动态审计日志功能

    前言 最近一直在写 Go 和 Python ,好久没写 C# ,重新回来写 C# 代码时竟有一种亲切感~ 说回正题. 在当今这个数字化迅速发展的时代,每一个操作都可能对业务产生深远的影响,无论是对数据 ...