如果您认为这种行为有问题,并且您需要对调用进行精细控制System.exit
,那么您唯一能做的就是将 System.exit 功能包装在您自己的逻辑中。如果我们这样做,我们可以执行 finally 块并关闭资源作为退出流程的一部分。
我正在考虑做的是将System.exit
调用和功能包装在我自己的静态方法中。在我的实现中,exit
我将抛出 or 的自定义子类Throwable
,Error
并实现自定义 Uncaught 异常处理程序Thread.setDefaultUncaughtExceptionHandler
来处理该异常。因此我的代码变为:
//in initialization logic:
Thread.setDefaultUncaughtExceptionHandler((thread, exception) -> {
if(exception instanceof SystemExitEvent){
System.exit(((SystemExitEvent)exception).exitCode);
}
})
// in "main flow" or "close button" or whatever
public void mainFlow(){
try {
businessLogic();
Utilities.exit(0);
}
finally {
cleanUpFileSystemOrDatabaseConnectionOrWhatever();
}
}
//...
class Utilities {
// I'm not a fan of documentaiton,
// but this method could use it.
public void exit(int exitCode){
throw new SystemExitEvent(exitCode);
}
}
class SystemExitEvent extends Throwable {
private final int exitCode;
public SystemExitEvent(int exitCode){
super("system is shutting down")
this.exitCode = exitCode;
}
}
这个策略还有一个额外的“好处”是让这个逻辑可测试:为了测试包含我们的“主要流程”的方法是否真的请求系统退出,我们所要做的就是捕获一个 throwable 并断言它是 write 类型。例如,我们的业务逻辑包装器的测试可能如下所示:
//kotlin, a really nice language particularly for testing on the JVM!
@Test fun `when calling business logic should business the business`(){
//setup
val underTest = makeComponentUnderTest(configureToReturnExitCode = 42);
//act
val thrown: SystemExitEvent = try {
underTest.mainFlow();
fail("System Exit event not thrown!")
}
catch(event: SystemExitEvent){
event;
}
//assert
assertThat(thrown.exitCode).isEqualTo(42)
这种策略的主要缺点是它是一种从异常流中获取功能的方法,这通常会产生意想不到的后果。在这种情况下,最明显的是,您编写的任何地方try { ... } catch(Throwable ex){ /*doesnt rethrow*/ }
都必须更新。对于具有自定义执行上下文的库,需要对其进行改造以理解此异常。
总的来说,这对我来说似乎是一个很好的策略。这里还有其他人这么认为吗?