一,本文介紹Spring MVC的自定義異常處理,即在Controller中拋出自定義的異常時,客戶端收到更友好的JSON格式的提示。而不是常見的報錯頁面。
二,場景描述:實現公用API,驗證API key的邏輯,放在攔截器中判斷(等同於在Controller中)並拋出異常,用戶收到類似下圖的提示:
其中,Http狀態Code也能自由控制。
三,解決方案:
1,在RateLimitInterceptor.java攔截器中拋出異常:
1 public class RateLimitInterceptor extends HandlerInterceptorAdapter{ 2 3 @Autowired private RedisService rs; 4 5 /** 6 * 流量控制檢查入口 7 */ 8 @Override 9 public boolean preHandle(HttpServletRequest request, 10 HttpServletResponse response, Object handler) throws RequiredParameterException, SignException, RateLimitException,Exception { 11 super.preHandle(request, response, handler); 12 String appKey = request.getParameter("appKey"); 13 //判斷appKey是否為空或是否合法 14 if(appKey == null){ 15 throw new RequiredParameterException(""); 16 }else if(AppKeyUtils.isFormatCorrect(appKey) || !rs.isExist(appKey)){ 17 18 throw new SignException(); 19 20 }else { 21 try { 22 AppCall appCall = AppCall.create(appKey, AppKeyUtils.getPlanDetails(appKey)); 23 appCall.decrease(); 24 rs.save(appCall); 25 System.out.println("RateLimitInterceptor pass."); 26 } catch (RateLimitException e) { 27 //拋出超限異常 28 throw new RateLimitException(); 29 } 30 } 31 return true; 32 } 33 34 }
當代碼走到(具體怎樣走到,需考慮具體業務邏輯,上述代碼使用AppCall類來封裝,這是后話)
1 throw new SignException();
時,Spring將自動捕獲這個異常。然后做一些處理。這是正常的流程。那么Spring如何自動不火
2,使用Spring MVC的@ControllerAdvice,在GlobalExceptionHandler.java類中實現全局的異常處理類:
1 @ControllerAdvice 2 public class GlobalExceptionHandler { 3 4 @ExceptionHandler(SQLException.class) 5 @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 6 @ResponseBody 7 public ExceptionResponse handleSQLException(HttpServletRequest request, Exception ex) { 8 String message = ex.getMessage(); 9 return ExceptionResponse.create(HttpStatus.INTERNAL_SERVER_ERROR.value(), message); 10 } 11 12 @ResponseStatus(value=HttpStatus.NOT_FOUND, reason="IOException occured") 13 @ExceptionHandler(IOException.class) 14 @ResponseBody 15 public void handleIOException(){ 16 //returning 404 error code 17 } 18 19 @ResponseStatus(HttpStatus.BAD_REQUEST) 20 @ResponseBody 21 @ExceptionHandler(SignException.class) 22 public ExceptionResponse signException(SignException ex) { 23 return ex.getEr(); 24 } 25 26 }
在方法的頭上注解:
1 @ExceptionHandler(SignException.class)
即表示讓Spring捕獲到所有拋出的SignException異常,並交由這個被注解的方法處理。
注解:
1 @ResponseBody
即表示返回的對象,Spring會自動把該對象進行json轉化,最后寫入到Response中。
注解:
1 @ResponseStatus(HttpStatus.BAD_REQUEST)
表示設置狀態碼。如果應用級別的錯誤,此處其實永遠返回200也是可以接受的,但是要在你自定義的異常串和異常碼中做好交代。
本文的方案自定義了一個ExceptionResponse.java類,如下:
1 /** 2 * 返回的json數據 3 * @author craig 4 * 5 */ 6 public class ExceptionResponse { 7 8 private String message; 9 private Integer code; 10 11 /** 12 * Construction Method 13 * @param code 14 * @param message 15 */ 16 public ExceptionResponse(Integer code, String message){ 17 this.message = message; 18 this.code = code; 19 } 20 21 public static ExceptionResponse create(Integer code, String message){ 22 return new ExceptionResponse(code, message); 23 } 24 25 public Integer getCode() { 26 return code; 27 } 28 public String getMessage() { 29 return message; 30 } 31 32 }
如你所知,這個類就是最后傳到用戶面前的一個異常類,code和message根據業務定義並讓用戶知曉。而自定義的SignException.java實際上起到了一個橋梁的作用。Spring把SignException對象捕獲到,轉成相應的ExceptionResponse對象,剩下的就是如何優雅實現的問題了。 如下是SignException.java的實現:
1 /** 2 * 簽名異常 3 * @author tuxiao.czz 4 * 5 */ 6 public class SignException extends Exception { 7 8 private static final long serialVersionUID = 4714113994147018010L; 9 private String message = "AppKey is not correct, please check."; 10 private Integer code = 10002; 11 12 private ExceptionResponse er; 13 14 public SignException() { 15 er = ExceptionResponse.create(code, message); 16 } 17 18 public ExceptionResponse getEr() { 19 return er; 20 } 21 22 }
所有關於這個異常的code和message都寫在這個類里,個人感覺還是可以接受。當然還有另外一種實現,就是只攔截、定義一種Exception類,然后傳不同的code和message進去,然后做相應的處理。這些都是比較靈活的。
以上便是實現“自定義異常json化處理”的相關代碼和說明。