SpringBoot初始化加载配置的八种方式总结
作者:独行客-编码爱好者
- @PostConstruct 注解
- InitializingBean 接口
- @Bean initMethod方法
- 构造器注入
- ApplicationListener
- CommandLineRunner
- ApplicationRunner
- SmartLifecycle
序号 | 初始化加载方式 | 执行时机 |
1 | @PostConstruct 注解(在方法加注解) | Bean对象初始化完成后执行( 该方法会在所有依赖字段注入后才执行 ) |
2 | 构造器注入(构造方法加 @Autowired注解 ) | Bean对象初始化完成后执行( 该方法会在所有依赖字段注入后才执行 ) |
3 | InitializingBean 接口(继承 InitializingBean接口,并实现 afterPropertiesSet()这个方法) | Bean对象初始化完成后执行( 该方法会在所有依赖字段注入后才执行 ) 3和4是一样的,实现方式不同 |
4 | @Bean initMethod方法 | Bean对象初始化完成后执行( 该方法会在所有依赖字段注入后才执行 ) 3和4是一样的,实现方式不同 |
5 | SmartLifecycle 接口(继承SmartLifecycle 接口),并实现start() 方法 | SmartLifecycle 的执行时机是在 Spring 应用上下文刷新完成之后,即所有的 Bean 都已经被实例化和初始化之后。 |
6 | ApplicationListener(继承 ApplicationListener接口,并实现onApplicationEvent()方法 与方法上加 @EventListener的效果一样 ) | 所有的Bean都初始化完成后才会执行方法 |
7 | CommandLineRunner 继承 CommandLineRunner,继承 run() 方法 ) | 应用启动后执行 |
8 | ApplicationRunner( 继承 CommandLineRunner,继承 run() 方法 ) | 应用启动后执行 |
背景
在日常开发时,我们常常需要 在SpringBoot 应用启动时执行某一段逻辑,如下面的场景:
获取一些当前环境的配置或变量
向缓存数据库写入一些初始数据
连接某些第三方系统,确认对方可以工作
在实现这些功能时,我们可能会遇到一些"坑"。 为了利用SpringBoot框架的便利性,我们不得不将整个应用的执行控制权交给容器,于是造成了大家对于细节是一无所知的。那么在实现初始化逻辑代码时就需要小心了,比如,我们并不能简单的将初始化逻辑在Bean类的构造方法中实现,类似下面的代码:
@Component
public class InvalidInitExampleBean {
@Autowired
private Environment env;
public InvalidInitExampleBean() {
env.getActiveProfiles();
}
}注意:这里,我们在InvalidInitExampleBean的构造方法中试图访问一个自动注入的env字段,当真正执行时,你一定会得到一个空指针异常(NullPointerException)。原因在于,当构造方法被调用时,Spring上下文中的Environment这个Bean很可能还没有被实例化,同时也仍未注入到当前对象,所以并不能这样进行调用。
下面,我们来看看在SpringBoot中实现"安全初始化"的一些方法:
一、@PostConstruct 注解
@PostConstruct 注解其实是来自于 javax的扩展包中(大多数人的印象中是来自于Spring框架),它的作用在于声明一个Bean对象初始化完成后执行的方法。
来看看它的原始定义:
The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization.
也就是说,该方法会在所有依赖字段注入后才执行,当然这一动作也是由Spring框架执行的。
示例:
package com.cfcc.teis.load;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Arrays;
/**
* @Description TODO
* @date 2024/12/5 11:19
* @Version 1.0
* @Author gezongyang
*/
@Component
@Slf4j
public class InvalidInitExampleBean {
@Autowired
private Environment environment;
/**
* 该方法会在所有依赖字段(environment)注入后才执行,当然这一动作也是由Spring框架执行的。
*/
@PostConstruct
public void init() {
//environment 已经注入
log.info("@PostConstruct execute:{}",Arrays.asList(environment.getDefaultProfiles()));
}
}二、实现 InitializingBean 接口
InitializingBean 是由Spring框架提供的接口,其与@PostConstruct注解的工作原理非常类似。
如果不使用注解的话,你需要让Bean实例继承 InitializingBean接口,并实现afterPropertiesSet()这个方法。
示例:
package com.cfcc.teis.load;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @Description 继承 InitializingBean接口,并实现afterPropertiesSet()
* afterPropertiesSet() 会在所有依赖的字段(environment)注入后才执行
* @date 2024/12/5 11:37
* @Version 1.0
* @Author gezongyang
*/
@Component
@Slf4j
public class InitializingBeanExampleBean implements InitializingBean {
@Autowired
private Environment environment;
/**
* 这个方法会在environment注入后执行
*/
@Override
public void afterPropertiesSet() {
//environment 已经注入
log.info("InitializingBean execute:{}", Arrays.asList(environment.getDefaultProfiles()));
}
}三、@Bean initMethod方法
我们在声明一个Bean的时候,可以同时指定一个initMethod属性,该属性会指向Bean的一个方法,表示在初始化后执行。
示例:
package com.cfcc.teis.load;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @Description 指定一个initMethod属性,该属性会指向Bean的一个方法,表示在初始化后执行。
* @date 2024/12/5 11:37
* @Version 1.0
* @Author gezongyang
*/
@Component
@Slf4j
public class InitializingBean {
@Autowired
private Environment environment;
/**
* 这个方法会在environment注入后执行
*/
public void init() {
//environment 已经注入
log.info("@Bean initMethod execute:{}", Arrays.asList(environment.getDefaultProfiles()));
}
/**
* 这里将initMethod指向init方法,相应的我们也需要在Bean中实现这个方法:
* @return
*/
@Bean(initMethod="init")
public InitializingBean exBean() {
return new InitializingBean();
}
}四、构造器注入
如果依赖的字段在Bean的构造方法中声明,那么Spring框架会先实例这些字段对应的Bean,再调用当前的构造方法。
package com.cfcc.teis.load;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @Description 如果依赖的字段在Bean的构造方法中声明,那么Spring框架会先实例这些字段对应的Bean,
* 再调用当前的构造方法。
* @date 2024/12/5 14:11
* @Version 1.0
* @Author gezongyang
*/
@Slf4j
@Component
public class LogicInConstructorExampleBean {
private final Environment environment;
/**
* LogicInConstructorExampleBean(Environment environment) 调用在
* Environment 对象实例化之后
* @param environment
*/
@Autowired
public LogicInConstructorExampleBean(Environment environment) {
//environment实例已初始化
this.environment = environment;
log.info("LogicInConstructor:{}", Arrays.asList(environment.getDefaultProfiles()));
}
}五、实现ApplicationListener 接口
ApplicationListener 是由 spring-context组件提供的一个接口,主要是用来监听 “容器上下文的生命周期事件”。它的定义如下:
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}这里的event可以是任何一个继承于ApplicationEvent的事件对象。 对于初始化工作来说,我们可以通过监听ContextRefreshedEvent这个事件来捕捉上下文初始化的时机。
示例:
package com.cfcc.teis.load;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
/**
* @Description 当所有的Bean都初始化完成后,执行onApplicationEvent 方法
* @date 2024/12/5 14:19
* @Version 1.0
* @Author gezongyang
*/
@Slf4j
@Component
public class StartupApplicationListenerExample implements ApplicationListener<ContextRefreshedEvent> {
public static int counter;
/**
* 这里的event可以是任何一个继承于ApplicationEvent的事件对象。
* 对于初始化工作来说,我们可以通过监听ContextRefreshedEvent这个事件来捕捉上下文初始化的时机。
* @param event
*/
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
log.info("ApplicationListener run!");
counter++;
}
}在Spring上下文初始化完成后,这里定义的方法将会被执行。 与前面的 InitializingBean 不同的是,通过 ApplicationListener 监听的方式是全局性的,也就是当 所有 的 Bean 都 初始化完成 后才会执行方法。
Spring 4.2 之后引入了新的 @EventListener注解,可以实现同样的效果:
package com.cfcc.teis.load;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* @Description TODO
* @date 2024/12/5 14:24
* @Version 1.0
* @Author gezongyang
*/
@Slf4j
@Component
public class EventListenerExampleBean {
public static int counter;
/**
* 所有的Bean都初始化完成后,执行onApplicationEvent方法。
* @param event
*/
@EventListener
public void onApplicationEvent(ContextRefreshedEvent event) {
log.info("@EventListener execute", event.toString());
counter++;
}
}六、实现 CommandLineRunner 接口
SpringBoot 提供了一个CommanLineRunner接口,用来实现在应用启动后的逻辑控制,其定义如下:
public interface CommandLineRunner {
/**
* Callback used to run the bean.
* @param args incoming main method arguments
* @throws Exception on error
*/
void run(String... args) throws Exception;
}此外,对于多个CommandLineRunner的情况下可以使用@Order注解来控制它们的顺序。
案例:
1 定义一个CommandDemo实现CommandLineRunner,并纳入到spring容器
package com.cfcc.teis.load;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/**
* @Description 应用启动后的回调函数,在应用启动后执行此方法
* @date 2024/12/5 14:32
* @Version 1.0
* @Author gezongyang
*/
@Component
@Slf4j
public class CommandDemo implements CommandLineRunner {
/**
* 此方法在应用启动后执行
* @param args
*/
@Override
public void run(String... args) {
log.info("CommandLineRunner run!");
}
}2 配置参数,然后执行启动类

