教你在SpringBoot初始化时搞点事情!

简介: 悉Spring的同学一定知道,容器刷新成功意味着所有的Bean初始化已经完成,当容器刷新之后Spring将会调用容器内所有实现了ApplicationListener<ContextRefreshedEvent>的Bean的onApplicationEvent方法,应用程序可

容器刷新完成扩展点

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

基本用法

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

@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方法被执行两次。解决此问题的方法如下:

@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以最小成本实现一个观察者模式:

  • 先自定义一个事件:
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方法
}
  • 注册一个事件监听器
@Component
public class NotifyListener implements ApplicationListener<NotifyEvent> {
    @Override
    public void onApplicationEvent(NotifyEvent event) {
        System.out.println("邮件地址:" + event.getEmail());
        System.out.println("邮件内容:" + event.getContent());
    }
}
  • 发布事件
@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方法,下面这段代码可起到和上文同样的作用:

@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>这种扩展相比,更加灵活
// 从控制台输入参数示例
java -jar CommandLineAppStartupRunner.jar abc abcd

3、SpringBootApplicationRunner接口

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

@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++;
    }
}

比如:

java -jar CommandLineAppStartupRunner.jar abc abcd --autho=mark verbose

Bean初始化完成扩展点

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

1、@PostConstruct注解

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

@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方法

@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的定义

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注入

@Bean(initMethod="init")
public InitMethodExampleBean initMethodExampleBean() {
    return new InitMethodExampleBean();
}

4、通过构造函数注入

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

@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初始化完成扩展点执行顺序?

可以用一个简单的测试:

@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后输出:

[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

相关文章
|
8月前
|
Java 关系型数据库 MySQL
Spring Boot实现第一次启动时自动初始化数据库
在现在的后端开发中,只要是使用关系型数据库,相信SSM架构(Spring Boot + MyBatis)已经成为首选。 不过在我们第一次运行或者部署项目的时候,通常要先手动连接数据库,执行一个SQL文件以创建数据库以及数据库表格完成数据库的初始化工作,这样我们的SSM应用程序才能够正常工作。 这样也对实际部署或者是容器化造成了一些麻烦,必须先手动初始化数据库再启动应用程序。 那能不能让我们的SSM应用程序第一次启动时,自动地帮我们执行SQL文件以完成数据库初始化工作呢? 这样事实上是没问题的,今天就以Spring Boot + MyBatis为例,使用MySQL作为数据库,完成上述的数
|
12天前
|
Java
SpringBoot 项目启动初始化一个Map对象到内存
SpringBoot 项目启动初始化一个Map对象到内存
|
1月前
|
缓存 前端开发 Java
SpringBoot启动后加载初始化数据
SpringBoot启动后加载初始化数据
|
1月前
|
Java 应用服务中间件
Springboot启动的时候初始化的线程池默认配置tomcat
Springboot启动的时候初始化的线程池默认配置tomcat
16 1
|
2月前
|
Java 容器 Spring
SpringBoot:Bean生命周期自定义初始化和销毁
SpringBoot:Bean生命周期自定义初始化和销毁
|
5月前
|
SQL 前端开发 Java
Hasor【环境搭建 01】SpringBoot集成Dataway接口配置服务(依赖+配置+数据库数据源初始化+注解添加+demo验证测试)
Hasor【环境搭建 01】SpringBoot集成Dataway接口配置服务(依赖+配置+数据库数据源初始化+注解添加+demo验证测试)
85 0
|
7月前
|
Oracle Java 关系型数据库
Spring Boot 3系列之一(初始化项目)
近期,JDK 21正式发布,而Spring Boot 3也推出已有一段时间。作为这两大技术领域的新一代标杆,它们带来了许多令人振奋的新功能和改进。尽管已有不少博客和文章对此进行了介绍,但对于我们这些身处一线的开发人员来说,有些文章和文档可能一看就会,一写就废。因此,为了更深入地理解JDK 21和Spring Boot 3的新特性,以及加深对Java和Spring Boot生态的理解,我们决定通过编写并分享代码,来展示一个简单的项目。我们计划在之后逐步完善这个项目。本文将向您展示如何使用最新版本的Spring Boot和JDK来初始化一个简单的Spring Boot 3程序。
194 1
Spring Boot 3系列之一(初始化项目)
|
11月前
|
Java Spring
使用Spring Initializr 快速初始化一个 Spring Boot 工程
使用Spring Initializr 快速初始化一个 Spring Boot 工程
|
11月前
|
XML 监控 NoSQL
【SpringBoot学习笔记 一】SpringBoot基本概念和项目初始化
【SpringBoot学习笔记 一】SpringBoot基本概念和项目初始化
183 0
|
11月前
|
缓存 Java 数据库连接
Spring Boot 系统初始化器详解
Spring Boot 系统初始化器详解
148 0