Исключения ServletException, выдаваемые HttpServlet, регистрируются Tomcat как "SEVERE", хотя обрабатываются рекомендованным способом.

Описание проблемы

Tomcat регистрирует сообщение SEVERE, включая трассировку стека, когда мой HttpServlet создает исключение ServletException, хотя оно правильно перенаправляется на другой HttpServlet в файле web.xml.

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 генерирует исключение ServletException, обертывающее CustomException (подкласс Exception), в метод doGet():

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 - 4 вышеупомянутый метод является правильным способом обработки исключений в сервлетах. Вопрос 29 гласит (предупреждение спойлера: жирный шрифт - это правильные ответы):

Что из нижеперечисленного является разумным способом отправки страницы с ошибкой клиенту в случае бизнес-исключения, которое происходит от java.lang.Exception?

  1. Перехватите исключение и используйте RequestDispatcher, чтобы переслать запрос на страницу ошибки.
  2. Не перехватывайте исключение и определяйте отображение "исключение для страницы ошибки" в web.xml
  3. Перехватите исключение, поместите его в ServletException и определите отображение "бизнес-исключение для страницы ошибки" в файле web.xml.
  4. Перехватите исключение, поместите его в ServletException и определите отображение "ServletException to error-page" в файле web.xml.
  5. Ничего не делать, контейнер сервлета автоматически отправит страницу ошибки по умолчанию

Третий ответ (который отмечен как правильный) ясно утверждает, что мой способ переориентации исключений является разумным решением.

Дальнейшее обсуждение материала

Я нашел следующую цитату на этой странице (от 10-2-2012 Тома Холлоуэя на CodeRanch.com)

На самом деле, исключение ServletException некуда подняться в веб-приложении, и поэтому его появление на главной консоли не так уж и необоснованно, поскольку оно указывает на то, что приложение само не решает проблему.

Фактически, Javadocs говорят это о конструкторе ServletException:

"Создает новое исключение сервлета с указанным сообщением. Сообщение может быть записано в журнал сервера и / или отображено для пользователя".

Обратите внимание, что в нем явно написано журнал сервера.

Сервер может быть вовлечен несколькими способами здесь. Во-первых, вы должны иметь возможность определить общий обработчик исключений в файле web.xml, чтобы приложение могло обрабатывать исключение, при котором этот обработчик может не только регистрироваться в журнале приложения, но и определять, каким должно быть действие восстановления, если таковое имеется. принято (то, что не может сделать более общий код сервера). Во-вторых, вы можете определить пользовательскую страницу ошибки, и в этом случае Tomcat перехватит исключение ServletException и отправит эту страницу. Обратите внимание, однако, что рабочее слово - страница. Как и экраны входа в систему, эти страницы вызываются непосредственно из Tomcat и поэтому не могут быть направлены через сервлеты. Другими словами, используйте HTML или JSP, а не Struts или JSF.

Суть в том, что выброс ServletExceptions является признаком плохого дизайна приложения. Это означает, что кто-то был слишком ленив или слишком спешил, чтобы правильно справиться с проблемой. По сравнению с этим место, где регистрируется ошибка, имеет второстепенное значение.

Это утверждение заставляет меня усомниться в пробном экзамене OCEJWCD (упомянутом выше) в Java Beat и моем собственном решении. Как вы думаете, бизнес-исключения должны обрабатываться другим сервлетом? И если так, думаете ли вы, что Servlet Container (Tomcat) должен регистрировать трассировку стека этих исключений или нет? Если нет, то что тогда будет лучшей практикой?

Заключительные замечания

  • Бросок исключений RuntimeExceptions вместо ServletExceptions приводит к тому же журналу SEVERE.
  • Рабочий пример проблемы предоставлен через этот репозиторий Bitbucket.

1 ответ

Решение

It seems your basic problem is that you want to centralize your error handling but without using a mechanism that causes Tomcat to log the errors as SEVERE?

Since you control all the servlets AFAICT from your question would it not make more sense to define an abstract base servlet that defines all the error handling logic and then just have the rest of your servlets derive from this class?

So you have an abstract base 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);

}

And then your actual servlet:

public class MyServlet extends MyServletBase {

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

Это грубый набросок моей головы без ссылки на Javadoc, поэтому, возможно, подписи метода не будут идеальными, но, надеюсь, вы поняли идею.

Это также имеет то преимущество, что если вам когда-либо понадобится добавить дополнительную логику обработки ошибок в производный сервлет, и вы не захотите менять базовый сервлет по любой причине, вы можете просто переопределить handleError() метод, который не так легко сделать при использовании механизма обработки исключений Tomcat

Другие вопросы по тегам