企业级spring-boot案例-Spring Boot 启动时的运行方法

Posted ThinkWon

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了企业级spring-boot案例-Spring Boot 启动时的运行方法相关的知识,希望对你有一定的参考价值。

文章目录

企业级spring-boot案例系列文章上线了,涵盖了大部分企业级的spring-boot使用场景,会不定期进行更新,企业级spring-boot案例源码地址:https://gitee.com/JourWon/spring-boot-example,欢迎各位大佬一起学习和指正

企业级spring-boot案例
|----Spring Boot整合actuator,实现服务监控管理 spring-boot-actuator
|----Spring Boot集成事件发布及监听 spring-boot-applicationevent
|----Spring Boot整合异步线程池 spring-boot-async
|----Spring Boot整合自定义banner spring-boot-banner
|----Spring Boot整合本地缓存caffeine spring-boot-cache-caffeine
|----Spring Boot整合验证码captcha spring-boot-captcha
|----Spring Boot整合cors跨域资源共享 spring-boot-cors
|----Spring Boot整合jpa实现增删改查 spring-boot-data-jpa
|----Spring Boot整合PostgreSQL spring-boot-data-postgresql
|----Spring Boot整合Redis spring-boot-data-redis
|----Spring Boot整合Docker spring-boot-docker
|----Spring Boot设计模式之工厂模式 spring-boot-dp-factory
|----Spring Boot整合Druid数据库连接池 spring-boot-druid
|----Spring Boot整合dubbo spring-boot-dubbo
|----Spring Boot整合EasyExcel,实现Excel导入导出 spring-boot-easyexcel
|----Spring Boot整合邮件发送 spring-boot-email
|----Spring Boot整合全局异常处理,接口统一响应对象 spring-boot-exception-handler
|----Spring Boot整合flyway,数据库版本控制 spring-boot-flyway
|----Spring Boot整合git插件,将项目打包为tar.gz,并带上git版本号,然后通过sh脚本快速部署 spring-boot-git-commit-id-plugin
|----spring-boot快速入门-HelloWorld spring-boot-helloworld
|----Spring Boot整合HikariCP数据库连接池 spring-boot-hikaricp
|----Spring Boot整合https spring-boot-https
|----Spring Boot整合Jackson,实现数据脱敏 spring-boot-json-desensitization
|----Spring Boot整合Kafka spring-boot-kafka
|----Spring Boot整合Knife4j-API接口文档 spring-boot-knife4j
|----Spring Boot整合log4j2日志 spring-boot-log4j2
|----Spring Boot整合logback日志 spring-boot-logback
|----Spring Boot整合MyBatis spring-boot-mybatis
|----Spring Boot整合MyBatis,使用注解方式 spring-boot-mybatis-annotation
|----Spring Boot整合MyBatis逆向工程 spring-boot-mybatis-generator
|----Spring Boot整合通用mapper spring-boot-mybatis-mapper
|----Spring Boot整合MyBatis多数据源 spring-boot-mybatis-multi-datasource
|----Spring Boot整合MyBatis,使用pagehelper进行分页 spring-boot-mybatis-pagehelper
|----Spring Boot整合MyBatis脱敏插件,实现手机号等信息脱敏 spring-boot-mybatis-plugin-sensitive
|----Spring Boot整合Mybatis-Plus spring-boot-mybatis-plus
|----Spring Boot整合MyBatis Plus代码生成器 spring-boot-mybatis-plus-generator
|----Spring Boot整合MyBatis Plus多数据源 spring-boot-mybatis-plus-multi-datasource
|----Spring Boot整合pf4j,进行插件式编程拓展 spring-boot-pf4j
|----Spring Boot加载配置文件 spring-boot-properties
|----Spring Boot整合接口限流-guava单体方式 spring-boot-ratelimit-guava
|----Spring Boot整合接口限流-redis集群方式 spring-boot-ratelimit-redis
|----Spring Boot整合RestTemplate,实现服务间调用 spring-boot-resttemplate
|----Spring Boot整合retrofit,支持通过接口的方式发起HTTP请求 spring-boot-retrofit
|----Spring Boot启动时的运行方法 spring-boot-runner
|----Spring Boot整合定时任务scheduler spring-boot-scheduler
|----Spring Boot整合Screw,一键生成数据库文档 spring-boot-screw
|----Spring Boot整合Shiro spring-boot-shiro
|----Spring Boot整合Swagger3-API接口文档 spring-boot-swagger3
|----Spring Boot整合模板引擎Thymeleaf spring-boot-thymeleaf
|----Spring Boot整合undertow spring-boot-undertow
|----Spring Boot项目打包成war包 spring-boot-war
|----Spring Boot整合zip,压缩和解压文件 spring-boot-zip

在开发spring boot应用的时候,有时候我们需要在应用启动的时候运行一个方法或者一段代码。这段代码可以是任何范围,从记录某些信息到设置数据库、cron 作业等。我们不能只将此代码放在构造函数中,因为所需的变量或服务可能尚未初始化。这可能会导致空指针或其他一些异常。

为什么我们需要在 spring boot 启动时运行代码?