3 打印结果:
=====应用已经启动成功======[aaa,bbb]
七、实现 ApplicationRunner 接口
package org.springframework.boot;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
/**
* Interface used to indicate that a bean should <em>run</em> when it is contained within
* a {@link SpringApplication}. Multiple {@link ApplicationRunner} beans can be defined
* within the same application context and can be ordered using the {@link Ordered}
* interface or {@link Order @Order} annotation.
*
* @author Phillip Webb
* @since 1.3.0
* @see CommandLineRunner
*/
@FunctionalInterface
public interface ApplicationRunner {
/**
* Callback used to run the bean.
* @param args incoming application arguments
* @throws Exception on error
*/
void run(ApplicationArguments args) throws Exception;
}与 CommandLineRunner接口类似, Spring boot 还提供另一个ApplicationRunner 接口来实现初始化逻辑。不同的地方在于 ApplicationRunner.run()方法接受的是封装好的ApplicationArguments参数对象,而不是简单的字符串参数。ApplicationArguments是对参数做了进一步的处理,可以解析key=value形式,我们可以通过name来获取value(而CommandLineRunner只是获取key=value整体)
package org.springframework.boot;
import java.util.List;
import java.util.Set;
/**
* Provides access to the arguments that were used to run a {@link SpringApplication}.
*
* @author Phillip Webb
* @since 1.3.0
*/
public interface ApplicationArguments {
/**
* Return the raw unprocessed arguments that were passed to the application.
* @return the arguments
*/
String[] getSourceArgs();
/**
* Return the names of all option arguments. For example, if the arguments were
* "--foo=bar --debug" would return the values {@code ["foo", "debug"]}.
* @return the option names or an empty set
*/
Set<String> getOptionNames();
/**
* Return whether the set of option arguments parsed from the arguments contains an
* option with the given name.
* @param name the name to check
* @return {@code true} if the arguments contain an option with the given name
*/
boolean containsOption(String name);
/**
* Return the collection of values associated with the arguments option having the
* given name.
* <ul>
* <li>if the option is present and has no argument (e.g.: "--foo"), return an empty
* collection ({@code []})</li>
* <li>if the option is present and has a single value (e.g. "--foo=bar"), return a
* collection having one element ({@code ["bar"]})</li>
* <li>if the option is present and has multiple values (e.g. "--foo=bar --foo=baz"),
* return a collection having elements for each value ({@code ["bar", "baz"]})</li>
* <li>if the option is not present, return {@code null}</li>
* </ul>
* @param name the name of the option
* @return a list of option values for the given name
*/
List<String> getOptionValues(String name);
/**
* Return the collection of non-option arguments parsed.
* @return the non-option arguments or an empty list
*/
List<String> getNonOptionArgs();
}ApplicationArguments可以接收key=value这样的参数,getOptionNames()方法可以得到key的集合,getOptionValues(String name)方法可以得到value这样的集合
示例:
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
/**
* @Description TODO
* @date 2022/6/23 9:55
* @Version 1.0
* @Author gezongyang
*/
@Component
public class ApplicationRunnerDemo implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("====getOptionNames======"+args.getOptionNames());
System.out.println("====getOptionValues====="+args.getOptionValues("key"));
}
}配置参数启动

