ピット日記(非同期スレッド異常消失&RequestContextHolder)

9164 ワード

SpringMVCプロジェクトでは、通常、現在のスレッドにバインドされているHttpServertRequestオブジェクトを取得するために、次のコードを使用します.
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

最近、会社のプロジェクト開発では、モジュールAが業務データを統計し、メールを送信し、そのインタフェースは、スケジューラモジュールに提供され、タイミングタスクによって呼び出されます.スケジューラモジュール側は,スケジューラモジュールのインタフェースに「直ちに戻る」ことを要求する.すなわち、トリガ条件を満たすと、スケジューラはモジュールAに対する呼び出しを開始し、モジュールAは直ちに応答し、非同期的にタスクを実行し、タスクの実行が完了した後、コールバックインタフェースを通じて、スケジューラ某某のタスクが完了したことを通知しなければならない.
最初は「すぐに戻る」必要があることに気づかなかったが、インタフェースの処理は同期方式を採用していた.そこで、モジュールAのデータ統計インタフェースを呼び出すとき、非常に時間がかかり、スケジューラはモジュールAの応答を遅々として受け取ることができず、今回のタスクが失敗したと考え、失敗再試行をトリガーし、モジュールAのデータ統計インタフェースを絶えず呼び出す.最後に、モジュールAのデータ統計インタフェースが複数回呼び出される.
その後、私は非同期処理に変更し、データ統計の時間のかかる操作をCompletableFuture.runAsyncに置いた.次にエラーが発生し、データ・レポートのメールが送信されず、ログを見てもエラー・レコードはありません.最後にローカルでデバッグすると、一歩一歩debugし、コンテキストRequestオブジェクトを取得する場所でプログラムが停止していることがわかります.次の行のコードです.プログラムがこのコードを実行すると、停止し、その後のコードも実行されず、ログにもエラーメッセージが出力されません.
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

私はdebugモードを開き、この方法の内部についていくと、現在のコンテキストのRequestオブジェクトが取得されず、異常を投げ出すコードブロックに辿り着いた.
/** RequestContextHolder.class**/
public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
        RequestAttributes attributes = getRequestAttributes();
        if (attributes == null) {
            if (jsfPresent) {
                attributes = RequestContextHolder.FacesRequestAttributesFactory.getFacesRequestAttributes();
            }

            if (attributes == null) {
                throw new IllegalStateException("No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
            }
        }

        return attributes;
    }

本来ならば、この運転時に異常が投げ出され、JVMはそのエラーメッセージを捉えて印刷するはずですが、この異常は捉えられておらず、検索資料ではサブスレッドに発生した異常であるため、メインスレッドに上向きに投げ出されないことがわかりました.ただし、メインスレッド内のランタイム例外はJVMによってスナップされ、処理されます.次のコードを試してみてください.
public static void main(String[] args) throws InterruptedException {
        CompletableFuture.runAsync(() -> {
           System.out.println("start"); 
           String a = null;
           String[] split = a.split(",");
           System.out.println("end");
        });
        System.out.println("wait");
    	// 2s 
        Thread.sleep(2000);
        System.out.println("exit");
    }

NPEに報告すべき非同期コードブロックは,実行後コンソールで何の情報も見られなかった.
また、注意RequestContextHolder.currentRequestAttributes()という文で取得したRequestコンテキストは、スレッドにバインドされており、具体的なロジックはFrameworkServletprocessRequestメソッドであり、SpringMVCのDispatcherServiceletはFrameworkServiceletから継承され、1つのRequestリクエストが来るとservice()メソッドがトリガーされ、FrameworkServletではprocessRequestが呼び出され、このメソッド内では、現在のコンテキスト環境は、ThreadLocalを使用してRequestContextHolderのThreadLocal変数に保存されます.非同期コードブロックでは、実行されるスレッドは、Requestリクエストを処理するスレッドと同じではないことが明らかであるため、Requestコンテキストを取得できません.
非同期コードブロックに実行時異常が発生した場合,どのように処理するかはさらに検討する必要がある.初歩的な解決策は,非同期コードブロック内部にtry−catchで包み,catch句で異常処理を行うことである.あるいはCompletableFutureで非同期タスクを実行する場合、そのタスクの実行をFuture変数で保存してgetメソッドを呼び出し、非同期ブロックの異常をメインスレッドに投げ込むことができる
2020/2/17更新CompletableFuture.runAsync().exceptionally()を使用してexceptionallyメソッドに異常情報をログに記録すればよい