SpringBoot-(三十)实现SpringBoot启动后自动执行指定业务逻辑

本文最后更新于:February 13, 2023 pm

SpringBoot框架中有两个非常重要的策略:开箱即用和约定优于配置。其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。

目录

今天在启动项目时,看见有SQL查询语句自动执行了。然后就 ‘网购’ 了一下,然后进行了一下整理。

启动类

这是最简单,也是最容易想到的办法。就是在启动类中进行添加代码。如下:

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
public class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
System.out.println("启动自动执行...");
}

}

除此之外,SpringBoot给我们提供了两个接口来帮助我们实现这种需求,他们的执行时机为容器启动完成的时候。

  • ApplicationRunner
  • CommandLineRunner

当接口有多个实现类时,提供了@order注解实现自定义执行顺序,也可以实现Ordered接口来自定义顺序。

ApplicationRunner

  • 实现 ApplicationRunner 接口,重写 run() 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.tothefor.mqdemo;

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

@Component //被 spring 容器管理
@Order(1) //如果多个自定义的 ApplicationRunner ,用来标明执行的顺序
public class ApplicationRunnerStartService implements ApplicationRunner {


@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner 自动执行...");
}

}

CommandLineRunner

  • 实现 CommandLineRunner 接口,重写 run() 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.tothefor.mqdemo;

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

@Component //被 spring 容器管理
@Order(10) //如果多个自定义的 CommandLineRunner,用来标明执行的顺序,数字越小,顺序越靠前
public class CommandLineRunnerStartService implements CommandLineRunner{


@Override
public void run(String... args) throws Exception {
//执行自己的业务逻辑
System.out.println("CommandLineRunner 自动执行...");
}

}

比较

当前面两者均存在时如何执行?

  • 同等级下,ApplicationRunner前,CommandLineRunner后。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// ApplicationRunner-1
@Component //被 spring 容器管理
@Order(1) //如果多个自定义的 ApplicationRunner ,用来标明执行的顺序
public class ApplicationRunnerStartService implements ApplicationRunner {


@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner-1 自动执行...");
}

}


// ApplicationRunner-2
@Component //被 spring 容器管理
@Order(2) //如果多个自定义的 ApplicationRunner ,用来标明执行的顺序
public class ApplicationRunnerStartService2 implements ApplicationRunner {


@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner-2 自动执行...");
}

}

// CommandLineRunner-1
@Component //被 spring 容器管理
@Order(1) //如果多个自定义的 CommandLineRunner,用来标明执行的顺序,数字越小,顺序越靠前
public class CommandLineRunnerStartService implements CommandLineRunner{


@Override
public void run(String... args) throws Exception {
//执行自己的业务逻辑
System.out.println("CommandLineRunner-1 自动执行...");
}

}

// CommandLineRunner-2
@Component //被 spring 容器管理
@Order(2) //如果多个自定义的 CommandLineRunner,用来标明执行的顺序,数字越小,顺序越靠前
public class CommandLineRunnerStartService2 implements CommandLineRunner{


@Override
public void run(String... args) throws Exception {
//执行自己的业务逻辑
System.out.println("CommandLineRunner-2 自动执行...");
}

}

输出结果:

1
2
3
4
ApplicationRunner-1 自动执行...
CommandLineRunner-1 自动执行...
ApplicationRunner-2 自动执行...
CommandLineRunner-2 自动执行...

总结:

  • 先按照Order排序,越小越在前。
  • 在Order相同的情况下,ApplicationRunner 在 CommandLineRunner的前面。

InitializingBean

  • 实现 InitializingBean 接口,重写 afterPropertiesSet() 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.tothefor.mqdemo;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;

@Service
public class UserService implements InitializingBean {

@Override
public void afterPropertiesSet() {
System.out.println("InitializingBean 自动执行...");
}
}

三者比较

代码同上。就看输出结果:

1
2
3
4
5
6
InitializingBean 自动执行...
... // 其他日志
ApplicationRunner-1 自动执行...
CommandLineRunner-1 自动执行...
ApplicationRunner-2 自动执行...
CommandLineRunner-2 自动执行...

自定义配置类

首先随便写一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.tothefor.mqdemo;

import lombok.Data;

/**
* @Author DragonOne
* @Date 2023/2/13 23:20
* @墨水记忆 www.tothefor.com
*/
@Data
public class TestPeron {
private String name;
private String age;

// 无参构造器
public TestPeron() {
System.out.println("Person NoConstructor");
}

// 充当init method
public void customInit() {
System.out.println("Person init-method");
}

// 充当destroy method
public void customDestroy() {
System.out.println("Person destroy-method");
}
}

这里很显然只是一个普通的java类,拥有一个无参构造和另外两个方法。
需要注意的是,这里的init和destroy两个方法名实际上是可以随意取的,不叫这个也没有问题,只不过算是一种约定俗称。
另外我们也知道,这个默认构造方法也可以不要的,因为会隐式创建,但是为了更清楚的看到init和destroy什么时候执行,我们就显示的写出来。
创建好了这个类,就可以使用@Bean注解的方式指定两个方法,让他们生效。

然后我们再写一个配置类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.tothefor.mqdemo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfig {

@Bean(destroyMethod = "customDestroy", initMethod = "customInit")
public TestPeron lifeCycleBean(){
TestPeron lifeCycleBean = new TestPeron();
return lifeCycleBean;
}

}
  • initMethod:指定了初始化方法。和对应的类中的方法名称对应。
  • destroyMethod:指定销毁时的方法。和对应的类中的方法名称对应。

然后启动启动类,打印如下:

1
2
Person NoConstructor
Person init-method

这时并没有执行销毁方法,只有关闭时才会执行。关闭后打印:

1
Person destroy-method

其他

  • Spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件中通过init-method指定,两种方式可以同时使用。

  • 实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率要高一点,但是init-method方式消除了对spring的依赖。

  • 如果调用afterPropertiesSet方法时出错,则不调用init-method指定的方法。

综合

将上面的5种方式一起使用,打印如下:

1
2
3
4
5
6
7
InitializingBean 自动执行...
Person NoConstructor
Person init-method
ApplicationRunner-1 自动执行...
CommandLineRunner-1 自动执行...
启动自动执行...
Person destroy-method

所以,以上可以看出执行顺序为:

  • InitializingBean -> 自定义配置类 -> ApplicationRunner -> CommandLineRunner -> 启动类