使用Spring MVC的@ControllerAdvice注解做Json的异常处理
一,本文介绍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化处理”的相关代码和说明。