Spring Boot:ApplicationRunner接口


#Spring Boot#


ApplicationRunner 接口和 CommandLineRunner 类似,在 Spring 容器启动完成时,继承 ApplicationRunner 接口的类的 run 方法会被自动执行(前提是这个类被Spring 管理)。相比于 CommandLineRunner, ApplicationRunner 提供了对命令行参数的解析功能。

示例1:入门

demo01 项目结构:

demo01
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── CustomApplicationRunner.java
    │   │       └── Demo01App.java
    │   └── resources
    └── test
        ├── java
        └── resources

build.gradle 内容:

buildscript {
    ext {
        springBootVersion = '2.1.3.RELEASE'
    }
    repositories {
        maven { url 'http://mirrors.cloud.tencent.com/nexus/repository/maven-public/' }
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    maven { url 'http://mirrors.cloud.tencent.com/nexus/repository/maven-public/' }
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

Demo01App 类内容:

package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Demo01App {

    public static void main(String[] args) {
        SpringApplication.run(Demo01App.class, args);
    }

}

CustomApplicationRunner 类内容:

package demo;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class CustomApplicationRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("This is " + getClass().getSimpleName());
    }

}

执行 Demo01App ,输出:

This is CustomApplicationRunner

示例2:获取命令行参数

demo02 项目结构:

demo02
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── CustomApplicationRunner.java
    │   │       └── Demo02App.java
    │   └── resources
    └── test
        ├── java
        └── resources

Demo02App 类代码:

package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Demo02App {

    public static void main(String[] args) {
        SpringApplication.run(Demo02App.class, args);
    }

}

CustomApplicationRunner 类代码:

package demo;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class CustomApplicationRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("This is " + getClass().getSimpleName());
        // 获取所有命令行参数
        System.out.println("获取所有命令行参数");
        for (String arg: args.getSourceArgs()) {
            System.out.println(arg);
        }

        System.out.println("------");

        // 获取所有选项
        System.out.println("获取所有选项");
        for (String option: args.getOptionNames()) {
            System.out.println(option);
        }

        System.out.println("------");

        // 获取解析后的参数,以 --msg 为例子
        System.out.println("获取解析后的参数,以 --msg 为例子");
        if (args.containsOption("msg")) {
            System.out.println("--msg选项的值有:");
            for (String s:args.getOptionValues("msg")) {
                System.out.println(s);
            }
        }

        System.out.println("------");

        // 获取无选项指定的参数
        System.out.println("获取无选项指定的参数");
        for (String val: args.getNonOptionArgs()) {
            System.out.println(val);
        }
    }

}

containsOption 函数可以用来判断是否存在某个选项,可以当做开关用,也可以当做kv键值对的键来用。

进入 demo02 目录,打包,看下执行效果:

$ gradle build

$ java -jar ./build/libs/demo02-0.0.1-SNAPSHOT.jar  --msg
This is CustomApplicationRunner
获取所有命令行参数
--msg
------
获取所有选项
msg
------
获取解析后的参数,以 --msg 为例子
--msg选项的值有:
------
获取无选项指定的参数

$ java -jar ./build/libs/demo02-0.0.1-SNAPSHOT.jar  --msg=hi test.txt

This is CustomApplicationRunner01
获取所有命令行参数
--msg=hi
test.txt
------
获取所有选项
msg
------
获取解析后的参数,以 --msg 为例子
--msg选项的值有:
hi
------
获取无选项指定的参数
test.txt

$ java -jar ./build/libs/demo02-0.0.1-SNAPSHOT.jar  --msg=hi --msg=hi2 test.txt
This is CustomApplicationRunner01
获取所有命令行参数
--msg=hi
--msg=hi2
test.txt
------
获取所有选项
msg
------
获取解析后的参数,以 --msg 为例子
--msg选项的值有:
hi
hi2
------
获取无选项指定的参数
test.txt

示例3:多个 ApplicationRunner 实现类

demo03 项目结构:

demo03
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── CustomApplicationRunner01.java
    │   │       ├── CustomApplicationRunner02.java
    │   │       └── Demo03App.java
    │   └── resources
    └── test
        ├── java
        └── resources

Demo03App 类代码:

package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Demo03App {

    public static void main(String[] args) {
        SpringApplication.run(Demo03App.class, args);
    }

}

CustomApplicationRunner01 类代码:

package demo;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class CustomApplicationRunner01 implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("This is " + getClass().getSimpleName());
    }

}

