一,本文介绍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 @ResponseBody15 public void handleIOException(){16 //returning 404 error code17 }18 19 @ResponseStatus(HttpStatus.BAD_REQUEST)20 @ResponseBody21 @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 Method13 * @param code14 * @param message15 */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化处理”的相关代码和说明。