我维护一个 JDBC 驱动程序,该驱动程序还具有通过 native 库(通过 JNA 访问)提供的嵌入式数据库服务器模式。由于其依赖项的卸载顺序,作为 native 库本身卸载的一部分完成的关闭在 Windows 上会遇到问题。为避免访问冲突或其他问题,我需要在卸载此库之前明确关闭嵌入式引擎。
鉴于其使用的性质,很难确定调用关闭的合适时机,我现在看到的普通 Java 应用程序的唯一正确方法是使用 Runtime.getRuntime( ).addShutdownHook
与实现关闭逻辑的 Thread
的子类。
这对于普通的 Java 应用程序来说工作正常,但对于将我的库作为应用程序的一部分(在 WAR 的 WEB-INF/lib
中)的 Web 应用程序,这将导致内存在取消部署时泄漏,因为关闭 Hook 将保持对我的关闭实现和 Web 应用程序的类加载器的强引用。
什么是解决这个问题的适当方法?我现在正在研究的选项是:
使用
java.sql.DriverAction.deregister()
进行清理。不适合,因为驱动程序不会在正常的应用程序退出时注销。
使用
java.sql.DriverAction.deregister()
删除关闭 Hook 并自行执行关闭逻辑。DriverAction
的使用存在一些问题,因为驱动程序仍然支持 Java 7,并且此类是在 JDBC 4.2 (Java 8) 中引入的。这在技术上并不总是正确使用操作(JDBC 驱动程序也可以在现有连接保持有效和使用时注销),并且有可能使用该驱动程序(通过javax.sql.DataSource
) 而 JDBCjava.sql.Driver
实现未注册。包括一个用
@WebListener
注释的javax.servlet.ServletContextListener
实现,该驱动程序将删除关闭 Hook 并自行执行关闭逻辑。如果将驱动程序作为一个整体部署到服务器而不是特定的 Web 应用程序(尽管可以解决这些复杂问题),则此选项会很复杂。
Java 中是否有我忽略的适合我需要的关闭机制?
最佳答案
我试图弄清楚这一点,因为这似乎是一个有趣的案例。我在这里发布我的发现,尽管我觉得我可能仍然误解了某些东西,或者做了一些过于牵强的简化。其实,也有可能我完全误解了你的情况,这个回答毫无用处(如果是这样,我道歉)。
我在这里汇集的内容基于两个概念:
- 应用程序服务器全局状态(我使用
System.props
,但它可能不是最佳选择 - 也许一些临时文件会更好) - 特定于容器的全局状态(这意味着由特定于容器的
ClassLoader
加载的所有类)
我提出了一个 EmbeddedEngineHandler.loadEmbeddedEngineIfNeeded
方法,该方法将被调用:
- 在您的司机注册期间
- 在你的
javax.sql.DataSource
实现静态初始化器中(如果整个DataSource
相关的东西都是那样工作的——我对此知之甚少)
如果我做对了,您根本不需要调用 Runtime.removeShutdownHook
。
我在这里不确定的主要事情是 - 如果驱动程序是全局部署的,它会在任何 servlet 初始化之前注册吗?如果没有,那我就错了,这行不通。但是也许检查 EmbeddedEngineHandler
的 ClassLoader
会有帮助吗?
这是EmbeddedEngineHandler
:
final class EmbeddedEngineHandler {
private static final String PREFIX = ""; // some ID for your library here
private static final String IS_SERVLET_CONTEXT = PREFIX + "-is-servlet-context";
private static final String GLOBAL_ENGINE_LOADED = PREFIX + "-global-engine-loaded";
private static final String TRUE = "true";
private static volatile boolean localEngineLoaded = false;
// LOADING
static void loadEmbeddedEngineIfNeeded() {
if (isServletContext()) {
// handles only engine per container case
loadEmbeddedEngineInLocalContextIfNeeded();
} else {
// handles both normal Java application & global driver cases
loadEmbeddedEngineInGlobalContextIfNeeded();
}
}
private static void loadEmbeddedEngineInLocalContextIfNeeded() {
if (!isGlobalEngineLoaded() && !isLocalEngineLoaded()) { // will not load if we have a global driver
loadEmbeddedEngine();
markLocalEngineAsLoaded();
}
}
private static void loadEmbeddedEngineInGlobalContextIfNeeded() {
if (!isGlobalEngineLoaded()) {
loadEmbeddedEngine();
markGlobalEngineAsLoaded();
Runtime.getRuntime().addShutdownHook(new Thread(EmbeddedEngineHandler::unloadEmbeddedEngine));
}
}
private static void loadEmbeddedEngine() {
}
static void unloadEmbeddedEngine() {
}
// SERVLET CONTEXT (state shared between containers)
private static boolean isServletContext() {
return TRUE.equals(System.getProperty(IS_SERVLET_CONTEXT));
}
static void markAsServletContext() {
System.setProperty(IS_SERVLET_CONTEXT, TRUE);
}
// GLOBAL ENGINE (state shared between containers)
private static boolean isGlobalEngineLoaded() {
return TRUE.equals(System.getProperty(GLOBAL_ENGINE_LOADED));
}
private static void markGlobalEngineAsLoaded() {
System.setProperty(GLOBAL_ENGINE_LOADED, TRUE);
}
// LOCAL ENGINE (container-specific state)
static boolean isLocalEngineLoaded() {
return localEngineLoaded;
}
private static void markLocalEngineAsLoaded() {
localEngineLoaded = true;
}
}
这是ServletContextListener
:
@WebListener
final class YourServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
EmbeddedEngineHandler.markAsServletContext();
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
if (EmbeddedEngineHandler.isLocalEngineLoaded()) {
EmbeddedEngineHandler.unloadEmbeddedEngine();
}
}
}
关于java - 在 'normal' Java 应用程序和 Web 应用程序中运行良好的库关闭例程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51685146/