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

标签 java servlets jdbc jna shutdown-hook

我维护一个 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) 而 JDBC java.sql.Driver 实现未注册。

  • 包括一个用 @WebListener 注释的 javax.servlet.ServletContextListener 实现,该驱动程序将删除关闭 Hook 并自行执行关闭逻辑。

    如果将驱动程序作为一个整体部署到服务器而不是特定的 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;
    }
}

这是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/

相关文章:

java - Spring:让 ServeltDispatcher 上下文等待直到另一个上下文完成加载

java - 无法访问 servlet

java - JDBC "database table is locked"错误

java - 通过jsp调用存储过程时出错

java - 数据未插入 Firebase 实时数据库抛出 Android

java - 如何知道在背包问题(DP实现)中选择了哪个项目?

java - 为什么无法加载 lambda 类?

java - 表单例份验证 - 监听器

java - Infinispan拦截器配置?

java - JDBC ODBC 和 4D 数据库 : when reading a row, 未发送列的第一个空格字符