出于多种原因,我们需要在应用程序启动时运行方法,例如:

  • 记录重要的事情或消息说应用程序已启动
  • 处理数据库或文件、索引、创建缓存等。
  • 启动后台进程,如发送通知、从某个队列中获取数据等。

spring boot中启动后不同的运行方式

每种方式都有其自身的好处。让我们详细看看来决定我们应该使用哪个,

  1. 使用 CommandLineRunner 接口
  2. 使用 ApplicationRunner 接口
  3. Spring 启动应用程序事件
  4. 方法上的@Postconstruct 注解
  5. InitializingBean 接口
  6. @bean注解的初始化属性

1.使用CommandLineRunner接口

org.springframework.boot.CommandLineRunnerSpring Boot提供的一个接口,当你实现该接口并将之注入Spring IoC容器后,Spring Boot应用启动时就会执行其run方法。

实现 CommandLineRunner 接口的示例

@Slf4j
@Component
public class MyCommandLineRunner implements CommandLineRunner 

    @Override
    public void run(String... args) throws Exception 
        log.info("启动预加载数据(MyCommandLineRunner)...", Arrays.toString(args));
    


创建 CommandLineRunner 接口的 bean 示例

@Slf4j
@SpringBootApplication
public class SpringBootRunnerApplication 

    public static void main(String[] args) 
        SpringApplication.run(SpringBootRunnerApplication.class, args);
        log.info("应用启动...");
    

    @Bean
    public CommandLineRunner CommandLineRunnerBean() 
        return (args) -> 
            log.info("启动预加载数据(In CommandLineRunnerImpl)...", Arrays.toString(args));
        ;
    


我们可以使用命令行或 IDE 运行应用程序。让我们举一个例子,当我们使用“–status=running”作为参数运行应用程序时

mvn spring-boot:run -Dspring-boot.run.arguments="--spring.profile.active=test, --foo=bar, --foo=baz, --debug, runner"

或者

mvn package 
java -jar target/<FILENAME.JAR HERE> --spring.profile.active=test, --foo=bar, --foo=baz, --debug, runner

或者

java -jar spring-boot-runner-1.0.0.jar --spring.profile.active=test, --foo=bar, --foo=baz, --debug, runner

或者在 IDE 运行应用程序指定参数

这将产生以下日志输出:

2021-12-04 10:24:04.525  INFO 6964 --- [           main] c.j.s.boot.runner.MyCommandLineRunner    : 启动预加载数据(MyCommandLineRunner)...[--spring.profile.active=test, --foo=bar, --foo=baz, --debug, runner]
2021-12-04 10:24:04.526  INFO 6964 --- [           main] c.j.s.boot.SpringBootRunnerApplication   : 启动预加载数据(In CommandLineRunnerImpl)...[--spring.profile.active=test, --foo=bar, --foo=baz, --debug, runner]
2021-12-04 10:24:04.527  INFO 6964 --- [           main] c.j.s.boot.SpringBootRunnerApplication   : 应用启动...

正如我们所看到的,参数没有被解析,而是被解释为单个值"–spring.profile.active=test, --foo=bar, --foo=baz, --debug, runner"。

要以解析格式访问命令行参数,我们需要使用 ApplicationRunner 接口。

Spring Boot 在启动过程中添加了 CommandLineRunner 接口。因此在 commandlinerRunner 中抛出异常将强制 Spring boot 中止启动。

一个Spring Boot可以存在多个CommandLineRunner的实现,当存在多个时,你可以实现Ordered接口控制这些实现的执行顺序(Order 数值越小优先级越高)。

@Component
@Order(1)
public class CommandLineRunnerImpl implements CommandLineRunner 
    ........

2.使用ApplicationRunner接口

要访问解析的参数,我们需要使用 ApplicationRunner 接口,在Spring Boot 1.3.0引入了一个和CommandLineRunner功能一样的接口。CommandLineRunner接收可变参数String... args,而ApplicationRunner 接收一个封装好的对象参数ApplicationArguments。除此之外它们功能完全一样,甚至连方法名都一样。

它提供了访问参数的不同方法,如下所示

方法说明
String[] GetSourceArgs()被传递给应用程序的原始参数,返回这些参数的字符串数组
Set getOptionNames()获取选项名称的Set字符串集合,选项参数的前面都有"–"。如 --spring.profiles.active=dev --debug 将返回["spring.profiles.active","debug"]
List getNonOptionArgs()用来获取所有的无选项参数,不带"–"的参数
boolean containsOption(String name)用来判断是否包含某个选项的名称
List getOptionValues(String name)通过名称来获取该名称对应的选项值,我们可以在命令行中多次使用相同的键。如--foo=bar --foo=baz 将返回["bar","baz"]

实现 ApplicationRunner 接口的示例

让我们使用"–spring.profile.active=test, --foo=bar, --foo=baz, --debug, runner"参数运行下面的程序