打印结果:
====getOptionNames======[key] ====getOptionValues=====[value]
八、实现SmartLifecycle 接口
package org.springframework.context;
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable var1);
}SmartLifecycle 是 Spring Framework 中的一个接口,它扩展了 Lifecycle 接口,并提供了更灵活的生命周期管理功能。使用 SmartLifecycle 可以更精细地控制应用组件的启动和关闭过程。以下是 SmartLifecycle 的一些主要特性:
特性:
isAutoStartup(): 返回一个布尔值,指示该组件是否应该在容器启动时自动启动。
getPhase(): 返回一个整数值,表示组件的启动顺序(阶段)。较低的数字表示较早的启动顺序。
start(): 启动组件,当返回时,组件应该已经启动完毕。
stop(Runnable callback): 停止组件,并且可以在停止完成后执行提供的回调函数。
isRunning(): 返回一个布尔值,指示组件当前是否处于运行状态。
案例:
package com.cfcc.teis.load;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;
/**
* @Description SmartLifecycle 可以更精细地控制应用组件的启动和关闭过程
* @date 2024/12/5 15:10
* @Version 1.0
* @Author gezongyang
*/
@Slf4j
@Component
public class SmartLifecycleDemo implements SmartLifecycle {
private volatile boolean running = false;
/**
* SmartLifecycle run
*/
@Override
public void start() {
log.info("SmartLifecycle run!");
this.running = true;
}
@Override
public void stop() {
}
/**
* 在这里放置停止逻辑
* @param callback
*/
@Override
public void stop(Runnable callback) {
this.running = false;
callback.run(); // 确保回调被执行
}
@Override
public boolean isRunning() {
return this.running;
}
/**
* 组件将在Spring上下文加载后自动启动
* @return
*/
@Override
public boolean isAutoStartup() {
return true;
}
/**
* 定义启动顺序,数字越小越先启动
* @return
*/
@Override
public int getPhase() {
return 0;
}
}注意事项
当你实现 SmartLifecycle 时,确保 start() 方法不会阻塞,因为它会延迟整个应用程序的启动。如果需要长时间运行的任务,考虑将其放入单独的线程中。
stop(Runnable callback) 方法中的回调是重要的,必须调用它来通知框架组件已经停止。如果你不打算使用某些方法,可以不重写它们;默认实现将提供合理的行为。
通过使用 SmartLifecycle,你可以更好地控制应用程序中不同组件的生命周期行为,这对于那些对启动和关闭有特殊需求的应用特别有用。
SmartLifecycle 的执行时机:
是在 Spring 应用上下文刷新完成之后,即所有的 Bean 都已经被实例化和初始化之后。具体来说,Spring 容器在调用 finishRefresh() 方法时会触发所有实现了 SmartLifecycle 接口的 Bean 的启动过程。这是通过容器中的 LifecycleProcessor 来管理的,默认使用的是 DefaultLifecycleProcessor。
SmartLifecycle 执行的具体流程:
上下文刷新完成后:当 Spring 应用上下文完成刷新(finishRefresh),意味着所有的 Bean 已经被加载并初始化完毕。
初始化生命周期处理器:Spring 会初始化 LifecycleProcessor,如果没有显式定义,则创建一个默认的 DefaultLifecycleProcessor。
调用 onRefresh():接着,Spring 会调用 LifecycleProcessor 的 onRefresh() 方法,这将导致调用 startBeans() 方法。
根据 phase 分组并排序:startBeans() 方法会获取所有实现了 Lifecycle 接口的 Bean,并根据它们的 phase 属性进行分组和排序。phase 值越小的 Bean 将越早启动。
检查 isAutoStartup():对于每个 SmartLifecycle Bean,如果它的 isAutoStartup() 方法返回 true,那么它的 start() 方法就会被自动调用。
执行 start() 方法:按照 phase 排序后的顺序,依次调用每个 SmartLifecycle Bean 的 start() 方法来启动组件。
设置运行状态:start() 方法应该确保组件处于运行状态,并且 isRunning() 方法应返回 true。
同样的逻辑也适用于停止组件的过程,但是是以相反的顺序执行的,即 phase 值较大的 Bean 会先停止。
因此,如果你希望在 Spring 容器完全准备好之后执行某些任务,比如开启消息监听、启动定时任务等,你可以实现 SmartLifecycle 接口并在 start() 方法中放置这些逻辑。同时,通过调整 getPhase() 返回的值,可以控制多个 SmartLifecycle Bean 之间的启动顺序。
以上就是SpringBoot初始化加载配置的八种方式总结的详细内容,更多关于SpringBoot初始化加载配置的资料请关注脚本之家其它相关文章!
