Spring Boot中ApplicationRunner与CommandLineRunner执行顺序探秘
各位朋友,大家好!今天我们来深入探讨Spring Boot中ApplicationRunner和CommandLineRunner这两个接口,重点关注它们的执行顺序以及如何在实际应用中灵活运用它们。
什么是ApplicationRunner和CommandLineRunner?
在Spring Boot应用启动过程中,我们经常需要在应用上下文完全加载完毕后执行一些初始化操作,例如加载配置、数据库连接、缓存预热等等。Spring Boot提供了两个非常方便的接口来实现这些需求:ApplicationRunner和CommandLineRunner。
CommandLineRunner: 提供对命令行参数的访问,适用于处理启动时传递的命令行参数。ApplicationRunner: 提供对ApplicationArguments的访问,可以更灵活地处理命令行参数,包括选项和非选项参数。
简单来说,它们都是在Spring Boot应用启动完成后执行的接口,用于执行一些初始化任务。它们的区别在于对命令行参数的处理方式不同。
接口定义
我们先来看看这两个接口的定义:
CommandLineRunner
package org.springframework.boot;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
@FunctionalInterface
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;
}
ApplicationRunner
package org.springframework.boot;
import org.springframework.boot.ApplicationArguments;
@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的run方法接收一个String... args参数,而ApplicationRunner的run方法接收一个ApplicationArguments args参数。ApplicationArguments接口提供了更丰富的方法来访问命令行参数,例如:
String[] getSourceArgs(): 获取原始的命令行参数数组。String[] getOptionNames(): 获取所有选项的名称。List<String> getOptionValues(String name): 获取指定选项的值。List<String> getNonOptionArgs(): 获取所有非选项参数。
如何使用它们?
使用ApplicationRunner和CommandLineRunner非常简单,只需要创建一个Bean,实现相应的接口,并重写run方法即可。
示例:CommandLineRunner
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner executed!");
if (args != null && args.length > 0) {
System.out.println("Command line arguments:");
for (String arg : args) {
System.out.println(arg);
}
} else {
System.out.println("No command line arguments provided.");
}
}
}
示例:ApplicationRunner
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner executed!");
System.out.println("Non option Args:" + args.getNonOptionArgs());
System.out.println("Option Names:" + args.getOptionNames());
}
}
运行上述代码,并传递一些命令行参数,例如:
java -jar myapp.jar --name=John --age=30 arg1 arg2
你将会看到类似以下的输出:
CommandLineRunner executed!
Command line arguments:
--name=John
--age=30
arg1
arg2
ApplicationRunner executed!
Non option Args:[arg1, arg2]
Option Names:[name, age]
执行顺序:默认情况
默认情况下,Spring Boot会按照以下规则执行ApplicationRunner和CommandLineRunner:
- 首先执行所有实现了
CommandLineRunner接口的Bean。 - 然后执行所有实现了
ApplicationRunner接口的Bean。 - 在同一类型的Runner中,如果没有指定顺序,则按照Bean的名称进行排序。
示例:默认顺序
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component("commandLineRunnerA")
public class CommandLineRunnerA implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunnerA executed!");
}
}
@Component("commandLineRunnerB")
public class CommandLineRunnerB implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunnerB executed!");
}
}
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component("applicationRunnerA")
public class ApplicationRunnerA implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunnerA executed!");
}
}
@Component("applicationRunnerB")
public class ApplicationRunnerB implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunnerB executed!");
}
}
运行上述代码,你将会看到类似以下的输出:
CommandLineRunnerA executed!
CommandLineRunnerB executed!
ApplicationRunnerA executed!
ApplicationRunnerB executed!
可以看到,CommandLineRunner先于ApplicationRunner执行,并且在同一类型中,按照Bean的名称排序(A在B之前)。
使用@Order注解控制执行顺序
如果我们需要精确地控制ApplicationRunner和CommandLineRunner的执行顺序,可以使用@Order注解。@Order注解可以指定一个整数值,值越小,优先级越高,越先执行。
示例:使用@Order注解
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(2)
public class OrderedCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("OrderedCommandLineRunner executed with order 2!");
}
}
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(1)
public class OrderedApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("OrderedApplicationRunner executed with order 1!");
}
}
运行上述代码,你将会看到以下输出:
OrderedApplicationRunner executed with order 1!
OrderedCommandLineRunner executed with order 2!
可以看到,虽然CommandLineRunner默认情况下先于ApplicationRunner执行,但是通过@Order注解,我们可以让ApplicationRunner先于CommandLineRunner执行。
注意: @Order注解不仅可以用于ApplicationRunner和CommandLineRunner,还可以用于任何实现了Ordered接口或者使用了@Priority注解的Bean。Spring Boot会根据这些信息来确定Bean的执行顺序。
使用Ordered接口控制执行顺序
除了使用@Order注解,我们还可以让Bean实现org.springframework.core.Ordered接口来控制执行顺序。实现Ordered接口需要重写getOrder()方法,该方法返回一个整数值,值越小,优先级越高。
示例:使用Ordered接口
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
@Component
public class OrderedCommandLineRunner implements CommandLineRunner, Ordered {
@Override
public void run(String... args) throws Exception {
System.out.println("OrderedCommandLineRunner executed with order 2!");
}
@Override
public int getOrder() {
return 2;
}
}
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
@Component
public class OrderedApplicationRunner implements ApplicationRunner, Ordered {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("OrderedApplicationRunner executed with order 1!");
}
@Override
public int getOrder() {
return 1;
}
}
运行上述代码,你将会看到以下输出:
OrderedApplicationRunner executed with order 1!
OrderedCommandLineRunner executed with order 2!
可以看到,使用Ordered接口的效果与使用@Order注解是相同的。
优先级比较:@Order vs Ordered vs Bean Name
当同时使用@Order注解、Ordered接口和Bean名称来定义执行顺序时,Spring Boot会按照以下优先级进行排序:
@Order注解: 如果存在@Order注解,则使用注解的值作为优先级。Ordered接口: 如果实现了Ordered接口,则使用getOrder()方法的返回值作为优先级。- Bean名称: 如果没有指定顺序,则按照Bean的名称进行排序。
也就是说,@Order注解的优先级最高,Ordered接口的优先级次之,Bean名称的优先级最低。
示例:优先级比较
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component("commandLineRunnerA")
@Order(3)
public class CommandLineRunnerA implements CommandLineRunner, Ordered {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunnerA executed with order 3!");
}
@Override
public int getOrder() {
return 1;
}
}
@Component("commandLineRunnerB")
public class CommandLineRunnerB implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunnerB executed!");
}
}
在这个例子中,CommandLineRunnerA同时使用了@Order注解、Ordered接口和Bean名称来定义执行顺序。由于@Order注解的优先级最高,因此CommandLineRunnerA的优先级为3。CommandLineRunnerB没有指定顺序,因此按照Bean名称排序,它的优先级低于CommandLineRunnerA。
运行上述代码,你将会看到以下输出:
CommandLineRunnerB executed!
CommandLineRunnerA executed with order 3!
可以看到,CommandLineRunnerB先于CommandLineRunnerA执行,因为CommandLineRunnerB的默认优先级比CommandLineRunnerA的@Order注解指定的优先级低。虽然CommandLineRunnerA实现了Ordered接口,但是由于@Order注解的优先级更高,因此getOrder()方法的返回值被忽略了。
异常处理
在ApplicationRunner和CommandLineRunner的run方法中,如果发生异常,Spring Boot会将异常记录到日志中,并继续执行其他的Runner。如果希望在发生异常时停止应用启动,可以抛出Exception或者其子类。
示例:异常处理
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class ExceptionCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("ExceptionCommandLineRunner executed!");
throw new Exception("Something went wrong!");
}
}
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class AfterExceptionApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("AfterExceptionApplicationRunner executed!");
}
}
运行上述代码,你将会看到以下输出:
ExceptionCommandLineRunner executed!
// 异常日志
AfterExceptionApplicationRunner executed!
可以看到,虽然ExceptionCommandLineRunner抛出了异常,但是AfterExceptionApplicationRunner仍然被执行了。
使用场景
ApplicationRunner和CommandLineRunner在实际应用中有很多用途,例如:
- 加载配置: 从数据库、文件或者远程服务器加载配置信息。
- 数据库连接: 建立数据库连接,并执行一些初始化操作,例如创建表、导入数据等。
- 缓存预热: 将一些常用的数据加载到缓存中,提高应用的性能。
- 定时任务: 启动定时任务,例如定期清理缓存、备份数据等。
- 监听器注册: 注册监听器,例如监听消息队列、文件变化等。
- 参数校验: 校验启动参数是否合法,如果不合法则退出应用。
示例:数据库连接
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@Component
public class DatabaseConnectionRunner implements CommandLineRunner {
private final DataSource dataSource;
public DatabaseConnectionRunner(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void run(String... args) throws Exception {
try (Connection connection = dataSource.getConnection()) {
System.out.println("Database connection successful!");
// 执行一些初始化操作,例如创建表、导入数据等
} catch (SQLException e) {
System.err.println("Failed to connect to database: " + e.getMessage());
}
}
}
在这个例子中,DatabaseConnectionRunner使用DataSource来建立数据库连接,并在连接成功后执行一些初始化操作。如果连接失败,则会记录错误日志。
总结
ApplicationRunner和CommandLineRunner是Spring Boot中非常实用的接口,可以帮助我们在应用启动完成后执行一些初始化任务。它们的主要区别在于对命令行参数的处理方式不同,ApplicationRunner提供了更灵活的参数访问方式。我们可以使用@Order注解或者Ordered接口来控制它们的执行顺序。掌握这两个接口的使用方法,可以让我们更好地构建和管理Spring Boot应用。
记住,默认情况下CommandLineRunner先执行,同类型Runner按照Bean名称排序,而@Order注解的优先级最高。
如何选择?
那么,在实际开发中,我们应该选择ApplicationRunner还是CommandLineRunner呢?
- 如果只需要访问原始的命令行参数数组,或者只需要处理简单的命令行参数,可以使用
CommandLineRunner。 - 如果需要更灵活地处理命令行参数,例如区分选项和非选项参数,或者需要获取选项的值,可以使用
ApplicationRunner。 - 如果对执行顺序有严格的要求,可以使用
@Order注解或者Ordered接口来控制执行顺序。
通常情况下,ApplicationRunner提供了更多的功能,因此更受欢迎。但是,CommandLineRunner在某些简单的场景下也足够使用。
希望今天的讲解能够帮助大家更好地理解Spring Boot中ApplicationRunner和CommandLineRunner的使用方法和执行顺序。感谢大家的聆听!
灵活运用,提升启动效率
理解了ApplicationRunner和CommandLineRunner的执行顺序和使用方式,我们可以更灵活地运用它们,例如将一些耗时的初始化任务放在较低优先级的Runner中执行,避免阻塞应用启动。或者将一些必须在其他任务之前执行的任务放在较高优先级的Runner中执行,确保应用的正确运行。