- @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初始化加载配置的资料请关注代码网其它相关文章!
发表评论