Spring Boot 中的 ApplicationRunner 和 CommandLineRunner

god23bin發表於2023-04-04

前言

一般專案中的初始化操作,初次遇見,妙不可言。如果你還有哪些方式可用於初始化操作,歡迎在評論中分享出來~

ApplicationRunner 和 CommandLineRunner

Spring Boot 應用,在啟動的時候,如果想做一些事情,比如預先載入並快取某些資料,讀取某些配置等等。總而言之,做一些初始化的操作時,那麼 Spring Boot 就提供了兩個介面幫助我們實現。

這兩個介面是:

  • ApplicationRunner 介面
  • CommandLineRunner 介面

原始碼如下:

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

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 CommandLineRunner} beans can be defined
 * within the same application context and can be ordered using the {@link Ordered}
 * interface or {@link Order @Order} annotation.
 * <p>
 * If you need access to {@link ApplicationArguments} instead of the raw String array
 * consider using {@link ApplicationRunner}.
 *
 * @author Dave Syer
 * @since 1.0.0
 * @see 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;

}

可以看到,這兩個介面的註釋幾乎一模一樣,如出一轍。大致的意思就是,這兩個介面可以在 Spring 的環境下指定一個 Bean 執行(run)某些你想要做的事情,如果你有多個 Bean 進行指定,那麼可以透過 Ordered 介面或者 @Order 註解指定執行順序。

說白了,就是可以搞多個實現類實現這兩個介面,透過 @Order 確定實現類的誰先執行,誰後執行

@Order

再看看 @Order 註解的原始碼:

/**
 * {@code @Order} defines the sort order for an annotated component.
 *
 * <p>The {@link #value} is optional and represents an order value as defined in the
 * {@link Ordered} interface. Lower values have higher priority. The default value is
 * {@code Ordered.LOWEST_PRECEDENCE}, indicating lowest priority (losing to any other
 * specified order value).
 * ..... 省略剩下的註釋
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {

    /**
     * The order value.
     * <p>Default is {@link Ordered#LOWEST_PRECEDENCE}.
     * @see Ordered#getOrder()
     */
    int value() default Ordered.LOWEST_PRECEDENCE;

}

Ordered.LOWEST_PRECEDENCE 的預設值是 Integer.MAX_VALUE

在頂部的註釋中,可以知道,@Order 註解是給使用了 @Component 註解的 Bean 定義排序順序(defines the sort order for an annotated component),然後 @Order 註解的 value 屬性值越低,那麼代表這個 Bean 有著更高的優先順序(Lower values have higher priority)。

測試

分別寫兩個實現類,實現這兩個介面,然後啟動 Spring Boot 專案,看看執行順序

ApplicationRunnerImpl:

@Slf4j
@Component
public class ApplicationRunnerImpl implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("我正在載入 ------------------> ApplicationRunnerImpl");
    }
}

CommandLineRunnerImpl:

@Slf4j
@Component
public class CommandLineRunnerImpl implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        log.info("我正在載入 ------------------> CommandLineRunnerImpl");
    }
}

控制檯輸出:

2023-03-26 15:46:38.344  INFO 25616 --- [           main] c.g.demo.init.ApplicationRunnerImpl      : 我正在載入 ------------------> ApplicationRunnerImpl
2023-03-26 15:46:38.344  INFO 25616 --- [           main] c.g.demo.init.CommandLineRunnerImpl      : 我正在載入 ------------------> CommandLineRunnerImpl

可以看到,是 ApplicationRunnerImpl 先執行的,CommandLineRunnerImpl 後執行的。

我們給 CommandLineRunnerImpl 加上 @Order 註解,給其 value 屬性設定 10:

@Slf4j
@Order(10)
@Component
public class CommandLineRunnerImpl implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        log.info("我正在載入 ------------------> CommandLineRunnerImpl");
    }
}

控制檯輸出:

2023-03-26 15:50:43.524  INFO 16160 --- [           main] c.g.demo.init.CommandLineRunnerImpl      : 我正在載入 ------------------> CommandLineRunnerImpl
2023-03-26 15:50:43.524  INFO 16160 --- [           main] c.g.demo.init.ApplicationRunnerImpl      : 我正在載入 ------------------> ApplicationRunnerImpl

區別

回到這兩個介面,看似一模一樣,但肯定有小小區別的,最主要的區別就是介面的抽象方法的引數

ApplicationRunner:

  • void run(ApplicationArguments args) throws Exception;

CommandLineRunner:

  • void run(String... args) throws Exception;

具體來說,ApplicationRunner 介面的 run 方法中的引數為 ApplicationArguments 物件,該物件封裝了應用程式啟動時傳遞的命令列引數和選項。

CommandLineRunner 介面的 run 方法中的引數為 String 陣列,該陣列直接包含了應用程式啟動時傳遞的命令列引數和選項。

測試

列印下命令列引數:

@Slf4j
@Component
public class ApplicationRunnerImpl implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("ApplicationRunner: optionNames = " + args.getOptionNames() + ", sourceArgs = " + args.getSourceArgs());
    }
}
@Slf4j
@Component
public class CommandLineRunnerImpl implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("CommandLineRunner: " + Arrays.toString(args));
    }
}

用 Maven 打包專案為 Jar 包,啟動該 Jar 包:

// 使用 java -jar 啟動,加上兩個引數:name 和 description
java -jar demo-0.0.1-SNAPSHOT.jar --name=god23bin --description=like_me

輸出:

ApplicationRunner: optionNames = [name, description]sourceArgs = [Ljava.lang.String;@5c90e579
CommandLineRunner: [--name=god23bin, --description=like_me]

最後的最後

由本人水平所限,難免有錯誤以及不足之處, 螢幕前的靚仔靚女們 如有發現,懇請指出!

最後,謝謝你看到這裡,謝謝你認真對待我的努力,希望這篇部落格對你有所幫助!

你輕輕地點了個贊,那將在我的心裡世界增添一顆明亮而耀眼的星!

相關文章