SpringBoot启动时加载
一、CommandLineRunner和ApplicationRunner
1.1 应用及场景
SpringBoot
中提供了两个接口可以在SpringBoot
启动的过程中进行一些额外的操作,比如读取配置文件、数据库操作等自定义的内容。而这些功能的实现也非常简单,直接实现这两个接口并实现其run
方法,然后将该类实例化即可。以下代码便实现了CommandLineRunner
接口,并在run
方法内打印了对应的日志,同时,通过@Component
将其注册为Spring
的一个bean
。
@Component
public class LearnCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) {
System.out.println("LearnCommandLineRunner running");
}
}
1.2 源代码
下面看一下CommandLineRunner
和ApplicationRunner
的源代码:
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;
}
public interface ApplicationRunner {
/**
* Callback used to run the bean.
* @param args incoming application arguments
* @throws Exception on error
*/
void run(ApplicationArguments args) throws Exception;
}
通过源代码的对比会发现,它们唯一不同便是run
方法的参数。在实际应用中它们的区别也只有这些。
1.3 执行顺序
通过接口的官方文档,我们得知其实执行CommandLineRunner
和ApplicationRunner
的实现类是有顺序的,只不过在示例中并没有展示。针对上面的示例,我们可以通过@Order
或实现Ordered
接口来对其指定执行顺序。
@Order(value = 1)
@Component
public class LearnCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) {
System.out.println("LearnCommandLineRunner running");
}
}
1.4 使用源码分析
说到执行顺序,那么再进一步了解一下这两个方法是在什么时候执行的。这两个接口的实现执行的时机在于SpringApplication
初始化之后,调用的run
方法中被调用的。
public ConfigurableApplicationContext run(String... args) {
// 创建StopWatch对象,用于统计run方法启动时长
StopWatch stopWatch = new StopWatch();
// 启动统计
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置headless属性
configureHeadlessProperty();
// 获得SpringApplicationRunListener数组,
// 该数组封装于SpringApplicationRunListeners对象的listeners中。
SpringApplicationRunListeners listeners = getRunListeners(args);
// 启动监听,遍历SpringApplicationRunListener数组每个元素,并执行。
listeners.starting();
try {
// 创建ApplicationArguments对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 加载属性配置,包括所有的配置属性(如:application.properties中和外部的属性配置)
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印Banner
Banner printedBanner = printBanner(environment);
// 创建容器
context = createApplicationContext();
// 异常报告器
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备容器,组件对象之间进行关联
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 初始化容器
refreshContext(context);
// 初始化操作之后执行,默认实现为空。
afterRefresh(context, applicationArguments);
// 停止时长统计
stopWatch.stop();
// 打印启动日志
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 通知监听器:容器启动完成。
listeners.started(context);
// 调用ApplicationRunner和CommandLineRunner的运行方法。
callRunners(context, applicationArguments);
} catch (Throwable ex) {
// 异常处理
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 通知监听器:容器正在运行。
listeners.running(context);
} catch (Throwable ex) {
// 异常处理
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
我们可以看到,在try
方法的最后,会执行一个callRunners
的方法,在此方法中会对实现这两个接口的实现类进行调用。
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
try {
(runner).run(args);
} catch (Exception ex) {
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
}
}
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
try {
(runner).run(args.getSourceArgs());
} catch (Exception ex) {
throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
}
}
通过以上代码,我们也就了解到这两个接口的实现类的执行时机了。
二、@PostConstruct
另一个需求是,在类加载的时候,为当前类初始化一些数据,那么可以使用@PostConstruct
注解。
Servlet
中增加了两个影响Servlet
生命周期的注解,@PostConstruct
和@PreDestroy
,这两个注解被用来修饰一个非静态的void()
方法。
在一个类内,如果有构造器(Constructor
),有@PostConstruct
,还有@Autowired
,他们的先后执行顺序为Constructor
>> @Autowired
>> @PostConstruct
。
因为一个有声明注解的类文件(必须有声明,这样在项目初始化时候才会注入),在项目启动后,会对对象进行依赖注入,而初始化的动作会依赖于对象,所以假象上看,也类似于项目启动就会执行的操作,因此,我们也可以通过这样的形式,对数据进行初始化。
说明一下@PostConstruct
更针对性于当前类文件,而CommandLineRunner
更服务于整个项目。所以在我们使用中,可根据自己的使用场景来进行选择用这两种方式来实现初始化。
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class Init {
@PostConstruct
private void init(){
System.out.println("PostConstruct注解 初始化数据.");
}
}
说明一下:执行结果可以看到,在项目还没有启动成功的时候,@PostConstruct
已经执行完了,因为@PostConstruct
是在Init
类注入完成后立马执行的,它并不依赖于项目的启动。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器