使用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化处理”的相关代码和说明。

posted @ 2018-08-21 09:50  浅滩沙洲  阅读(1294)  评论(2编辑  收藏  举报