面试官:Spring Boot项目怎么在启动后初始化一些数据?

上次百度面试的时候,面试官问我:你知道怎么在Spring Boot应用启动之后加载一些数据吗?

我说:这不很简单吗,不就是用@PostConstruct注解就可以了吗?

她说:你确定吗?你确定使用这个注解就能在应用启动之后加载数据吗?

我说:确定啊!

她说:其实这样是不行的,你回去可以自己在想想。

当时感觉很奇怪,之前学过的初始化注解就这一个啊,难道还有其他方式?

所以我就回家自己上网查了一下,发现还真有其他的方式,现在就来总结一下这几种方式。

一、使用@PostConstruct注解的形式

我们先来看一下官方API里面是怎么讲解@PostConstruct的:

@PostConstruct注解用于需要依赖注入完成以执行任何初始化之后需要执行的方法上。在类投入使用之前必须调用此方法。所有支持依赖注入的类都必须支持该注解。即使该类不要求注入任何资源,也必须调用用@PostConstruct注释的方法。此注解只能注释一种方法。

也就是说用@PostConstruct注解注释的方法会在一个类里面的依赖注入完成之后执行,这符合我们初始化数据的要求。

新建两个类:

1
2
3
4
5
6
7
8
9
10
@Service
public class UserService {
public List<String> getUser(){

List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
return list;
}
}
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
@Service
public class BusinessSercice {
@Autowired
private UserService userService;

private List<String> list = null;

/**
* 构造方法执行之后,调用此方法
*/
@PostConstruct
public void init(){
System.out.println("@PostConstruct方法被调用");
// 实例化类之前缓存获得用户信息
List<String> list = userService.getUser();
this.list = list;
if(list != null && !list.isEmpty()){
for(String user : list){
System.out.println("用户:" + user);
}
}
}

public BusinessSercice(){
System.out.println("构造方法被调用");
}

public List<String> getList() {
return list;
}

public void setList(List<String> list) {
this.list = list;
}
}

二、新建一个类实现CommandLineRunner的run方法

在Spring Boot中可以用一个类来专门做初始化的工作,只要这个类实现CommandLineRunner的run方法就可以了。只不过这种方式是在应用启动之后才会执行run方法,不同于上面的使用@PostConstruct注解的方式,因为使用@PostConstruct注解会在类注入到Spring容器的阶段执行,而run方法是在所有类都注入完毕且应用成功启动之后才会执行的。

1
2
3
4
5
6
7
@Component
public class Init1 implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner的run方法执行");
}
}

三、新建一个类实现ApplicationRunner的run方法

再介绍一个和上面类似的方法,就是实现ApplicationRunner的run方法。

1
2
3
4
5
6
7
@Component
public class Init2 implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner的run方法执行");
}
}

这种方式和上面的区别就是输入参数的不同:ApplicationRunner中run方法的参数为ApplicationArguments,而CommandLineRunner接口中run方法的参数为String数组。想要更详细地获取命令行参数,那就使用ApplicationRunner接口。下图为ApplicationArguments类型的参数提供的接口:

ApplicationArguments类型的参数提供的接口

还有一个区别就是如果你在项目中同时实现上述两个类的run方法,默认会先执行ApplicationRunner的run再执行CommandLineRunner的run方法。

那如果我们想让CommandLineRunner的run方法先执行怎么办呢?Spring Boot也给我们提供了一种注解,见名知意,它就是@Order。只要在类上加入这个注解,括号里面填入表示优先级的数字就可以了,其中1表示优先级最高的,其他以此类推。

1
2
3
4
5
6
7
8
@Component
@Order(1)
public class Init implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner的run方法执行");
}
}
1
2
3
4
5
6
7
8
@Component
@Order(2)
public class Init2 implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner的run方法执行");
}
}

启动应用之后两个run方法的执行顺序就是这样的:

好了,现在回过头来看看那个百度的面试官为什么说@PostConstruct注解不能在应用启动之后初始化数据你应该懂了吧,因为它是在类注入到Spring容器之后进行的。


面试官:Spring Boot项目怎么在启动后初始化一些数据?
https://www.chuckfang.com/2019/09/29/How-does-springboot-load-data-immediately-after-the-project-starts/
作者
方程
发布于
2019年9月29日
许可协议