Spring扩展点

经常需要在容器启动时做一些钩子动作,比如注册消息消费者,监听配置等。

容器刷新完成扩展点

监听容器刷新完成扩展点ApplicationListener<ContextRefreshedEvent>

容器刷新成功意味着所有的Bean已初始化完成,当容器刷新之后Spring将会调用容器内所有实现了ApplicationListener<ContextRefreshedEvent>BeanonApplicationEvent方法,应用程序可以以此达到监听容器初始化完成事件的目的。

1
2
3
4
5
6
7
@Log4j2
public class ApplicationListenerExample implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
log.info("ApplicationListenerExample Startup");
}
}

上面的写法,就会造成onApplicationEvent方法被执行两次。因为在Spring MVC项目中,系统会存在两个容器,一个是root ApplicationContext,一个是作为root ApplicationContext的子容器的WebApplicationContext

1
2
3
4
5
6
7
8
9
@Log4j2
public class ApplicationListenerExample implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext().getParent() == null) {
log.info("ApplicationListenerExample Startup");
}
}
}
自定义事件

可以借助Spring以最小成本实现一个观察者模式,首先定义一个事件,然后注册一个监听器,最后发布事件:

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
public class NotifyEvent extends ApplicationEvent {
public NotifyEvent(Object source) {
super(source);
}
}

@Log4j2
public class NotifyListener implements ApplicationListener<NotifyEvent> {
@Override
public void onApplicationEvent(NotifyEvent event) {
log.info("NotifyListener Startup");
}
}

@RunWith(SpringRunner.class)
@SpringBootTest
public class ListenerTest {
@Autowired
private WebApplicationContext webApplicationContext;

@Test
public void testListener() {
NotifyEvent event = new NotifyEvent("object");
webApplicationContext.publishEvent(event);
}
}

SpringBootCommandLineRunner接口

当容器上下文初始化完成之后,SpringBoot也会调用所有实现了CommandLineRunner接口的run方法。

1
2
3
4
5
6
7
8
@Log4j2
@Component
public class CommandLineStartupRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
log.info("CommandLineStartupRunner Startup");
}
}

多个实现了CommandLineRunnerBean的执行顺序可以根据Bean上的@Order注解调整。其run方法可以接受从控制台输入的参数,跟ApplicationListener<ContextRefreshedEvent>这种扩展相比更加灵活。

1
java -jar CommandLineStartupRunner.jar abc abcd

SpringBootApplicationRunner接口

SpringBootCommandLineRunner接口扩展类似,只不过接受参数是一个ApplicationArguments类,对控制台输入的参数提供了更好的封装,以--开头的被视为带选项的参数,否则是普通的参数。

1
2
3
4
5
6
7
8
@Log4j2
@Component
public class ApplicationStartupRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("ApplicationStartupRunner Startup: {}", args.getOptionNames());
}
}

控制台输入参数示例:

1
java -jar ApplicationStartupRunner.jar abc abcd --autho=mark verbose

Bean初始化完成扩展点

@PostConstruct注解

@PostConstruct注解一般放在Bean的方法上,被@PostConstruct修饰的方法会在Bean初始化后马上调用:

1
2
3
4
5
6
7
8
9
10
11
@Log4j2
@Component
public class PostConstructExample {
@Autowired
private Environment environment;

@PostConstruct
public void init() {
log.info(Arrays.asList(environment.getDefaultProfiles()));
}
}

InitializingBean接口

InitializingBean的用法基本上与@PostConstruct一致,只不过相应的Bean需要实现afterPropertiesSet方法。

1
2
3
4
5
6
7
8
9
10
11
@Log4j2
@Component
public class InitializingBeanExample implements InitializingBean {
@Autowired
private Environment environment;

@Override
public void afterPropertiesSet() throws Exception {
log.info(Arrays.asList(environment.getDefaultProfiles()));
}
}

@Bean注解的初始化方法

通过@Bean注入Bean的时候可以指定初始化方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Log4j2
@Component
public class InitMethodExampleBean {
@Autowired
private Environment environment;

public void init() {
log.info(Arrays.asList(environment.getDefaultProfiles()));
}
}

@Bean(initMethod="init")
public InitMethodExampleBean initMethodExampleBean() {
return new InitMethodExampleBean();
}

通过构造函数注入

Spring也支持通过构造函数注入,我们可以把搞事情的代码写在构造函数中,同样能达到目的

1
2
3
4
5
6
7
8
9
10
11
@Log4j2
@Component
public class ConstructorExampleBean {
private final Environment environment;

@Autowired
public ConstructorExampleBean(Environment environment) {
this.environment = environment;
log.info(Arrays.asList(environment.getDefaultProfiles()));
}
}

Bean初始化完成扩展点执行顺序是:构造函数注入@PostConstruct注解InitializingBean接口@Bean注解的初始化方法

SmartLifecycle

可以定义一个SmartLifecycle来监听ApplicationContext的启动和关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
public class ElevenLifecycle implements SmartLifecycle {

private boolean isRunning = false;

@Override
public void start() {
System.out.println("启动");
isRunning = true;
}

@Override
public void stop() {
// 要触发stop(),要调用context.close(),或者注册关闭钩子(context.registerShutdownHook();)
System.out.println("停止");
isRunning = false;
}

@Override
public boolean isRunning() {
return isRunning;
}
}