jvm 的shutdownHook 可以实现对于jvm 退出的一些处理,比如资源清理,异常事件通知,spring 自定义事件(或者使用内部的)可以实现
bean 的一些事件驱动处理,两个结合起来可以方便我们进行一些业务处理
一些业务场景
- 资源清理
- 服务停止业务状态一致性补偿
- 服务注册场景中的取消注册
- 服务停止业务处理
简单使用
- 项目结构
├── HELP.md
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── dalong
│ │ └── shutdowndemo
│ │ ├── Api.java
│ │ ├── BizA.java
│ │ ├── BizB.java
│ │ ├── BizC.java
│ │ ├── EventHook.java
│ │ ├── MyEvent.java
│ │ └── ShutdowndemoApplication.java
│ └── resources
│ ├── application.properties
│ ├── static
│ └── templates
- 代码简单说明
ShutdowndemoApplication为spring boot 的入口,Api业务接口(rest api 入口),BizA,BizB,BizC 是业务服务,
EventHook 一个spring 系统的事件处理,MyEvent 自定义事件 - ShutdowndemoApplication.java
@SpringBootApplication
public class ShutdowndemoApplication {
private static ApplicationContext applicationContext;
public static void main(String[] args) {
applicationContext= SpringApplication.run(ShutdowndemoApplication.class, args);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Shutting down");
MyEvent myEvent = new MyEvent("1.0.0");
myEvent.setName("dalong");
myEvent.setVersion("1.0.0");
applicationContext.publishEvent(myEvent);
} ));
}
}
Api.java 主要是调用业务服务,同时实现了spring 系统事件,可以进行一些处理
@RestController
public class Api implements ApplicationListener<ContextClosedEvent> {
@Autowired
private BizA bizA;
@Autowired
private BizB bizB;
@Autowired
private BizC bizC;
@GetMapping(value = {"/demo"})
public String demo() {
String a = bizA.acitonA();
String b = bizB.actionB();
String c = bizC.actionC();
return a + b + c;
}
private void doClean() {
try {
System.out.println("Api doClean start");
Thread.sleep(3000);
System.out.println("Api doClean end");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
System.out.println("Api onApplicationEvent Shutting down");
doClean();
}
}
BizA.java (其他的b,c 都比较类似)
@Service
public class BizA implements ApplicationListener<ContextClosedEvent> {
public String doBiz() {
try {
System.out.println("doBizA biz start");
Thread.sleep(6000);
System.out.println("doBizA biz end");
return "doBizA biz end";
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public String acitonA() {
return "actionA";
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
System.out.println("BizA onApplicationEvent Shutting down");
doBiz();
}
}
EventHook.java
@Component
public class EventHook implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
try {
System.out.println("onApplicationEvent Shutting down");
Thread.sleep(1000);
System.out.println("onApplicationEvent biz end");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean supportsAsyncExecution() {
return ApplicationListener.super.supportsAsyncExecution();
}
}
MyEvent.java
public class MyEvent extends ApplicationEvent {
private String name;
private String version;
public MyEvent(Object source) {
super(source);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
运行效果
如果实际我们执行kill <jvm pid>
,效果如下
说明
以上只是一个简单的演示,实际上如果我们想更好的基于jvm shutdownHook + spring 进行一些业务系统的处理,比如业务务感知的业务滚动升级处理,实际上有不少东西要处理,比如服务的prestop 阶段的的处理,同时业务请求流量的处理,业务一致性的保证,但是基于spring 以及jvm 提供的一些
完整测试代码我已经push 到github 了可以参考
能力可以简化我们的实际实现
参考资料
https://docs.oracle.com/javase/8/docs/technotes/guides/lang/hook-design.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationListener.html
https://www.baeldung.com/spring-events
https://www.baeldung.com/spring-boot-enable-disable-endpoints-at-runtime