5

問題の説明

web.xml で別の HttpServlet に適切にリダイレクトされますが、Tomcat は、HttpServlet が ServletException をスローしているときに、スタックトレースを含む SEVERE メッセージをログに記録しています。

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 catch Throwable: CustomException' が出力されるため、これは機能しますが、どういうわけか、Tomcat はそのスタック トレースを含む上記の SEVERE メッセージもログに記録します。これは私のロギングを台無しにします。

なぜ私はこのようにしたいのですか?

Java Beat のOCEJWCD 6 Mock Exam – 4によると、上記の方法は、サーブレットで例外処理を処理する適切な方法です。質問 29 の状態 (ネタバレ注意: 太字は正解):

java.lang.Exception から拡張されたビジネス例外が発生した場合にクライアントにエラー ページを送信する賢明な方法は次のうちどれですか?

  1. 例外をキャッチし、RequestDispatcher を使用してリクエストをエラー ページに転送します。
  2. 例外をキャッチせず、web.xml で「エラー ページへの例外」マッピングを定義します。
  3. 例外をキャッチし、それを ServletException にラップし、web.xml で「エラー ページへのビジネス例外」マッピングを定義します。
  4. 例外をキャッチして ServletException にラップし、web.xml で「ServletException からエラーページへ」のマッピングを定義します。
  5. 何もしないでください。サーブレット コンテナは自動的にデフォルトのエラー ページを送信します。

3 番目の回答 (正しいとマークされています) は、例外をリダイレクトする私の方法が賢明な解決策であることを明確に示しています。

さらなる討論資料

このページで次の引用を見つけました(2012 年 10 月 2 日、Tom Holloway による CodeRanch.com から)

実際、Web アプリケーションでは ServletException は上り坂になる場所がないため、マスター コンソールに表示することはそれほど不合理ではありません。アプリケーション自体が問題を処理していないことを示しているからです。

実際、Javadoc は ServletException コンストラクターについて次のように述べています。

「指定されたメッセージで新しいサーブレット例外を構築します。メッセージはサーバー ログに書き込んだり、ユーザーに表示したりできます。」

サーバーログと明示的に記載されていることに注意してください。

サーバーは、ここでさまざまな方法で関与できます。まず、web.xml で一般的な例外ハンドラーを定義して、アプリが例外を処理できるようにする必要があります。このハンドラーは、アプリケーション ログにログを記録するだけでなく、回復アクションが必要な場合はそれを決定できます。 (より一般的なサーバー コードでは実行できないこと)。次に、カスタム エラー ページを定義できます。この場合、Tomcat は ServletException をキャッチし、そのページをディスパッチします。ただし、操作ワードはページであることに注意してください。ログイン画面と同様に、これらのページは Tomcat から直接呼び出されるため、サーブレットを介してルーティングすることはできません。つまり、Struts や JSF ではなく、HTML または JSP を使用してください。

ただし、肝心なのは、ServletExceptions をスローすることは、アプリケーション設計が悪いことを示しているということです。これは、誰かが怠けすぎたり、急いで問題に適切に対処できなかったことを意味します。それに比べて、エラーがログに記録される場所は二次的な重要性です。

このステートメントは、Java Beat の OCEJWCD Mock Exam (前述) と、優れた実践としての私自身のソリューションに疑問を投げかけます。ビジネス例外は別のサーブレットで処理する必要があると思いますか? もしそうなら、サーブレット コンテナ (Tomcat) はこれらの例外のスタック トレースをログに記録する必要があると思いますか? そうでない場合、ベストプラクティスは何ですか?

最後に

  • ServletExceptions の代わりに RuntimeExceptions をスローすると、同じ SEVERE ログが生成されます。
  • 問題の実際の例は、この Bitbucket リポジトリで提供されています。
4

1 に答える 1