在 "normal" Java 应用程序和 Web 应用程序中运行良好的库关闭例程



我维护一个JDBC驱动程序,该驱动程序还具有通过本地库提供的嵌入式数据库服务器模式,该库通过JNA访问。关闭过程是卸载本机库本身的一部分,由于卸载其依赖项的顺序,在Windows上会遇到问题。为了避免访问冲突或其他问题,我需要在卸载此库之前明确关闭嵌入式引擎。

由于其使用性质,很难确定要求关闭的适当时机。对于普通Java应用程序来说,唯一正确的方法是使用注册关闭挂钩

CCD_ 1与实现关闭逻辑的CCD_ 2的子类。

这对于普通的Java应用程序来说很好,但对于包括我的库作为应用程序一部分的web应用程序(在WAR的WEB-INF/lib中(,这将在取消部署时导致内存泄漏,因为关闭挂钩将保留对我的关闭实现和web应用程序的类加载器的强引用。

解决这一问题的合适和适当的方式是什么?我现在正在研究的选项是:

  • 使用java.sql.DriverAction.deregister()进行清理。

    不适合,因为在正常应用程序退出时不会注销驱动程序。

  • 使用java.sql.DriverAction.deregister()移除关机挂钩并执行关机逻辑本身。

    DriverAction的使用有点问题,因为驱动程序仍然支持Java7,并且这个类是在JDBC 4.2(Java8(中引入的。从技术上讲,这并不总是正确的操作使用(JDBC驱动程序也可以在现有连接保持有效和使用中的情况下注销(,并且可能在JDBCjava.sql.Driver实现未注册的情况下使用驱动程序(通过javax.sql.DataSource(。

  • 包括一个用Runtime.getRuntime().addShutdownHook0注释的javax.servlet.ServletContextListener实现,该实现带有将移除关机挂钩并执行关机逻辑本身的驱动程序。

    如果将驱动程序作为一个整体部署到服务器,而不是部署到特定的web应用程序,则此选项会很复杂(尽管这些复杂问题可以解决(。

Java中是否存在我忽略的适合我需要的关闭机制?

我试图弄清楚这一点,因为这似乎是一个非常有趣的案例。我在这里发布我的发现,尽管我觉得我可能仍然误解了一些东西,或者做了一些过于牵强的简化。事实上,也有可能我完全误解了你的情况,这个答案毫无用处(如果是这样,我道歉(。

我在这里组装的是基于两个概念:

  • 应用程序服务器全局状态(我使用System.props,但它可能不是最佳选择——也许一些临时文件会做得更好(
  • 特定于容器的全局状态(表示特定于容器ClassLoader加载的所有类(

我提出了一种EmbeddedEngineHandler.loadEmbeddedEngineIfNeeded方法,称为:

  • 在您的驾驶员注册期间
  • 在您的javax.sql.DataSource实现静态初始值设定项中(如果整个与DataSource相关的东西都是这样工作的——我对此知之甚少(

如果我做对了,您根本不需要调用Runtime.removeShutdownHook

我在这里不确定的主要问题是——如果驱动程序是全局部署的,它会在初始化任何servlet之前注册吗?如果没有,那我就错了,这是行不通的。但检查EmbeddedEngineHandlerClassLoader可能会有所帮助吗?


这是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;
}
}

这就是Thread0:

@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();
}
}
}

最新更新