Процедура закрытия библиотек, которая хорошо работает в "нормальном" Java-приложении и в веб-приложении

Я поддерживаю драйвер JDBC, который также имеет встроенный режим сервера базы данных, предоставляемый через собственную библиотеку (доступ через JNA). Завершение работы, выполняемое как часть разгрузки собственной библиотеки, запускается в Windows из-за порядка выгрузки его зависимостей. Чтобы избежать нарушений доступа или других проблем, мне нужно явно отключить встроенный движок до того, как эта библиотека будет выгружена.

Учитывая характер его использования, трудно определить подходящий момент для вызова shutdown, и единственный правильный способ для обычного Java-приложения, который я вижу сейчас, - зарегистрировать крюк остановки с использованием Runtime.getRuntime().addShutdownHook с подклассом Thread которая реализует логику выключения.

Это нормально работает для обычного Java-приложения, но для веб-приложений, которые включают мою библиотеку как часть приложения (в WEB-INF/lib WAR), это приведет к утечке памяти при распаковке, поскольку крюк остановки будет поддерживать сильная ссылка на мою реализацию shutdown и загрузчик классов веб-приложения.

Каким будет подходящий и подходящий способ решения этой проблемы? Параметры, которые я сейчас рассматриваю:

  • С помощью java.sql.DriverAction.deregister() выполните очистку.

    Не подходит, поскольку водитель не будет отменен регистрацией при обычном выходе приложения.

  • Используя java.sql.DriverAction.deregister() удалите крюк выключения и выполните логику завершения работы.

    Использование DriverAction немного проблематично, учитывая, что драйвер по-прежнему поддерживает Java 7, и этот класс был представлен в JDBC 4.2 (Java 8). Это технически не всегда правильное использование действия (драйвер JDBC также можно отменить, пока существующие соединения остаются действительными и используются), и возможно, что драйвер используется (через javax.sql.DataSource), а JDBC java.sql.Driver не регистрируется.

  • Включая реализацию javax.servlet.ServletContextListener аннотированную с помощью @WebListener с драйвером, который удалит крюк отключения и выполнит логику завершения работы.

    Эта опция имеет сложности, если драйвер развертывается на сервере в целом, а не в конкретном веб-приложении (хотя эти проблемы могут быть решены).

Есть ли механизм отключения в Java, который я забыл, который может быть подходящим для моих нужд?

Ответ 1

Я попытался понять это, так как это кажется таким интересным делом. Я публикую свои выводы здесь, хотя я чувствую, что, возможно, все еще что-то неправильно понял или сделал некоторые слишком надуманные упрощения. Собственно, также возможно, что я полностью неправильно понял ваше дело, и этот ответ бесполезен (если так, я извиняюсь).

То, что я собрал здесь, основано на двух понятиях:

  • глобальное состояние сервера приложений (я использую System.props, но это может быть не лучший выбор - возможно, некоторые временные файлы будут лучше)
  • глобальное состояние, специфичное для контейнера (что означает все классы, загружаемые контейнером ClassLoader)

Я предлагаю метод EmbeddedEngineHandler.loadEmbeddedEngineIfNeeded, который будет вызываться:

  • во время регистрации вашего водителя
  • в вашем статическом инициализаторе реализации javax.sql.DataSource (если вся эта работа DataSource -related работает именно так - я мало знаю об этом)

Если я правильно понял, вам не нужно Runtime.removeShutdownHook называть Runtime.removeShutdownHook.

Главное, что я здесь не уверен в этом - если драйвер развернут глобально, будет ли он зарегистрирован до инициализации любого сервлета? Если нет, то я понял, что это неправильно, и это не сработает. Но может ли после этого осмотреть ClassLoader EmbeddedEngineHandler?


Это 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();
        }
    }
}