java - HttpServlet 抛出的 ServletExceptions 被 Tomcat 记录为 'SEVERE',尽管以推荐的方式处理

标签 java tomcat servlets exception-handling servletexception

问题描述

当我的 HttpServlet 抛出 ServletException 时,Tomcat 正在记录一条包含堆栈跟踪的严重消息,尽管它已正确重定向到 web.xml 中的另一个 HttpServlet。

Tomcat 使用堆栈跟踪记录以下消息:

21-Mar-2015 15:24:57.521 SEVERE [http-nio-8080-exec-28] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [MyHttpServlet] in context with path [/HttpServletExceptionHandler] threw exception [CustomException] with root cause CustomException
at MyHttpServlet.doGet(MyHttpServlet.java:20)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:618)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:516)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1086)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:659)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:223)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1558)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1515)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)

我做了什么?

首先,MyHttpServlet 在它的 doGet() 方法中抛出一个包含 CustomException(Exception 的子类)的 ServletException:

public class MyHttpServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
        throw new ServletException(new CustomException());
    }

}

然后,抛出的 CustomException 被重定向到 MyServletExceptionHandler(映射到位置 '/MyServletExceptionHandler'。此重定向在 web.xml 中按以下方式定义:

<error-page>
    <exception-type>CustomException</exception-type>
    <location>/MyServletExceptionHandler</location>
</error-page>

最后,MyServletExceptionHandler 接收到抛出的异常并打印出来:

public class MyServletExceptionHandler extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
        final Throwable throwable = (Throwable) req.getAttribute("javax.servlet.error.exception");
        System.out.println("MyServletExceptionHandler caught Throwable: " + throwable.toString());
    }

}

这导致了预期的“MyServletExceptionHandler caught Throwable: CustomException”打印,所以这确实有效,但 Tomcat 也以某种方式记录了上面提到的 SEVERE 消息,包括该堆栈跟踪。这弄乱了我的日志记录。

为什么我要这样?

根据 Java Beat 的 OCEJWCD 6 Mock Exam – 4上面提到的方法是在 Servlet 中处理异常处理的正确方法。问题 29 状态(剧透警告:粗体为正确答案):

Which of the following is a sensible way of sending an error page to the client in case of a business exception that extends from java.lang.Exception?

  1. Catch the exception and use RequestDispatcher to forward the request to the error page
  2. Don’t catch the exception and define the ‘exception to error-page’ mapping in web.xml
  3. Catch the exception, wrap it into ServletException and define the ‘business exception to error-page’ mapping in web.xml
  4. Catch the exception, wrap it into ServletException, and define the ‘ServletException to error-page’ mapping in web.xml
  5. Don’t do anything, the servlet container will automatically send a default error page

第三个答案(标记为正确)清楚地表明我重定向异常的方法是一个明智的解决方案。

进一步的讨论 Material

我在 this page 上找到了以下引述(来自 CodeRanch.com 上的 Tom Holloway,2012 年 10 月 2 日)

Actually, a ServletException has nowhere to go uphill in a webapp, and therefore having it appear on the master console isn't really that unreasonable, because it indicates that the application isn't handling the problem itself.

In fact, the Javadocs say this about the ServletException constructor:

"Constructs a new servlet exception with the specified message. The message can be written to the server log and/or displayed for the user."

请注意,它明确表示服务器日志。

服务器可以通过多种方式参与进来。首先,您应该能够在 web.xml 中定义一个通用异常处理程序以允许应用程序处理异常,该处理程序不仅可以记录到应用程序日志中,而且可以确定应该执行什么恢复操作(如果有的话)已采取(更通用的服务器代码无法做到的事情)。其次,您可以定义自定义错误页面,在这种情况下,Tomcat 将捕获 ServletException 并分派(dispatch)该页面。但是请注意,关键词是页面。与登录屏幕一样,这些页面直接从 Tomcat 调用,因此不能通过 servlet 进行路由。换句话说,使用 HTML 或 JSP,而不是 Struts 或 JSF。

不过,最重要的是,抛出 ServletExceptions 是糟糕的应用程序设计的标志。这意味着某人太懒惰或太急于正确处理问题。与此相比,记录错误的位置是次要的。

此声明让我质疑 Java Beat 的 OCEJWCD 模拟考试(如上所述)和我自己的解决方案作为良好实践。你认为业务异常应该由另一个 Servlet 来处理吗?如果是这样,您认为 Servlet 容器 (Tomcat) 是否应该记录这些异常的堆栈跟踪?如果不是,那么最佳做法是什么?

结语

  • 抛出 RuntimeExceptions 而不是 ServletExceptions 会导致相同的 SEVERE 日志。
  • 问题的工作示例通过 this Bitbucket repository 提供。 .

最佳答案

看来您的基本问题是您想集中处理错误,但没有使用导致 Tomcat 将错误记录为 SEVERE 的机制?

既然您从您的问题中控制了所有 servlet AFAICT,那么定义一个定义所有错误处理逻辑的抽象基础 servlet,然后让您的其余 servlet 派生自此类不是更有意义吗?

所以你有一个抽象的基础 servlet:

public abstract class MyServletBase extends HttpServlet {

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) {
    try {
      doGetInternal(req, resp);
    } catch (RuntimeException e) {
      handleError(e, req, resp);
    }
  }

  protected void handleError(RuntimeException e, HttpServletRequest req, HttpServletResponse resp) {
    // Error handling logic goes here
  }

  protected void doGetInternal(HttpServletRequest req, HttpServletResponse resp);

}

然后是您的实际 servlet:

public class MyServlet extends MyServletBase {

  @Override
  protected void doGetInternal(HttpServletRequest req, HttpServlet resp) {
    // Actual servlet logic here
  }
}

这是我脑海中的粗略草图,没有引用 Javadoc,因此方法签名可能不完美,但希望您能理解。

它还有一个优点,如果您需要向派生 servlet 添加额外的错误处理逻辑,并且您不想出于任何原因更改基本 servlet,您可以覆盖 handleError() 方法,使用 Tomcat 的异常处理机制可不是那么容易做到的事情

关于java - HttpServlet 抛出的 ServletExceptions 被 Tomcat 记录为 'SEVERE',尽管以推荐的方式处理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24403448/

相关文章:

java - 拦截 JAX-RS 请求 : Register a ContainerRequestFilter with tomcat

tomcat - 在 SERVERPILOT 配置的环境中设置 tomcat

java - 服务器在错误的目录下寻找jsp Spring MVC + Tomcat

java - resultSet.getInt() 在包含两个整数的字符串上返回值

java - 我的服务器端有很多 @Get 的 ReSTLet

java - Eclipse 不正确的变量名

java - 在我的 servlet 中使用 this.variable 访问全局变量的目的是什么?

java - 即使更改 URL 后,Tomcat 仍会继续重定向到 servlet I

java - 为什么 LoginContext 生产者在 Java EE/Servlet 容器中工作?

java - getGenerateKeys 返回一个空的 ResultSet