22

如何在Spring Boot应用启动之后立刻执行一段逻辑

 4 years ago
source link: http://www.cnblogs.com/felordcn/p/13029558.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

jimEfa7.png!web

1. 前言

不知道你有没有接到这种需求,项目启动后立马执行一些逻辑。比如简单的缓存预热,或者上线后的广播之类等等。如果你使用 Spring Boot 框架的话就可以借助其提供的接口 CommandLineRunnerApplicationRunner 来实现。

2. CommandLineRunner

org.springframework.boot.CommandLineRunnerSpring Boot 提供的一个接口,当你实现该接口并将之注入 Spring IoC 容器后, Spring Boot 应用启动后就会执行其 run 方法。一个 Spring Boot 可以存在多个 CommandLineRunner 的实现,当存在多个时,你可以实现 Ordered 接口控制这些实现的执行顺序( Order 数值越大优先级越低 )。接下来我们来声明两个实现并指定顺序:

优先执行:

package cn.felord;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

/**
 * 优先级最高
 * 该类期望在springboot 启动后第一顺位执行
 * @author felord.cn
 * @since 12:57
 **/
@Slf4j
@Component
public class HighOrderCommandLineRunner implements CommandLineRunner, Ordered {
    @Override
    public void run(String... args) throws Exception {
        for (String arg : args) {
            log.info("arg = " + arg);
        }
        log.info("i am highOrderRunner");
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE+1;
    }
}

第二顺序执行:

package cn.felord;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

/**
 * 优先级低于{@code HighOrderCommandLineRunner}
 * @author felord.cn
 * @since 12:59
 **/
@Slf4j
@Component
public class LowOrderCommandLineRunner implements CommandLineRunner, Ordered {

    @Override
    public void run(String... args) throws Exception {
        log.info("i am lowOrderRunner");
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE+1;
    }
}

然后启动 Spring Boot 应用后,控制台按照预定的顺序打印出了结果:

2020-05-30 23:11:03.685  INFO 11976 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-05-30 23:11:03.701  INFO 11976 --- [           main] c.f.Application  : Started SpringBootApplication in 4.272 seconds (JVM running for 6.316)
2020-05-30 23:11:03.706  INFO 11976 --- [           main] c.f.HighOrderCommandLineRunner   : i am highOrderRunner
2020-05-30 23:11:03.706  INFO 11976 --- [           main] c.f.LowOrderCommandLineRunner   : i am lowOrderRunner

3. ApplicationRunner

Spring Boot 1.3.0 又引入了一个和 CommandLineRunner 功能一样的接口 ApplicationRunnerCommandLineRunner 接收可变参数 String... args ,而 ApplicationRunner 接收一个封装好的对象参数 ApplicationArguments 。除此之外它们功能完全一样,甚至连方法名都一样。 声明一个 ApplicationRunner 并让它优先级最低:

package cn.felord;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
 * 优先级最低
 * @author felord.cn
 * @since 13:00
 **/
@Slf4j
@Component
public class DefaultApplicationRunner implements ApplicationRunner, Ordered {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("i am applicationRunner");
        Set<String> optionNames = args.getOptionNames();
        log.info("optionNames = " + optionNames);
        String[] sourceArgs = args.getSourceArgs();
        log.info("sourceArgs = " + Arrays.toString(sourceArgs));
        List<String> nonOptionArgs = args.getNonOptionArgs();
        log.info("nonOptionArgs = " + nonOptionArgs);
        List<String> optionValues = args.getOptionValues("foo");
        log.info("optionValues = " + optionValues);
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE+2;
    }
}

按照顺序打印了三个类的执行结果:

2020-06-01 13:02:39.420  INFO 19032 --- [           main] c.f.MybatisResultmapApplication  : Started MybatisResultmapApplication in 1.801 seconds (JVM running for 2.266)
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.HighOrderCommandLineRunner   : i am highOrderRunner
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.LowOrderCommandLineRunner    : i am lowOrderRunner
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : i am applicationRunner
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : optionNames = []
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : sourceArgs = []
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : nonOptionArgs = []
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : optionValues = null

Ordered 接口并不能被 @Order 注解所代替。

4. 传递参数

相信很多同学看到这里都开始对这两个 run 方法的入参感兴趣了。 Spring Boot 应用启动时是可以接受参数的,换句话说也就是 Spring Bootmain 方法是可以接受参数的。这些参数通过命令行 java -jar yourapp.jar 来传递。 CommandLineRunner 会原封不动照单全收这些接口,这些参数也可以封装到 ApplicationArguments 对象中供 ApplicationRunner 调用。 我们来认识一下 ApplicationArguments 的相关方法:

  • getSourceArgs()被传递给应用程序的原始参数,返回这些参数的字符串数组。

  • getOptionNames()获取选项名称的 Set 字符串集合。如 --spring.profiles.active=dev --debug 将返回 ["spring.profiles.active","debug"]

  • getOptionValues(String name)通过名称来获取该名称对应的选项值。如 --foo=bar --foo=baz 将返回 ["bar","baz"]

  • containsOption(String name)用来判断是否包含某个选项的名称。

  • getNonOptionArgs()用来获取所有的无选项参数。

    接下来我们试验一波,你可以通过下面的命令运行一个 Spring Boot 应用 Jar

java -jar yourapp.jar --foo=bar --foo=baz --dev.name=码农小胖哥 java felordcn

或者在 IDEA 开发工具中打开 Spring Boot 应用 main 方法的配置项,进行如下配置,其他 IDE 工具同理。

Mzmyumi.png!web

运行 Spring Boot 应用,将会打印出:

2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = --foo=bar
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = --foo=baz
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = --dev.name=码农小胖哥
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = java
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = felordcn
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : i am highOrderRunner
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.LowOrderCommandLineRunner    : i am lowOrderRunner
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : i am applicationRunner
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : optionNames = [dev.name, foo]
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : sourceArgs = [--foo=bar, --foo=baz, --dev.name=码农小胖哥, java, felordcn]
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : nonOptionArgs = [java, felordcn]
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : optionValues = [bar, baz]

然后你就可以根据实际需要动态地执行一些逻辑。

5. 总结

今天我们对 CommandLineRunnerApplicationRunner 进行了讲解,从用法到顺序执行,又对 Spring Boot 传递参数进行了介绍和演示,希望对你有所帮助。多多关注: 码农小胖哥 ,更多编程干货分享给你。

关注公众号:Felordcn 获取更多资讯

个人博客:https://felord.cn


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK