如何让 ServletContextListener 停止 Java EE 应用程序?

发布于 2024-11-19 16:15:51 字数 346 浏览 2 评论 0原文

我有一个 ServletContextListener,它在我的 Java EE 应用程序启动时执行一些数据库管理功能。它在 JPA 和应用程序的其他部分启动/加载之前在我的应用程序中运行。如果数据库维护失败,我会记录错误。如果数据库维护失败,应用程序将无法正常运行,我想停止该应用程序。

如何从 ServletContextListener.contextInitialized 优雅且正确地停止应用程序?

下面 Viven 给出的解决方案很接近,但并不完全。当我抛出 RuntimeException 时,Glassfish 处于不一致的状态,无法访问其管理控制台,但某些进程仍在运行并保持端口 3700(IIOP?)打开,从而阻止重新启动。

I have a ServletContextListener which performs some database management functions when my Java EE application starts. This runs in my application before JPA and other pieces of the application are started/loaded. If the database maintenance fails I am logging the errors. If the database maintenance fails the application will not function properly and I would like to halt the application.

How can I gracefully and correctly stop the application from ServletContextListener.contextInitialized?

Solution given by Viven below is close but not quite. When I throw a RuntimeException Glassfish is left in an inconsistent state where its admin console is not accessible but some process is still running and keeping port 3700 (IIOP?) open which then prevents a restart.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

故人的歌 2024-11-26 16:15:51

在侦听器中,捕获任何异常并使用 servlet 上下文属性来存储有关错误的标志或其他有用信息。您可能还应该记录一些内容来表明该应用程序无法运行。

此时,您的选择可能取决于应用程序的架构。如果所有请求都由单个控制器/调度程序 Servlet 处理,则让它的 init 方法检查上下文属性并抛出 UnavailableException 可能是有意义的。请注意,该异常仅适用于引发该异常的特定 servlet。如果您的应用程序包含许多 servlet 或允许直接访问其他资源,这会使该方法更难以管理。

另一种选择是创建一个过滤器来拦截每个请求,检查上下文属性,然后抛出异常。其他变化当然是可能的。

In your listener, catch any exceptions and use servlet context attributes to store flags or other useful information about the error. You should probably also log something to indicate that the app is non-functional.

At this point, your options may be dictated by the architecture of your app. If all requests are handled by a single controller/dispatcher servlet, it might make sense to have its init method check the context attributes and throw an UnavailableException. Just be aware that the exception only applies to the specific servlet throwing it. This makes the approach less manageable if your app contains many servlets or allows direct access to other resources.

Another option would be to create a filter that intercepts every request, checks the context attributes and then throws an exception. Other variations are certainly possible.

情独悲 2024-11-26 16:15:51

如果您的 ServletContextListener 引发异常,则 Web 应用程序将无法正确加载,并且应用程序服务器可能阻止所有后续请求(并以 500 错误响应)。

它并不能完全阻止应用程序启动,也不能停止应用程序,但它可以阻止应用程序的任何进一步使用,并且可能对您的情况有用。

在规范中进行正确验证后,该行为在规范中不是强制性的。服务器可能(不是必须)返回 500 错误。因此,必须谨慎使用该解决方案。

请参阅此答案以获取 Servlet 规范的引用。

If your ServletContextListener throws an exception, the webapp won't load correctly and the application server may block all subsequent request (and respond with a 500 error).

It isn't exactly preventing the application to start, nor stopping the application, but it prevents any further usage of the app and could be useful in your case.

After proper verification in the spec, this behaviour isn't mandatory in the specification. The server may (not must) return 500 errors. This solution has to be used carefully, therefore.

See this Answer for a quote from the Servlet spec.

追我者格杀勿论 2024-11-26 16:15:51

以下是如何在不停止 Tomcat 本身的情况下正常停止 Tomcat 11* 上的 Web 应用程序:

package com.example;

import static java.util.logging.Level.SEVERE;
import static javax.xml.xpath.XPathConstants.STRING;
import java.io.File;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.sql.Connection;
import java.util.Hashtable;
import java.util.logging.Logger;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;

public class ExampleServletContextListener implements ServletContextListener {

    /** Standard logger */
    static final Logger LOGGER = Logger.getLogger(ExampleServletContextListener.class.getName());

    /** 
     * Data Source defined in <code>META-INF/context.xml</code>, e.g.
     * <code>
     * <Resource name="jdbc/example_data_source" auth="Container"
     *   type="javax.sql.DataSource" maxTotal="100" maxIdle="30"
     *   maxWaitMillis="10000" username="sa" password="password"
     *   driverClassName="org.h2.Driver"
     *   url="jdbc:h2:mem:example;DB_CLOSE_DELAY=-1" />
     * </code>
     */
    static final String DATA_SOURCE = "java:/comp/env/jdbc/example_data_source";

    /**
     * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
     */
    @Override
    public void contextInitialized(final ServletContextEvent event) {
        try {
            final InitialContext initialContext = new InitialContext();
            final DataSource dataSource = (DataSource) initialContext.lookup(DATA_SOURCE);
            try(final Connection connection = dataSource.getConnection()) {
                // Test the connection
            }
        } catch (final Exception e) {
            LOGGER.log(SEVERE, e.getMessage(), e);
            gracefulStop(event.getServletContext());
        }
    }
    
    /**
     * Gracefully stop the Web Application 
     * @param servletContext the Application context facade
     */
    static void gracefulStop(final ServletContext servletContext) {
        try(final InputStream is = servletContext.getResourceAsStream("/WEB-INF/web.xml")) {
            final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            final DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            final XPath xpath = XPathFactory.newInstance().newXPath();

            final Document webXmlDocument = documentBuilder.parse(is);
            final String contextName = (String) xpath.evaluate("/web-app/@id", webXmlDocument, STRING);

            final String catalinaHome = System.getProperty("catalina.home", System.getenv("CATALINA_HOME"));
            assert catalinaHome != null;
            final File serverXmlFile = new File(new File(new File(catalinaHome), "conf"), "server.xml");
            final Document serverXmlDocument = documentBuilder.parse(serverXmlFile);
            final String serviceName = (String) xpath.evaluate("/Server/Service/@name", serverXmlDocument, STRING); // Default is "Catalina"
            final String hostName = (String) xpath.evaluate("/Server/Service[@name='"+serviceName+"']/Engine/Host/@name", serverXmlDocument, STRING); // Default is "localhost"

            final Hashtable<String, String> table = new Hashtable<>();
            table.put("j2eeType", "WebModule");
            table.put("name", "//" + hostName + "/" + contextName);
            table.put("J2EEApplication", "none");
            table.put("J2EEServer", "none");
            final MBeanServerConnection mbeanServerConnection = ManagementFactory.getPlatformMBeanServer();
            final ObjectName webappObjectName = ObjectName.getInstance(serviceName, table);
            mbeanServerConnection.invoke(webappObjectName, "stop", null, null);
        } catch (final Exception e) {
            LOGGER.log(SEVERE, e.getMessage(), e);
        }
    }
}

*) 使用 Tomcat 11,因为它使用新的 jakarta.servlet 包命名。如果您希望使用 Tomcat 的早期版本,请将其更改为 javax.servlet

脚注:此答案基于:您可以使用 MBeans/JMX 关闭 Tomcat 中的单个应用程序

Here is how to gracefully stop the Web application on Tomcat 11* without stopping Tomcat itself:

package com.example;

import static java.util.logging.Level.SEVERE;
import static javax.xml.xpath.XPathConstants.STRING;
import java.io.File;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.sql.Connection;
import java.util.Hashtable;
import java.util.logging.Logger;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;

public class ExampleServletContextListener implements ServletContextListener {

    /** Standard logger */
    static final Logger LOGGER = Logger.getLogger(ExampleServletContextListener.class.getName());

    /** 
     * Data Source defined in <code>META-INF/context.xml</code>, e.g.
     * <code>
     * <Resource name="jdbc/example_data_source" auth="Container"
     *   type="javax.sql.DataSource" maxTotal="100" maxIdle="30"
     *   maxWaitMillis="10000" username="sa" password="password"
     *   driverClassName="org.h2.Driver"
     *   url="jdbc:h2:mem:example;DB_CLOSE_DELAY=-1" />
     * </code>
     */
    static final String DATA_SOURCE = "java:/comp/env/jdbc/example_data_source";

    /**
     * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
     */
    @Override
    public void contextInitialized(final ServletContextEvent event) {
        try {
            final InitialContext initialContext = new InitialContext();
            final DataSource dataSource = (DataSource) initialContext.lookup(DATA_SOURCE);
            try(final Connection connection = dataSource.getConnection()) {
                // Test the connection
            }
        } catch (final Exception e) {
            LOGGER.log(SEVERE, e.getMessage(), e);
            gracefulStop(event.getServletContext());
        }
    }
    
    /**
     * Gracefully stop the Web Application 
     * @param servletContext the Application context facade
     */
    static void gracefulStop(final ServletContext servletContext) {
        try(final InputStream is = servletContext.getResourceAsStream("/WEB-INF/web.xml")) {
            final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            final DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            final XPath xpath = XPathFactory.newInstance().newXPath();

            final Document webXmlDocument = documentBuilder.parse(is);
            final String contextName = (String) xpath.evaluate("/web-app/@id", webXmlDocument, STRING);

            final String catalinaHome = System.getProperty("catalina.home", System.getenv("CATALINA_HOME"));
            assert catalinaHome != null;
            final File serverXmlFile = new File(new File(new File(catalinaHome), "conf"), "server.xml");
            final Document serverXmlDocument = documentBuilder.parse(serverXmlFile);
            final String serviceName = (String) xpath.evaluate("/Server/Service/@name", serverXmlDocument, STRING); // Default is "Catalina"
            final String hostName = (String) xpath.evaluate("/Server/Service[@name='"+serviceName+"']/Engine/Host/@name", serverXmlDocument, STRING); // Default is "localhost"

            final Hashtable<String, String> table = new Hashtable<>();
            table.put("j2eeType", "WebModule");
            table.put("name", "//" + hostName + "/" + contextName);
            table.put("J2EEApplication", "none");
            table.put("J2EEServer", "none");
            final MBeanServerConnection mbeanServerConnection = ManagementFactory.getPlatformMBeanServer();
            final ObjectName webappObjectName = ObjectName.getInstance(serviceName, table);
            mbeanServerConnection.invoke(webappObjectName, "stop", null, null);
        } catch (final Exception e) {
            LOGGER.log(SEVERE, e.getMessage(), e);
        }
    }
}

*) Using Tomcat 11 because it uses the new jakarta.servlet package naming. If you wish to use a former version of Tomcat, change it to javax.servlet.

Footnote: this answer is based on: You can shutdown a single application in Tomcat, using MBeans/JMX

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文