Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >七种方式教你在SpringBoot初始化时搞点事情

七种方式教你在SpringBoot初始化时搞点事情

作者头像
CloudBest
发布于 2021-04-20 08:01:08
发布于 2021-04-20 08:01:08
3K00
代码可运行
举报
文章被收录于专栏:CloudBestCloudBest
运行总次数:0
代码可运行

我们经常需要在容器启动的时候做一些钩子动作,比如注册消息消费者,监听配置等,今天就总结下SpringBoot留给开发者的7个启动扩展点。

容器刷新完成扩展点

1、监听容器刷新完成扩展点ApplicationListener<ContextRefreshedEvent>

基本用法

熟悉Spring的同学一定知道,容器刷新成功意味着所有的Bean初始化已经完成,当容器刷新之后Spring将会调用容器内所有实现了ApplicationListener<ContextRefreshedEvent>BeanonApplicationEvent方法,应用程序可以以此达到监听容器初始化完成事件的目的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
public class StartupApplicationListenerExample implements 
  ApplicationListener<ContextRefreshedEvent> {

    private static final Logger LOG 
      = Logger.getLogger(StartupApplicationListenerExample.class);

    public static int counter;

    @Override public void onApplicationEvent(ContextRefreshedEvent event) {
        LOG.info("Increment counter");
        counter++;
    }
}
易错的点

这个扩展点用在web容器中的时候需要额外注意,在web 项目中(例如spring mvc),系统会存在两个容器,一个是root application context,另一个就是我们自己的context(作为root application context的子容器)。如果按照上面这种写法,就会造成onApplicationEvent方法被执行两次。解决此问题的方法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
public class StartupApplicationListenerExample implements 
  ApplicationListener<ContextRefreshedEvent> {

    private static final Logger LOG 
      = Logger.getLogger(StartupApplicationListenerExample.class);

    public static int counter;

    @Override public void onApplicationEvent(ContextRefreshedEvent event) {
        if (event.getApplicationContext().getParent() == null) {
            // root application context 没有parent
            LOG.info("Increment counter");
            counter++;
        }
    }
}
高阶玩法

当然这个扩展还可以有更高阶的玩法:自定义事件,可以借助Spring以最小成本实现一个观察者模式:

  • 先自定义一个事件:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class NotifyEvent extends ApplicationEvent {
    private String email;
    private String content;
    public NotifyEvent(Object source) {
        super(source);
    }
    public NotifyEvent(Object source, String email, String content) {
        super(source);
        this.email = email;
        this.content = content;
    }
    // 省略getter/setter方法
}
  • 注册一个事件监听器
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
public class NotifyListener implements ApplicationListener<NotifyEvent> {

    @Override
    public void onApplicationEvent(NotifyEvent event) {
        System.out.println("邮件地址:" + event.getEmail());
        System.out.println("邮件内容:" + event.getContent());
    }
}
  • 发布事件
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@RunWith(SpringRunner.class)
@SpringBootTest
public class ListenerTest {
    @Autowired
    private WebApplicationContext webApplicationContext;

    @Test
    public void testListener() {
        NotifyEvent event = new NotifyEvent("object", "abc@qq.com", "This is the content");
        webApplicationContext.publishEvent(event);
    }
}
  • 执行单元测试可以看到邮件的地址和内容都被打印出来了

2、SpringBootCommandLineRunner接口

当容器上下文初始化完成之后,SpringBoot也会调用所有实现了CommandLineRunner接口的run方法,下面这段代码可起到和上文同样的作用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
public class CommandLineAppStartupRunner implements CommandLineRunner {
    private static final Logger LOG =
      LoggerFactory.getLogger(CommandLineAppStartupRunner.class);

    public static int counter;

    @Override
    public void run(String...args) throws Exception {
        LOG.info("Increment counter");
        counter++;
    }
}

对于这个扩展点的使用有额外两点需要注意:

  • 多个实现了CommandLineRunnerBean的执行顺序可以根据Bean上的@Order注解调整
  • run方法可以接受从控制台输入的参数,跟ApplicationListener<ContextRefreshedEvent>这种扩展相比,更加灵活
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 从控制台输入参数示例
java -jar CommandLineAppStartupRunner.jar abc abcd

3、SpringBootApplicationRunner接口

这个扩展和SpringBootCommandLineRunner接口的扩展类似,只不过接受的参数是一个ApplicationArguments类,对控制台输入的参数提供了更好的封装,以--开头的被视为带选项的参数,否则是普通的参数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
public class AppStartupRunner implements ApplicationRunner {
    private static final Logger LOG =
      LoggerFactory.getLogger(AppStartupRunner.class);

    public static int counter;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        LOG.info("Application started with option names : {}", 
          args.getOptionNames());
        LOG.info("Increment counter");
        counter++;
    }
}

比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java -jar CommandLineAppStartupRunner.jar abc abcd --autho=mark verbose

Bean初始化完成扩展点

前面的内容总结了针对容器初始化的扩展点,在有些场景,比如监听消息的时候,我们希望Bean初始化完成之后立刻注册监听器,而不是等到整个容器刷新完成,Spring针对这种场景同样留足了扩展点:

