问题描述
当我的HttpServlet抛出ServletException时,Tomcat正在记录包含堆栈跟踪的SEVERE消息,尽管它已正确地重定向到web.xml中的另一个HttpServlet.
Tomcat使用stacktrace记录以下消息:
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捕获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?
- Catch the exception and use RequestDispatcher to forward the request to the error page
- Don’t catch the exception and define the ‘exception to error-page’ mapping in web.xml
- Catch the exception, wrap it into ServletException and define the ‘business exception to error-page’ mapping in web.xml
- Catch the exception, wrap it into ServletException, and define the ‘ServletException to error-page’ mapping in web.xml
- Don’t do anything, the servlet container will automatically send a default error page
第三个答案(标记为正确)清楚地表明我重新指导例外的方式是一个明智的解决方案.
进一步讨论材料
我在this page发现了以下引用(来自Coderanch.com的Tom Holloway的10-2-2012)
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并分派该页面.但请注意,操作词是页面.与登录屏幕一样,这些页面直接从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的异常处理程序机制时很容易做到