CustomApplicationRunner02 类代码:

package demo;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class CustomApplicationRunner02 implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("This is " + getClass().getSimpleName());
    }

}

执行 Demo03App ,输出:

This is CustomApplicationRunner01
This is CustomApplicationRunner02

一个问题:为什么是先执行 CustomApplicationRunner01 ,再执行 CustomApplicationRunner01 呢?是按照spring加载bean的顺序,还是给句类名排序?spring加载bean 的顺序是怎么样的?

示例4:使用 @Order 注解自定义多个 ApplicationRunner 实现类的run方法执行顺序

demo04 项目结构:

demo04
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── CustomApplicationRunner01.java
    │   │       ├── CustomApplicationRunner02.java
    │   │       └── Demo04App.java
    │   └── resources
    └── test
        ├── java
        └── resources

代码和 demo03 类似,但是加上了 Order 注解。

package demo;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(1)  // 值越小,越先执行
public class CustomApplicationRunner01 implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("This is " + getClass().getSimpleName());
    }

}
package demo;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(0)  // 值越小,越先执行
public class CustomApplicationRunner02 implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("This is " + getClass().getSimpleName());
    }

}

执行 Demo04App ,输出:

This is CustomApplicationRunner02
This is CustomApplicationRunner01

示例5:使用 Ordered 接口自定义多个 ApplicationRunner 实现类的run方法执行顺序

demo05 项目结构:

demo05
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── CustomApplicationRunner01.java
    │   │       ├── CustomApplicationRunner02.java
    │   │       └── Demo05App.java
    │   └── resources
    └── test
        ├── java
        └── resources

代码和示例4类似。但是不用 Order 注解,而是实现 Ordered 接口的方法。

package demo;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Component
public class CustomApplicationRunner01 implements ApplicationRunner, Ordered {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("This is " + getClass().getSimpleName());
    }

    @Override
    public int getOrder() {
        return 1;
    }
}
package demo;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Component
public class CustomApplicationRunner02 implements ApplicationRunner, Ordered {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("This is " + getClass().getSimpleName());
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

执行 Demo05App ,输出:

This is CustomApplicationRunner02
This is CustomApplicationRunner01

示例6:SpringBootApplication 注解的类实现 ApplicationRunner 接口

demo06 项目结构如下:

demo06
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       └── Demo06App.java
    │   └── resources
    └── test
        ├── java
        └── resources

Demo06App 类内容:

package demo;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;

@SpringBootApplication
public class Demo06App  implements ApplicationRunner {  // 同时实现 ApplicationRunner 接口

    public static void main(String[] args) {
        SpringApplication.run(Demo06App.class, args);
        System.out.println("finish");
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("This is " + getClass().getSimpleName());
    }
}

执行该类,输出:

This is Demo06App$$EnhancerBySpringCGLIB$$c1cc2ff9
finish

示例7:ApplicationRunner 与 CommandLineRunner 混用

demo07 项目结构:

demo07
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── CustomApplicationRunner01.java
    │   │       ├── CustomApplicationRunner02.java
    │   │       ├── CustomCommandLineRunner01.java
    │   │       ├── CustomCommandLineRunner02.java
    │   │       └── Demo07App.java
    │   └── resources
    └── test
        ├── java
        └── resources

主要是想看下,Order注解能否同时对 ApplicationRunner 接口实现类和 CommandLineRunner 接口实现类,进行排序。

package demo;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(3)
public class CustomApplicationRunner01 implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("This is " + getClass().getSimpleName());
    }

}
package demo;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(1)
public class CustomApplicationRunner02 implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("This is " + getClass().getSimpleName());
    }

}
package demo;

import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(2)
public class CustomCommandLineRunner01 implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("This is " + getClass().getSimpleName());
    }
}
package demo;

import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(0)
public class CustomCommandLineRunner02 implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("This is " + getClass().getSimpleName());
    }
}

运行 Demo07App ,输出:

This is CustomCommandLineRunner02
This is CustomApplicationRunner02
This is CustomCommandLineRunner01
This is CustomApplicationRunner01

Order 注解能够同时对对ApplicationRunner 接口实现类和 CommandLineRunner 接口实现类的执行顺序排序。

如果把4个类的@Order注解都去掉,执行结果:

This is CustomApplicationRunner01
This is CustomApplicationRunner02
This is CustomCommandLineRunner01
This is CustomCommandLineRunner02

( 本文完 )