美文网首页
用Spring Boot开发命令行执行程序

用Spring Boot开发命令行执行程序

作者: 洋洋洒洒看东西 | 来源:发表于2020-08-14 14:11 被阅读0次

通常使用Spring Boot,我们都是开发服务程序,是一种启动后就不停止的。如果想要用Spring Boot开发一次性执行的程序,该怎么设计呢?

基于Spring Boot,而不是纯粹的jdk开发一次性执行程序,有什么区别呢?那就是可以利用Springframework的特性都可以被使用。依赖反转、spring expression、日志、测试等都可以快速搭建起来。这不就是Spring Boot的宗旨吗?


1. 基于ApplicationRunner/CommandLineRunner

1.1 创建最基本的Spring Boot项目

首先从Spring initializr 上创建一个最简单的Spring Boot项目,不需要添加任何依赖,就会生成类似下方的项目文件。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo-cli-app</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo-cli-app</name>
    <description>Demo project for Spring Boot CommandLineRunner and ApplicationRunner</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

1.2 ApplicationRunner/CommandLineRunner

我们可以根据需要实现ApplicationRunner或CommandLineRunner接口,那么启动Spring Boot应用时就会执行我们的逻辑,并在全部执行结束后退出。

package com.example.democliapp.runner;

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

/**
 * @author jackeylv
 * @date 2020-08-14 10:13
 */
@Order(value = 21)
@Component
public class AppRunnerOne implements ApplicationRunner {
    private static final Logger logger = LoggerFactory.getLogger(AppRunnerOne.class);
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 命令行参数是  --p1=2 --p2=3 --p3 4 --p1=34

        // getSourceArgs的结果,与CommandLineRunner一样
        // Running with --p1=2|--p2=3|--p3|4|--p1=34
        logger.info("Running with {}", String.join("|", args.getSourceArgs()));
        // p1 = [2, 34]
        logger.info("p1 = {}", args.getOptionValues("p1"));
        // non-exist = null
        logger.info("non-exist = {}", args.getOptionValues("non-exist"));
        // names [p1, p2, p3]
        logger.info("names {}", args.getOptionNames());
        // NonOptionArgs [4]
        logger.info("NonOptionArgs {}", args.getNonOptionArgs());
    }
}

package com.example.democliapp.runner;

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

/**
 * CommandLineRunner会在SpringBootApplication启动以后执行。
 *
 * @author jackeylv
 * @date 2020-08-14 10:05
 */
@Order(value = 1)
@Component
public class CommandOne implements CommandLineRunner {
    private static final Logger logger = LoggerFactory.getLogger(CommandOne.class);

    @Override
    public void run(String... args) throws Exception {
        logger.info("Running with {}", String.join("|", args));
    }
}

1.3 这些Runner 是怎么执行的?

package com.example.democliapp;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author jackeylv
 */
@SpringBootApplication
public class DemoCliAppApplication {
    private static final Logger logger = LoggerFactory.getLogger(DemoCliAppApplication.class);

    public static void main(String[] args) {
        //Step 0. [main thread]这部分代码是最早开始执行的,会早于Spring-boot本身
        String strArgs = String.join("|", args);
        logger.info("starting the application with args: {}", strArgs);

        // Step 1. [main thread]启动SpringApplication
        // Step 2. [main thread] Step 1中启动时扫描发现的CommandLineRunner ApplicationRunner都会被执行。
        SpringApplication.run(DemoCliAppApplication.class, args);


        // Step 3. [main thread]执行到此。
        logger.info("Application is started, any others?");
    }
}

把程序跑起来我们就发现:

  • SpringBoot程序和各种Runner都是在一个线程里面运行,这里都是main thread。
  • 各种Runner都是在Spring Boot启动以后执行。
  • 以上两部分都是通过SpringApplication.run(DemoCliAppApplication.class, args);这一句完成

2. 其他

2.1 Runner的Order

我们可以用@Order(value= ?)来调整不同Runner的执行顺序,可以达到我们的业务诉求。
我们从SpringApplication的run方法进入,可以找到这些Runner是如何被调用执行的。关键就在于
org.springframework.boot.SpringApplication#callRunners 方法,以及该方法内部的org.springframework.core.annotation.AnnotationAwareOrderComparator排序方法。跟踪到这个类的父类org.springframework.core.OrderComparator说明中,可以看到具体的排序规则:

Comparator implementation for Ordered objects, sorting by order value ascending, respectively by priority descending.

  • PriorityOrdered Objects
    PriorityOrdered objects will be sorted with higher priority than plain Ordered objects.
  • Same Order Objects
    Objects that have the same order value will be sorted with arbitrary ordering with respect to other objects with the same order value.
  • Non-ordered Objects
    Any object that does not provide its own order value is implicitly assigned a value of
    Ordered.LOWEST_PRECEDENCE, thus ending up at the end of a sorted collection in arbitrary order with > respect to other objects with the same order value.

覆盖了有Order,没Order的各种情况,包括使用org.springframework.core.PriorityOrdered注解的方式都统一在这个java.util.Comparator中定义和实现。

2.2 Runner的差异

在实际应用中两种Runner有什么差异呢?仅仅差异在入参。可以详见org.springframework.boot.CommandLineRunnerorg.springframework.boot.ApplicationRunner的定义。

CommandLineRunner ApplicationRunner
定义 void run(String... args) throws Exception; void run(ApplicationArguments args) throws Exception;

建议用ApplicationRunner,因为不需要自己做命令参数解析,而且功能也完全覆盖CommandLineRunner

举例子,看下方的输出即可。

// 命令行参数是  --p1=2 --p2=3 --p3 4 --p1=34

// getSourceArgs的结果,与CommandLineRunner一样
// Running with --p1=2|--p2=3|--p3|4|--p1=34
logger.info("Running with {}", String.join("|", args.getSourceArgs()));
// p1 = [2, 34]
logger.info("p1 = {}", args.getOptionValues("p1"));
// non-exist = null
logger.info("non-exist = {}", args.getOptionValues("non-exist"));
// names [p1, p2, p3]
logger.info("names {}", args.getOptionNames());
// NonOptionArgs [4]
logger.info("NonOptionArgs {}", args.getNonOptionArgs());

相关文章

网友评论

      本文标题:用Spring Boot开发命令行执行程序

      本文链接:https://www.haomeiwen.com/subject/hsegdktx.html