1. 程式人生 > >spring @ControllerAdvice處理異常無法正確匹配自定義異常問題

spring @ControllerAdvice處理異常無法正確匹配自定義異常問題

首先說結論,使用@ControllerAdvice配合@ExceptionHandler處理全域性controller的異常時,如果想要正確匹配自己的自定義異常,需要在controller的方法上丟擲相應的自定義異常,或者自定義異常繼承RuntimeException類。

問題描述:
1、在使用@ControllerAdvice配合@ExceptionHandler處理全域性異常時,自定義了一個AppException(extends Exception),由於有些全域性的引數需要統一驗證,所以在所有controller的方法上加一層AOP校驗,如果引數校驗沒通過也丟擲AppException
2、在@ControllerAdvice標記的類上,主要有兩個@ExceptionHandler,分別匹配AppException.class和Throwable.class。
3、在測試時,由於全域性AOP的引數校驗沒通過,丟擲了AppException,但是發現這個AppException被Throwable.class匹配到了,而不是我們想要的AppException.class匹配上。

分析過程:

一階段

開始由於一直測試的兩個不同的請求(一個通過swagger,一個通過遊覽器地址輸入,兩個請求比較相似,我以為是同一個請求),一個方法上丟擲了AppException,一個沒有,然後發現這個問題時現時不現,因為無法穩定復現問題,我猜測可能是AppException出了問題,所以我修改了AppException,將其父類改為了RuntimeException,然後發現問題解決了

二階段

問題解決後,我又思考了下為啥會出現這種情況,根據java的異常體系來說,無論是繼承Exception還是RuntimeException,都不應該會匹配到Throwable.class上去。我再次跟蹤了異常的執行過程,粗略的過了一遍,發現在下面這個位置出現了差別:

catch (InvocationTargetException ex) {
            // Unwrap for HandlerExceptionResolvers ...
            Throwable targetException = ex.getTargetException();
            if (targetException instanceof RuntimeException) {
                throw (RuntimeException) targetException;
            }
            else
if (targetException instanceof Error) { throw (Error) targetException; } else if (targetException instanceof Exception) { throw (Exception) targetException; } else { String text = getInvocationErrorMessage("Failed to invoke handler method", args); throw new IllegalStateException(text, targetException); } }

成功的走的是Exception,失敗的走的是RuntimeException。
這時候到了@ControllerAdvice標記的類時就會出問題了,因為繼承AppException是和RuntimeException是平級,所以如果走runtimeException這個判斷條件丟擲去的異常註定就不會被AppException匹配上。

這時候再仔細對比下異常型別,可以發現正確的那個異常型別時AppException,而錯誤的那個異常型別時java.lang.reflect.UndeclaredThrowableException,內部包著AppException。

JDK的java doc是這麼解釋UndeclaredThrowableException的:如果代理例項的呼叫處理程式的 invoke 方法丟擲一個經過檢查的異常(不可分配給 RuntimeException 或 Error 的 Throwable),且該異常不可分配給該方法的throws子局宣告的任何異常類,則由代理例項上的方法呼叫丟擲此異常。

因為AppException繼承於Exception,所以代理丟擲的異常就是包著AppException的UndeclaredThrowableException,在@ControllerAdvice匹配的時候自然就匹配不上了。
而當AppException繼承於RuntimeException時,丟擲的異常依舊是AppException,所以能夠被匹配上。

結論

所以解決方法有兩種:AppException繼承RuntimeException或者Controller的方法丟擲AppException異常。