Spring Boot中ApplicationRunner与CommandLineRunner执行顺序探秘

Spring Boot中ApplicationRunner与CommandLineRunner执行顺序探秘

各位朋友,大家好!今天我们来深入探讨Spring Boot中ApplicationRunnerCommandLineRunner这两个接口,重点关注它们的执行顺序以及如何在实际应用中灵活运用它们。

什么是ApplicationRunner和CommandLineRunner?

在Spring Boot应用启动过程中,我们经常需要在应用上下文完全加载完毕后执行一些初始化操作,例如加载配置、数据库连接、缓存预热等等。Spring Boot提供了两个非常方便的接口来实现这些需求:ApplicationRunnerCommandLineRunner

  • 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;

}

可以看到,CommandLineRunnerrun方法接收一个String... args参数,而ApplicationRunnerrun方法接收一个ApplicationArguments args参数。ApplicationArguments接口提供了更丰富的方法来访问命令行参数,例如:

  • String[] getSourceArgs(): 获取原始的命令行参数数组。
  • String[] getOptionNames(): 获取所有选项的名称。
  • List<String> getOptionValues(String name): 获取指定选项的值。
  • List<String> getNonOptionArgs(): 获取所有非选项参数。

如何使用它们?

使用ApplicationRunnerCommandLineRunner非常简单,只需要创建一个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会按照以下规则执行ApplicationRunnerCommandLineRunner

  1. 首先执行所有实现了CommandLineRunner接口的Bean。
  2. 然后执行所有实现了ApplicationRunner接口的Bean。
  3. 在同一类型的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注解控制执行顺序

如果我们需要精确地控制ApplicationRunnerCommandLineRunner的执行顺序,可以使用@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注解不仅可以用于ApplicationRunnerCommandLineRunner,还可以用于任何实现了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会按照以下优先级进行排序:

  1. @Order注解: 如果存在@Order注解,则使用注解的值作为优先级。
  2. Ordered接口: 如果实现了Ordered接口,则使用getOrder()方法的返回值作为优先级。
  3. 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()方法的返回值被忽略了。

异常处理

ApplicationRunnerCommandLineRunnerrun方法中,如果发生异常,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仍然被执行了。

使用场景

ApplicationRunnerCommandLineRunner在实际应用中有很多用途,例如:

  • 加载配置: 从数据库、文件或者远程服务器加载配置信息。
  • 数据库连接: 建立数据库连接,并执行一些初始化操作,例如创建表、导入数据等。
  • 缓存预热: 将一些常用的数据加载到缓存中,提高应用的性能。
  • 定时任务: 启动定时任务,例如定期清理缓存、备份数据等。
  • 监听器注册: 注册监听器,例如监听消息队列、文件变化等。
  • 参数校验: 校验启动参数是否合法,如果不合法则退出应用。

示例:数据库连接

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来建立数据库连接,并在连接成功后执行一些初始化操作。如果连接失败,则会记录错误日志。

总结

ApplicationRunnerCommandLineRunner是Spring Boot中非常实用的接口,可以帮助我们在应用启动完成后执行一些初始化任务。它们的主要区别在于对命令行参数的处理方式不同,ApplicationRunner提供了更灵活的参数访问方式。我们可以使用@Order注解或者Ordered接口来控制它们的执行顺序。掌握这两个接口的使用方法,可以让我们更好地构建和管理Spring Boot应用。

记住,默认情况下CommandLineRunner先执行,同类型Runner按照Bean名称排序,而@Order注解的优先级最高。

如何选择?

那么,在实际开发中,我们应该选择ApplicationRunner还是CommandLineRunner呢?

  • 如果只需要访问原始的命令行参数数组,或者只需要处理简单的命令行参数,可以使用CommandLineRunner
  • 如果需要更灵活地处理命令行参数,例如区分选项和非选项参数,或者需要获取选项的值,可以使用ApplicationRunner
  • 如果对执行顺序有严格的要求,可以使用@Order注解或者Ordered接口来控制执行顺序。

通常情况下,ApplicationRunner提供了更多的功能,因此更受欢迎。但是,CommandLineRunner在某些简单的场景下也足够使用。

希望今天的讲解能够帮助大家更好地理解Spring Boot中ApplicationRunnerCommandLineRunner的使用方法和执行顺序。感谢大家的聆听!

灵活运用,提升启动效率

理解了ApplicationRunnerCommandLineRunner的执行顺序和使用方式,我们可以更灵活地运用它们,例如将一些耗时的初始化任务放在较低优先级的Runner中执行,避免阻塞应用启动。或者将一些必须在其他任务之前执行的任务放在较高优先级的Runner中执行,确保应用的正确运行。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注