@PostConstruct

@PostConstruct 是 JSR-250 规范中的注解,用于标记一个方法在依赖注入完成后立即执行。

特点

  1. 在 bean 完全初始化后(依赖注入完成后)执行
  2. 方法可以有任意访问修饰符,但不能有参数

使用案例

java
1
2
3
4
5
6
7
8
@Component
public class MyComponent {

@PostConstruct
public void init() {
System.out.println("@PostConstruct方法执行");
}
}

InitializingBean

InitializingBean 是 Spring 框架提供的接口,包含一个 afterPropertiesSet() 方法。

特点

  1. 在 bean 属性设置完成后执行
  2. 需要实现接口,是 Spring 特有的,与框架耦合

使用案例

java
1
2
3
4
5
6
7
8
@Component
public class MyBean implements InitializingBean {

@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean的afterPropertiesSet()执行");
}
}

CommandLineRunner

CommandLineRunner 是 Spring Boot 提供的接口,用于在应用启动后执行代码。

特点

  1. 在所有 bean 初始化完成后执行
  2. 可以访问原始的应用程序参数(String [] args)
  3. 可以有多个实现,通过@Order 或实现 Ordered 接口指定顺序
  4. 在应用上下文完全准备好之后运行

使用案例

java
1
2
3
4
5
6
7
8
9
@Component
@Order(1)
public class MyCommandLineRunner implements CommandLineRunner {

@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner执行,参数: " + Arrays.toString(args));
}
}

ApplicationRunner

ApplicationRunner 与 CommandLineRunner 类似,但提供了更丰富的 ApplicationArguments 来访问参数。

特点

  1. 功能与 CommandLineRunner 相似
  2. 提供了更结构化的参数访问方式
  3. 同样可以有多个实现并指定顺序
  4. 执行时机与 CommandLineRunner 相同

使用案例

java
1
2
3
4
5
6
7
8
9
10
11
@Component
@Order(2)
public class MyApplicationRunner implements ApplicationRunner {

@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner执行");
System.out.println("源参数: " + Arrays.toString(args.getSourceArgs()));
System.out.println("选项参数: " + args.getOptionNames());
}
}

执行顺序

  1. @PostConstruct
  2. InitializingBean.afterPropertiesSet()
  3. ApplicationRunner/CommandLineRunner(可以通过@Order 控制它们之间的顺序)

注意:@PostConstruct 和 InitializingBean.afterPropertiesSet()会根据类名称进行 ASCII 比较,根据顺序执行

使用案例

java
1
2
3
4
5
6
7
8
@Component
public class MyComponent {

@PostConstruct
public void init() {
System.out.println("@PostConstruct方法执行");
}
}
java
1
2
3
4
5
6
7
8
@Component
public class MyBean implements InitializingBean {

@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean的afterPropertiesSet()执行");
}
}
java
1
2
3
4
5
6
7
8
@Component
public class AComponent {

@PostConstruct
public void init() {
System.out.println("@PostConstruct方法执行");
}
}

使用场景

  1. @PostConstruct:简单的初始化逻辑,不依赖其他 bean 的完全初始化
  2. InitializingBean:Spring 特有的初始化
  3. CommandLineRunner/ApplicationRunner: 需要在应用完全启动后执行的逻辑,特别是需要访问命令行参数时

区别

@PostConstruct 和 afterPropertiesSet

afterPropertiesSet,顾名思义「在属性设置之后」,调用该方法时,该 bean 的所有属性已经被 Spring 填充。如果我们在某些属性上使用 @Autowired(常规操作应该使用构造函数注入),那么 Spring 将在调用 afterPropertiesSet 之前将 bean 注入这些属性。但 @PostConstruct 并没有这些属性填充限制

所以 InitializingBean.afterPropertiesSet 比使用 @PostConstruct 更安全,因为如果我们依赖尚未自动注入的 @Autowired 字段,则 @PostConstruct 方法可能会遇到 NullPointerExceptions

CommandLineRunner 和 ApplicationRunner

  1. CommandLineRunner 和 ApplicationRunner 在容器启动的时候会执行一些内容,比如:读取配置文件、数据库连接
  2. CommandLineRunner 接口的 run()方法接收 String 数组作为参数,即是最原始的参数,没有做任何处理;而 ApplicationRunner 接口的 run()方法接收 ApplicationArguments 对象作为参数,是对原始参数做了进一步的封装
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
@Order(value = 1)
public class JDDRunner implements ApplicationRunner {

@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("这个是测试ApplicationRunner接口");
String strArgs = Arrays.stream(arg0.getSourceArgs()).collect(Collectors.joining("|"));
System.out.println("Application started with arguments:" + strArgs);
}
}

// 启动时指定参数:java -jar xxxx.jar data1 data2 data3
// Application started with arguments: data1|data2|data3
java
1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class TestCommandLineRunner implements CommandLineRunner {

@Override
public void run(String... args) throws Exception {
System.out.println("这个是测试CommandLineRunn接口");
String strArgs = Arrays.stream(args).collect(Collectors.joining("|"));
System.out.println("Application started with arguments:" + strArgs);
}
}

// 启动时指定参数:java -jar xxxx.jar data1 data2 data3
// Application started with arguments: data1|data2|data3

注意:

  1. 可以使用@Order 注解或实现 Ordered 接口按一定顺序执行
  2. 在 ApplicationContext 容器加载完成之后,会调用 SpringApplication 类的 callRunners 方法,该方法中会获取所有实现了 ApplicationRunner 和 CommandLineRunner 的接口 bean,然后依次执行对应的 run 方法,并且是在同一个线程中执行。如果有某个实现了 ApplicationRunner 或 CommandLineRunner 的接口的 bean 的 run 方法一直循环不返回的话,后续的代码将不会被执行
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
//从上下文获取 ApplicationRunner 类型的 bean
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());

//从上下文获取 CommandLineRunner 类型的 bean
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());

//对二者进行排序,这也就是为什么二者的 order 是可以共享的了
AnnotationAwareOrderComparator.sort(runners);

//遍历对其进行调用
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}