@Slf4j
@Order(1)
@Component
public class MyApplicationRunner implements ApplicationRunner 

    @Override
    public void run(ApplicationArguments args) throws Exception 
        log.info("MyApplicationRunner run方法");
        boolean b1 = args.containsOption("spring.profile.active");
        boolean b2 = args.containsOption("abc");
        log.info("containsOption[spring.profile.active]:,containsOption[abc]:", b1, b2);

        log.info("SourceArgs:", Arrays.toString(args.getSourceArgs()));
        log.info("NonOptionArgs:", args.getNonOptionArgs());
        log.info("OptionNames:", args.getOptionNames());

        log.info("Printing key and value in loop:");
        for (String key : args.getOptionNames()) 
            log.info("key:", key);
            log.info("val:", args.getOptionValues(key));
        
    


输出:

2021-12-04 11:02:53.584  INFO 15260 --- [           main] c.j.s.boot.runner.MyApplicationRunner    : MyApplicationRunner run方法
2021-12-04 11:02:53.584  INFO 15260 --- [           main] c.j.s.boot.runner.MyApplicationRunner    : containsOption[spring.profile.active]:true,containsOption[abc]:false
2021-12-04 11:02:53.586  INFO 15260 --- [           main] c.j.s.boot.runner.MyApplicationRunner    : SourceArgs:[--spring.profile.active=test, --foo=bar, --foo=baz, --debug, runner]
2021-12-04 11:02:53.586  INFO 15260 --- [           main] c.j.s.boot.runner.MyApplicationRunner    : NonOptionArgs:[runner]
2021-12-04 11:02:53.586  INFO 15260 --- [           main] c.j.s.boot.runner.MyApplicationRunner    : OptionNames:[spring.profile.active, debug, foo]
2021-12-04 11:02:53.586  INFO 15260 --- [           main] c.j.s.boot.runner.MyApplicationRunner    : Printing key and value in loop:
2021-12-04 11:02:53.586  INFO 15260 --- [           main] c.j.s.boot.runner.MyApplicationRunner    : key:spring.profile.active
2021-12-04 11:02:53.587  INFO 15260 --- [           main] c.j.s.boot.runner.MyApplicationRunner    : val:[test]
2021-12-04 11:02:53.587  INFO 15260 --- [           main] c.j.s.boot.runner.MyApplicationRunner    : key:debug
2021-12-04 11:02:53.587  INFO 15260 --- [           main] c.j.s.boot.runner.MyApplicationRunner    : val:[]
2021-12-04 11:02:53.587  INFO 15260 --- [           main] c.j.s.boot.runner.MyApplicationRunner    : key:foo
2021-12-04 11:02:53.587  INFO 15260 --- [           main] c.j.s.boot.runner.MyApplicationRunner    : val:[bar, baz]
2021-12-04 11:02:53.587  INFO 15260 --- [           main] c.j.s.boot.runner.MyCommandLineRunner    : MyCommandLineRunner run方法...[--spring.profile.active=test, --foo=bar, --foo=baz, --debug, runner]
2021-12-04 11:02:53.587  INFO 15260 --- [           main] c.j.s.boot.SpringBootRunnerApplication   : bean注解创建CommandLineRunner,参数[--spring.profile.active=test, --foo=bar, --foo=baz, --debug, runner]
2021-12-04 11:02:53.588  INFO 15260 --- [           main] c.j.s.boot.SpringBootRunnerApplication   : 应用启动...

CommandLineRunner 和 ApplicationRunner 具有类似的功能,例如

  • run() 方法中的异常将中止应用程序启动
  • 可以使用 Ordered 接口或 @Order 注解对多个 ApplicationRunner 进行排序

需要注意的最重要的一点是,CommandLineRunners 和 ApplicationRunners 之间共享 Order。这意味着 commandlinerRunner 和 applicationRunner 之间的执行顺序可能会混合。

CommandLineRunnerApplicationRunner 区别

  • 参数不一样。两个接口的实现方法一样,参数不一样,设置命令行参数:–spring.profile.active=test,ApplicatonRunner接口的方法参数ApplicationArguments(是个对象)比CommandLineRunner接口的方法参数(是个可以接收多个string的参数)功能更强大。ApplicatonRunner接口的方法参数ApplicationArguments既可以获取参数的字符串,也可以直接获取key;CommandLineRunner接口的方法参数只能获取参数的字符串。

  • 方法执行顺序不一样。ApplicationRunner接口的实现方法比CommandLineRunner接口的实现方法前执行(当然也可以设置@Order的值来决定谁先执行)

执行顺序使用源码分析

说到执行顺序,那么再进一步了解一下这两个方法是在什么时候执行的。这两个接口的实现执行的时机在于SpringApplication初始化之后,调用的run

以上是关于企业级spring-boot案例-Spring Boot 启动时的运行方法的主要内容,如果未能解决你的问题,请参考以下文章

企业级spring-boot案例-Spring事件发布与监听

企业级spring-boot案例-Spring事件发布与监听

企业级spring-boot案例-自定义Spring Boot Starter

企业级spring-boot案例-Spring Boot 上传文件(图片)

企业级spring-boot案例-Spring Boot 上传文件(图片)

企业级spring-boot案例-Spring Boot 上传文件(图片)