1、@PostConstruct注解

@PostConstruct注解一般放在Bean的方法上,被@PostConstruct修饰的方法会在Bean初始化后马上调用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
public class PostConstructExampleBean {

    private static final Logger LOG 
      = Logger.getLogger(PostConstructExampleBean.class);

    @Autowired
    private Environment environment;

    @PostConstruct
    public void init() {
        LOG.info(Arrays.asList(environment.getDefaultProfiles()));
    }
}

2、 InitializingBean接口

InitializingBean的用法基本上与@PostConstruct一致,只不过相应的Bean需要实现afterPropertiesSet方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
public class InitializingBeanExampleBean implements InitializingBean {

    private static final Logger LOG 
      = Logger.getLogger(InitializingBeanExampleBean.class);

    @Autowired
    private Environment environment;

    @Override
    public void afterPropertiesSet() throws Exception {
        LOG.info(Arrays.asList(environment.getDefaultProfiles()));
    }
}

3、@Bean注解的初始化方法

通过@Bean注入Bean的时候可以指定初始化方法:

Bean的定义

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class InitMethodExampleBean {

    private static final Logger LOG = Logger.getLogger(InitMethodExampleBean.class);

    @Autowired
    private Environment environment;

    public void init() {
        LOG.info(Arrays.asList(environment.getDefaultProfiles()));
    }
}

Bean注入

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Bean(initMethod="init")
public InitMethodExampleBean initMethodExampleBean() {
    return new InitMethodExampleBean();
}

4、通过构造函数注入

Spring也支持通过构造函数注入,我们可以把搞事情的代码写在构造函数中,同样能达到目的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component 
public class LogicInConstructorExampleBean {

    private static final Logger LOG 
      = Logger.getLogger(LogicInConstructorExampleBean.class);

    private final Environment environment;

    @Autowired
    public LogicInConstructorExampleBean(Environment environment) {
        this.environment = environment;
        LOG.info(Arrays.asList(environment.getDefaultProfiles()));
    }
}

Bean初始化完成扩展点执行顺序?

可以用一个简单的测试:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
@Scope(value = "prototype")
public class AllStrategiesExampleBean implements InitializingBean {

    private static final Logger LOG 
      = Logger.getLogger(AllStrategiesExampleBean.class);

    public AllStrategiesExampleBean() {
        LOG.info("Constructor");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        LOG.info("InitializingBean");
    }

    @PostConstruct
    public void postConstruct() {
        LOG.info("PostConstruct");
    }

    public void init() {
        LOG.info("init-method");
    }
}

实例化这个Bean后输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[main] INFO o.b.startup.AllStrategiesExampleBean - Constructor
[main] INFO o.b.startup.AllStrategiesExampleBean - PostConstruct
[main] INFO o.b.startup.AllStrategiesExampleBean - InitializingBean
[main] INFO o.b.startup.AllStrategiesExampleBean - init-method

