【spring boot应用在项目启动后执行代码】

背景

有时候我们需要在项目启动阶段执行一些代码,比如为了记录log、启动时检查、启动完成后调用一段业务方法等。诸如上述业务要求我们可能会经常碰到。
Spring Boot 提供了至少 5 种方式用于在应用启动阶段执行代码。如下:

CommandLineRunner

CommandLineRunner 是一个接口,通过实现它,我们可以在 Spring 应用成功启动之后 执行一些代码片段。

@Component
@Order(1)
public class RunnerTest implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("spring boot应用启动完成,执行以下代码:");
        //模拟阻塞30s
        Thread.sleep(30000);
        System.out.println("执行完成");
        for (int i = 0; i < args.length; i++) {
            System.out.println("命令行参数:" + args[i]);
        }
    }
}

结果如下:
在这里插入图片描述当 Spring Boot 在应用上下文中找到 CommandLineRunner bean,它将会在应用成功启动之后调用 run() 方法,并传递用于启动应用程序的命令行参数。
到这里我们可以看出几个问题:

1、命令行传入的参数并没有被解析,而只是显示出我们传入的字符串内容,–test=cjy
2、在重写的 run() 方法上有 throws Exception 标记,Spring Boot 会将 CommandLineRunner 作为应用启动的一部分,如果运行 run() 方法时抛出 Exception,应用将会终止启动
3、我们在类上添加了 @Order(1) 注解,当有多个 CommandLineRunner 时,将会按照 @Order 注解中的数字从小到大排序 (数字当然也可以用复数)
4、不要使用 @Order 太多,如果这么写,说明多个代码片段是有相互依赖关系的,为了让我们的代码更好维护,我们应该减少这种依赖使用。

最后,如果我们只是想简单的获取以空格分隔的命令行参数,那 MyCommandLineRunner 就足够使用了。

ApplicationRunner

ApplicationRunner同上面的CommandLineRunner一样,也是在启动后执行run,但是如果要解析参数,那我们就要用到 ApplicationRunner 了。代码如下:

@Component
@Order(1)
public class RunnerTest implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("spring boot应用启动完成,执行以下代码:");
        //模拟阻塞30s
        Thread.sleep(30000);
        System.out.println("执行完成");
        List<String> test = args.getOptionValues("test");
        test.stream().forEach(System.out::println);
    }
}

结果如下:
在这里插入图片描述

可以看出:

1、同 MyCommandLineRunner 相似,但 ApplicationRunner 可以通过 run 方法的 ApplicationArguments 对象解析出命令行参数,并且每个参数可以有多个值在里面,因为 getOptionValues 方法返回 List数组
2、在重写的 run() 方法上有 throws Exception 标记,Spring Boot 会将 CommandLineRunner 作为应用启动的一部分,如果运行 run() 方法时抛出 Exception,应用将会终止启动
3、ApplicationRunner 也可以使用 @Order 注解进行排序,从启动结果来看,它与 CommandLineRunner 共享 order 的顺序,稍后我们通过源码来验证这个结论

也就是我们想要获取复杂的命令行参数时,我们可以使用ApplicationRunner。

ApplicationListener

如果我们不需要获取命令行参数时,我们可以将启动逻辑绑定到 Spring 的 ApplicationReadyEvent上,代码如下:

@Component
public class RunnerTest implements ApplicationListener<ApplicationReadyEvent>{
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        //可以拿到ApplicationContext
        ConfigurableApplicationContext applicationContext = event.getApplicationContext();
        System.out.println("执行完成");
    }
}

结果如下:
在这里插入图片描述

1、使用ApplicationListener去监听ApplicationReadyEvent 事件,此事件只有在应用程序就绪之后才触发。也可以是在本文说的所有解决方案都执行了之后才会被触发。
2、也可以使用@order注解,但是,这个顺序仅用于同类型的 ApplicationListener 之间的排序,与前面提到的 ApplicationRunners 和 CommandLineRunners 的排序并不共享。

如果我们不需要获取命令行参数,我们可以通过 ApplicationListener 创建一些全局的启动逻辑,我们还可以通过它获取 Spring Boot 支持的 configuration properties 环境变量参数

@PostConstruct

上面说的三种都是在启动完成后。如果是需要启动过程中执行:
创建启动逻辑的另一种简单解决方案是提供一种在 bean 创建期间由 Spring 调用的初始化方法。我们要做的就只是将@PostConstruct注解添加到方法中:

@Component
public class RunnerTest{
    @PostConstruct
    public void testPostConstruct() throws InterruptedException {
        System.out.println("开始执行");
        Thread.sleep(2000);
        System.out.println("执行完成");
    }
}

结果如下:
在这里插入图片描述
在这里插入图片描述

1、可以看到Spring 创建完 bean之后 (在启动之前),便会立即调用 @PostConstruct 注解标记的方法。
2、但是如果方法中的业务耗时,(代码中使用了阻塞模拟),方法的执行是会影响spring应用启动的。可见图中的启动时间明显增加。

因此,@PostConstruct 方法固有地绑定到现有的 Spring bean,因此应仅将其用于此单个 bean 的初始化逻辑;

InitializingBean

与 @PostConstruct 解决方案非常相似,我们可以实现 InitializingBean 接口,并让 Spring 调用某个初始化方法:

@Component
public class RunnerTest implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("开始执行");
        Thread.sleep(20000);
        System.out.println("执行完成");
    }
}

结果如下:
在这里插入图片描述
在这里插入图片描述
从上面的运行结果中,我们得到了和 @PostConstruct 一样的效果。但二者还是有差别的:

1、afterPropertiesSet,顾名思义「在属性设置之后」,调用该方法时,该 bean 的所有属性已经被 Spring 填充。如果我们在某些属性上使用 @Autowired(常规操作应该使用构造函数注入),那么 Spring 将在调用afterPropertiesSet 之前将 bean 注入这些属性。但 @PostConstruct 并没有这些属性填充限制
2、所以 InitializingBean.afterPropertiesSet 解决方案比使用 @PostConstruct 更安全,因为如果我们依赖尚未自动注入的 @Autowired 字段,则 @PostConstruct 方法可能会遇到 NullPointerExceptions

ApplicationListener(2)

当然,ApplicationListener也是可以监听Spring 创建完 bean之后 (在启动之前)的,只需要监听ApplicationStartingEvent即可,这个同上诉ApplicationListener。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值