Spring Boot启动后执行特定操作,然后自动停止

前言

我们原来使用Spring Boot一般都是在web工程中,执行顺序是启动内嵌tomcat容器→所有服务待命→等待请求的过来→处理请求,如此循环,当需要停止的话要在外部执行命令停止。

问题

但是问题来了,现在有一个特殊的需求,只需要Spring Boot执行一次,顺序是启动Spring Boot→执行service的一些方法→停止容器。整个过程不需要人工干预,Spring Boot启动后自动执行,执行后自动停止,不像原来那样挂起等待。

分析

首先分析一下需求,可以发现主要有两个特殊的点:
1、启动Spring Boot后执行特定的操作;
2、执行操作后自动停止;

解决

对于第一个问题,在网上很容易找到答案。只需要添加一个Listeners即可:

1
2
3
4
5
6
public class ApplicationStartup implements ApplicationListener<ContextRefreshedEvent> {

public void onApplicationEvent(ContextRefreshedEvent event) {
// 想要执行的操作
}
}

添加到SpringApplication中:

1
2
3
4
5
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(Application.class);
springApplication.addListeners(new ApplicationStartup());
springApplication.run(args);
}

重点是第二个问题:自动停止Spring Boot,找遍了搜索引擎都没有找到方法,应该是这个需求比较奇葩,一般人不会这么用。可是遇到了问题还是要解决,于是我就想,什么情况下Spring Boot会停止呢?出现异常!是的,启动Spring Boot的过程中,只要出现异常,容器就会停止,这是开发过程中经常出现的问题。那么在发现异常的时候,肯定有某些机制,准确的说是某些代码使得容器停止。找到了线索,方法自然就有了,我在Spring Boot执行完操作后,就是在ApplicationStartup的最后手动的抛出了一个异常,然后开启debug模式,追踪进去:
发现在EmbeddedWebApplicationContext类中有一个非常明显的停止操作:

1
2
3
4
5
6
7
8
9
public final void refresh() throws BeansException, IllegalStateException {
try {
super.refresh();
} catch (RuntimeException var2) {
// 就是这句!
this.stopAndReleaseEmbeddedServletContainer();
throw var2;
}
}

进到方法,发现了位于EmbeddedWebApplicationContext类中的罪魁祸首:

1
2
3
4
5
6
7
8
9
10
11
12
private void stopAndReleaseEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
if(localContainer != null) {
try {
localContainer.stop();
this.embeddedServletContainer = null;
} catch (Exception var3) {
throw new IllegalStateException(var3);
}
}

}

就是这句localContainer.stop()!至此,我们知道了Spring Boot停止容器的方法,就是调用EmbeddedServletContainerstop方法。所以我们只需要获取到EmbeddedServletContainer就可以了。

而要获取EmbeddedServletContainer,就要首先获取EmbeddedWebApplicationContext。观察EmbeddedWebApplicationContext,发现它叫做Context,那是否实现自ApplicationContext呢?通过引用关系,我发现它确实实现了ApplicationContext,这样就好办了,因为在日常开发中,我们经常要获取ApplicationContext和获取bean,已经封装有一个工具类BeanTool

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
@Component
public class BeanTool extends ApplicationObjectSupport implements ApplicationContextAware {
static ApplicationContext context;
private static ApplicationContext applicationContext = null;

public static void setApplicationContext(WebApplicationContext applicationContext) {
BeanTool.applicationContext = applicationContext;
}

public BeanTool getInstance() {
return new BeanTool();
}

protected void initApplicationContext(ApplicationContext context) {
super.initApplicationContext(context);
if (applicationContext == null) {
applicationContext = context;
}
}

public static ApplicationContext getAppContext() {
return applicationContext;
}

public static Object getBean(String name) {
return getAppContext().getBean(name);
}

public static Object getBean(Class clazz) {
return getAppContext().getBean(clazz);
}
}

这个工具类要注册到Spring Boot的入口类中:

1
2
3
4
5
6
7
/**
* bean工具类,可以在普通类中获取spring创建的bean
*/
@Bean
public BeanTool beanTool() {
return new BeanTool();
}

现在就差最后一步了!在ApplicationStartup的最后,调用BeanTool获取EmbeddedWebApplicationContext,从而获取EmbeddedServletContainer,最后调用其stop方法:

1
2
EmbeddedWebApplicationContext context = (EmbeddedWebApplicationContext)BeanTool.getAppContext();
context.getEmbeddedServletContainer().stop();

执行,可以看到执行完操作后,容器停止了,大功告成!

最后

从这个的经历中,可以看到问题的产生,问题的分析和问题的解决之中的是如何思考的。面对未知的问题,只要采取合适的方法,一步一步试错,一定能找到解决的方法。

“你个人的项目,应该有四分之一会失败,否则就说明你的冒险精神不够。”(Expect and hope that a quarter of your projects fail. If not, you’re not taking enough risks. –Adam Smith)


------本文结束  感谢阅读------