(完)

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数字科智 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
补习系列(21)-SpringBoot初始化之7招式
在日常开发时,我们常常需要 在SpringBoot 应用启动时执行某一段逻辑,如下面的场景:
美码师
2019/07/30
5700
Spring生命周期以及如何在Spring启动时加入逻辑
先上两张图,了解一下springbean的生命周期,对理解后面的正文有很大帮助。生命周期在面试和平时开发中也很重要。
明明如月学长
2021/08/27
1.1K0
了解这些,你就可以在Spring启动时为所欲为了
Spring 是一个控制反转依赖管理的容器,作为 Java Web 的开发人员,基本没有不熟悉 Spring 技术栈的,尽管在依赖注入领域,Java Web 领域不乏其他优秀的框架,如 google 开源的依赖管理框架 guice,如 Jersey web 框架等。但 Spring 已经是 Java Web 领域使用最多,应用最广泛的 Java 框架。
码哥字节
2021/01/14
1.3K0
从@PostConstruct重新认识初始化
有前端在调后端测试环境接口的时候反馈某个服务挂了,然后去机器上看了下。通过ps命令和supervisor工具检查进程在:
叔牙
2023/09/07
6060
从@PostConstruct重新认识初始化
【Spring容器】项目启动后初始化数据的两种实践方案
这种方式应该是比较常见的,上述代码之所以这么写,是因为Servlet中无法使用使用Spring容器的上下文,只能在servlet中重新获取,这也就导致了两次容器的加载,与之相对应就是两次相关程序的调用。
MavenTalker
2023/03/07
3590
【Spring容器】项目启动后初始化数据的两种实践方案
六种方式实现 springboot 项目 启动预加载
在实际工作中总是需要在项目启动时做一些初始化的操作,比如初始化线程池、提前加载好加密证书…
猫头虎
2024/04/07
6660
六种方式实现 springboot 项目 启动预加载
SpringBoot 启动时初始化数据
在使用 springboot 搭建项目的时候,有时候会碰到在项目启动时初始化一些操作的需求,针对这种需求 springboot(spring) 提供了以下几种方案:
BUG弄潮儿
2021/10/08
2.1K0
应用启动加速-并发初始化spring bean
随着需求的不断迭代,服务承载的内容越来越多,依赖越来越多,导致服务启动慢,从最开始的2min以内增长到5min,导致服务发布很慢,严重影响开发效率,以及线上问题的修复速度。所以需要进行启动加速。
方丈的寺院
2022/11/08
1.4K0
应用启动加速-并发初始化spring bean
优化你的Spring Boot应用:预加载的秘密
比如要监听ContextRefreshedEvent的时可以实现ApplicationListener接口,并且传入要监听的事件
一只牛博
2025/05/30
1290
六种方式,教你在SpringBoot初始化时搞点事情!
在实际工作中经常需要在项目启动时做一些初始化的操作,比如初始化线程池、提前加载好加密证书.......
用户1263954
2021/12/02
8920
六种方式,教你在SpringBoot初始化时搞点事情!
面试官:SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。
磊哥
2024/01/20
9930
面试官:SpringBoot如何实现缓存预热?
Spring Boot缓存预热实战指南
在现代应用程序中,缓存预热是一种常见的优化策略,旨在提高系统的响应速度和性能。特别是在Spring Boot项目启动时,预先将数据加载到缓存系统(如Redis)中,可以有效减少首次请求的延迟。本文将探讨在Spring Boot项目启动后,如何实现缓存预热的不同方案。
用户11397231
2025/06/02
1500
Spring Boot缓存预热实战指南
羞,Spring Bean 初始化/销毁竟然有这么多姿势
日常开发过程有时需要在应用启动之后加载某些资源,或者在应用关闭之前释放资源。Spring 框架提供相关功能,围绕 Spring Bean 生命周期,可以在 Bean 创建过程初始化资源,以及销毁 Bean 过程释放资源。Spring 提供多种不同的方式初始化/销毁 Bean,如果同时使用这几种方式,Spring 如何处理这几者之间的顺序?
andyxh
2019/11/29
1.8K0
事件监听思考
在整合在项目中,我们通常需要基于事件去触发另外的业务逻辑动作的完成。也即在我们做需求时,通常会基于不同的事件码来完成业务处理,此时可以考虑将其单独处理,基于观察者模式+策略模式。还有一种如果当Spring完成Bean的初始化,需要做一些特殊处理,此时除了使用InitializingBean,还可以使用监听完成一些定制化的初始化动作,实现ApplicationListener<ContextRefreshedEvent>。
路行的亚洲
2022/11/16
2.4K0
不知道吧?Spring Bean初始化/销毁竟然有这么多姿势
日常开发过程有时需要在应用启动之后加载某些资源,或者在应用关闭之前释放资源。Spring 框架提供相关功能,围绕 Spring Bean 生命周期,可以在 Bean 创建过程初始化资源,以及销毁 Bean 过程释放资源。Spring 提供多种不同的方式初始化/销毁 Bean,如果同时使用这几种方式,Spring 如何处理这几者之间的顺序?
Bug开发工程师
2020/03/12
1.8K0
不知道吧?Spring Bean初始化/销毁竟然有这么多姿势
一张图帮你记忆,Spring Boot 应用在启动阶段执行代码的几种方式
有时候我们需要在应用启动时执行一些代码片段,这些片段可能是仅仅是为了记录 log,也可能是在启动时检查与安装证书 ,诸如上述业务要求我们可能会经常碰到
用户4172423
2019/12/31
2.1K0
Spring高手之路7——事件机制与监听器的全面探索
观察者模式是一种行为设计模式,它定义了对象之间的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。在这个模式中,改变状态的对象被称为主题,依赖的对象被称为观察者。
砖业洋__
2023/07/06
1.9K0
Spring高手之路7——事件机制与监听器的全面探索
Spring中ApplicationListener的使用
ApplicationListener是Spring事件机制的一部分,与抽象类ApplicationEvent类配合来完成ApplicationContext的事件机制。
程序新视界
2019/08/01
9450
SchedulerFactoryBean初始化监听
今天碰到一个问题,使用的是Quartz动态控制定时器的运行,功能已经完善,但是每次上线定时项目的时候,总要重启,一重启,所有定时任务自动就停止了,就会跟数据库对应的定时器状态不一致,在网上找了半天,找到了关于SchedulerFactoryBean初始化监听的东西,网上的文章大部分是SchedulerFactoryBean初始化解析,全部去研究代码去了,找了半天才找到了一篇有用的文章,我也很无语。为此,我就把这个记录了一下,主要怕自己下次还是不会,也为了广大攻城狮们做个记录。有帮助的可以点个赞。
全栈程序员站长
2022/08/31
1880
Spring容器初始化完成的回调方法
我们可能经常会碰到一些奇奇怪怪的需求,比如在IOC容器初始化完成前实例化一些bean,bean的初始化回调等等等。今天来讲一下如何实现Spring IOC容器如何在完成初始化后回调某个方法。
冰枫
2018/04/25
3.5K2
推荐阅读
相关推荐
补习系列(21)-SpringBoot初始化之7招式
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档