Spring Boot异常统一处理

最近在学习自己搭建一个配置中心平台,准备用spring boot来搭建后台web系统,将遇到的问题在此记录。github项目地址:点击打开链接。

我们在用ajax向服务端请求数据时,免不了会有异常。如果不进行统一处理,直接把异常信息抛到前端,界面会很不友好。spring boot可以通过使用@ControllerAdvice来进行统一异常处理,@ExceptionHandler(value = Exception.class)来指定捕获的异常。

1.自定义异常类

首先,我们自定义一个异常类。在后台业务处理时,如果是可知的业务异常,我们直接抛出此类异常。当捕获到此类异常时,可以直接把异常信息返回到前端。代码如下:

[java]  view plain  copy
  1. package com.kevin.confcenter.common.exception;  
  2.   
  3. /** 
  4.  * @Author: kevin 
  5.  * @Description: 基础异常类 
  6.  * @Date: Created In 2018/3/10 14:51 
  7.  */  
  8. public abstract class ConfCenterException extends RuntimeException {  
  9.     /** 
  10.      * uid 
  11.      */  
  12.     private static final long serialVersionUID = 8037891447646609768L;  
  13.   
  14.     /** 
  15.      * 默认构造函数 
  16.      */  
  17.     public ConfCenterException() {  
  18.     }  
  19.   
  20.     /** 
  21.      * 构造函数 
  22.      * @param errMsg 异常消息 
  23.      */  
  24.     public ConfCenterException(String errMsg) {  
  25.         super(errMsg);  
  26.     }  
  27.   
  28.     /** 
  29.      * 构造函数 
  30.      * @param cause 原始异常 
  31.      */  
  32.     public ConfCenterException(Throwable cause) {  
  33.         super(cause);  
  34.     }  
  35.   
  36.     /** 
  37.      * 构造函数 
  38.      * @param errMsg 异常消息 
  39.      * @param cause 原始异常 
  40.      */  
  41.     public ConfCenterException(String errMsg, Throwable cause) {  
  42.         super(errMsg, cause);  
  43.     }  
  44.   
  45. }  

2.自定义返回数据类

我们自定义一个返回结果类,包装返回前端数据信息,包括状态、错误信息、数据等,所有的ajax请求,数据都用此类包装后返回前端。我们统一处理异常时,也会返回这个类对象。这样,前端根据状态码,就可以做出相应的操作,抛出封装好的错误信息或者跳转到指定的页面。代码如下

[java]  view plain  copy
  1. package com.kevin.confcenter.common.bean.vo;  
  2.   
  3. /** 
  4.  * 客户端的HTTP调用的应答结果类 
  5.  */  
  6. public class ResultInfo {  
  7.   
  8.     /** 
  9.      * 应答结果状态码——成功 
  10.      */  
  11.     public static final int RESULT_CODE_SUCCESS = 0;  
  12.     /** 
  13.      * 应答结果状态码——通用错误 
  14.      */  
  15.     public static final int RESULT_CODE_COMMONERR = 9999;  
  16.   
  17.     /** 
  18.      * session过期 
  19.      */  
  20.     public static final int RESULT_SESSION_TIMEOUT = 1;  
  21.   
  22.     /** 
  23.      * 返回状态 
  24.      */  
  25.     private int status = RESULT_CODE_SUCCESS;  
  26.   
  27.     /** 
  28.      * 返回状态描述 
  29.      */  
  30.     private String statusInfo = "SUCCESS"// 操作结果描述信息  
  31.   
  32.     /** 
  33.      * 返回数据 
  34.      */  
  35.     private Object data;// 操作返回数据绑定  
  36.   
  37.     /** 
  38.      * 返回一个默认的错误结果 
  39.      * 
  40.      * @return 错误结果 
  41.      */  
  42.     public static ResultInfo error() {  
  43.         ResultInfo res = new ResultInfo(RESULT_CODE_COMMONERR, "ERROR");  
  44.         return res;  
  45.     }  
  46.   
  47.     /** 
  48.      * 返回一个带错误信息的错误结果 
  49.      * 
  50.      * @param errorMessage 错误信息 
  51.      * @return 错误结果 
  52.      */  
  53.     public static ResultInfo errorMessage(String errorMessage) {  
  54.         ResultInfo res = new ResultInfo(RESULT_CODE_COMMONERR, errorMessage);  
  55.         return res;  
  56.     }  
  57.   
  58.     /** 
  59.      * session过期 
  60.      * 
  61.      * @return 
  62.      */  
  63.     public static ResultInfo sessionTimeout() {  
  64.         ResultInfo res = new ResultInfo(RESULT_SESSION_TIMEOUT, "登录超时");  
  65.         return res;  
  66.     }  
  67.   
  68.     /** 
  69.      * 返回一个带错误信息和数据的错误结果 
  70.      * 
  71.      * @param errorMessage 错误信息 
  72.      * @param data         数据 
  73.      * @return 错误结果 
  74.      */  
  75.     public static ResultInfo errorMessage(String errorMessage, Object data) {  
  76.         ResultInfo res = new ResultInfo(RESULT_CODE_COMMONERR, errorMessage);  
  77.         res.setData(data);  
  78.         return res;  
  79.     }  
  80.   
  81.     /** 
  82.      * 返回一个带状态和信息的结果 
  83.      * 
  84.      * @param status 状态 
  85.      * @param info   信息 
  86.      * @return 返回结果 
  87.      */  
  88.     public static ResultInfo result(int status, String info) {  
  89.         ResultInfo res = new ResultInfo();  
  90.         res.status = status;  
  91.         res.statusInfo = info;  
  92.         return res;  
  93.     }  
  94.   
  95.     /** 
  96.      * 返回一个带状态,信息和数据的结果 
  97.      * 
  98.      * @param status 状态 
  99.      * @param info   信息 
  100.      * @param data   数据 
  101.      * @return 返回结果 
  102.      */  
  103.     public static ResultInfo result(int status, String info, Object data) {  
  104.         ResultInfo res = new ResultInfo();  
  105.         res.status = status;  
  106.         res.statusInfo = info;  
  107.         res.data = data;  
  108.         return res;  
  109.     }  
  110.   
  111.     /** 
  112.      * 返回一个成功结果 
  113.      * 
  114.      * @return 成功结果 
  115.      */  
  116.     public static ResultInfo success() {  
  117.         ResultInfo res = new ResultInfo();  
  118.         return res;  
  119.     }  
  120.   
  121.     /** 
  122.      * 返回一个带数据的成功结果 
  123.      * 
  124.      * @param data 数据 
  125.      * @return 成功结果 
  126.      */  
  127.     public static ResultInfo success(Object data) {  
  128.         ResultInfo res = new ResultInfo();  
  129.         res.setData(data);  
  130.         return res;  
  131.     }  
  132.   
  133.     /** 
  134.      * 返回一个带信息的成功结果 
  135.      * 
  136.      * @param message 提示信息 
  137.      * @return 成功结果 
  138.      */  
  139.     public static ResultInfo successMessage(String message) {  
  140.         ResultInfo res = new ResultInfo(RESULT_CODE_SUCCESS, message);  
  141.         return res;  
  142.     }  
  143.   
  144.     /** 
  145.      * 默认构造函数 
  146.      */  
  147.     public ResultInfo() {  
  148.     }  
  149.   
  150.     /** 
  151.      * 带状态和信息的构造函数 
  152.      * 
  153.      * @param status     状态 
  154.      * @param statusInfo 提示信息 
  155.      */  
  156.     public ResultInfo(int status, String statusInfo) {  
  157.         this.status = status;  
  158.         this.statusInfo = statusInfo;  
  159.     }  
  160.   
  161.     /** 
  162.      * 带状态,信息和数据的构造函数 
  163.      * 
  164.      * @param status     状态 
  165.      * @param statusInfo 提示信息 
  166.      * @param data       数据 
  167.      */  
  168.     public ResultInfo(int status, String statusInfo, Object data) {  
  169.         super();  
  170.         this.status = status;  
  171.         this.statusInfo = statusInfo;  
  172.         this.data = data;  
  173.     }  
  174.   
  175.     public Object getData() {  
  176.         return data;  
  177.     }  
  178.   
  179.     public int getStatus() {  
  180.         return status;  
  181.     }  
  182.   
  183.     public String getStatusInfo() {  
  184.         return statusInfo;  
  185.     }  
  186.   
  187.     public void setData(Object data) {  
  188.         this.data = data;  
  189.     }  
  190.   
  191.     public void setStatus(int status) {  
  192.         this.status = status;  
  193.     }  
  194.   
  195.     public void setStatusInfo(String statusInfo) {  
  196.         this.statusInfo = statusInfo;  
  197.     }  
  198.   
  199.     @Override  
  200.     public int hashCode() {  
  201.         final int prime = 31;  
  202.         int result = 1;  
  203.         result = prime * result + ((data == null) ? 0 : data.hashCode());  
  204.         result = prime * result + status;  
  205.         result = prime * result + ((statusInfo == null) ? 0 : statusInfo.hashCode());  
  206.         return result;  
  207.     }  
  208.   
  209.     @Override  
  210.     public boolean equals(Object obj) {  
  211.         if (this == obj) {  
  212.             return true;  
  213.         }  
  214.         if (obj == null) {  
  215.             return false;  
  216.         }  
  217.         if (getClass() != obj.getClass()) {  
  218.             return false;  
  219.         }  
  220.         ResultInfo other = (ResultInfo) obj;  
  221.         if (data == null) {  
  222.             if (other.data != null) {  
  223.                 return false;  
  224.             }  
  225.         } else if (!data.equals(other.data)) {  
  226.             return false;  
  227.         }  
  228.         if (status != other.status) {  
  229.             return false;  
  230.         }  
  231.         if (statusInfo == null) {  
  232.             if (other.statusInfo != null) {  
  233.                 return false;  
  234.             }  
  235.         } else if (!statusInfo.equals(other.statusInfo)) {  
  236.             return false;  
  237.         }  
  238.         return true;  
  239.     }  
  240. }  

3.异常统一处理

使用@ControllerAdvice来进行统一异常处理,@ExceptionHandler(value = Exception.class)来指定捕获的异常。代码如下:

[java]  view plain  copy
  1. package com.kevin.confcenter.admin.extend;  
  2.   
  3. import com.kevin.confcenter.common.bean.vo.ResultInfo;  
  4. import com.kevin.confcenter.common.exception.ConfCenterException;  
  5. import org.slf4j.Logger;  
  6. import org.slf4j.LoggerFactory;  
  7. import org.springframework.web.bind.annotation.ControllerAdvice;  
  8. import org.springframework.web.bind.annotation.ResponseBody;  
  9.   
  10. /** 
  11.  * @Author: kevin 
  12.  * @Description: 异常统一处理 
  13.  * @Date: Created In 2018/4/16 10:25 
  14.  */  
  15. @ControllerAdvice  
  16. public class ExceptionHandler {  
  17.   
  18.     private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandler.class);  
  19.   
  20.     @org.springframework.web.bind.annotation.ExceptionHandler(value = Exception.class)  
  21.     @ResponseBody  
  22.     public ResultInfo handler(Exception e) {  
  23.         if (e instanceof ConfCenterException) {  
  24.             return ResultInfo.errorMessage(e.getMessage());  
  25.         } else {  
  26.             LOGGER.error("exception:{}", e.getMessage(), e);  
  27.             return ResultInfo.errorMessage("服务器内部错误");  
  28.         }  
  29.     }  
  30. }  

4.前端ajax封装

前端封装ajax方法,可以实现防重复提交和针对错误码进行相应的处理。代码如下:

[javascript]  view plain  copy
  1. // 系统全局的ajax队列  
  2.     ajax_queue: [],  
  3.     ajax: function (options) {  
  4.         if (options.lu_ajax_id) {  
  5.             // 检查之前的req是否已经完成  
  6.             if (conf.utils.ajax_queue.contains(options.lu_ajax_id)) {  
  7.                 $.fn.alert('不要频繁重复操作,请稍后再试.');  
  8.                 return;  
  9.             }  
  10.             // complete回调,用于移除之前的ajax queue中的请求标记  
  11.             options.complete = function (jqXHR, textStatus) {  
  12.                 var index = conf.utils.ajax_queue.indexOf(options.lu_ajax_id);  
  13.                 if (index > -1) {  
  14.                     conf.utils.ajax_queue.splice(index, 1);  
  15.                 }  
  16.             }  
  17.   
  18.             conf.utils.ajax_queue.push(options.lu_ajax_id);  
  19.         }  
  20.   
  21.         if (!options.dataType) {  
  22.             options.dataType = "json";  
  23.         }  
  24.   
  25.         if (!options.timeout) {  
  26.             options.timeout = 1000 * 60 * 60;  
  27.         }  
  28.   
  29.         if (!options.timeout) {  
  30.             options.timeout = 1000 * 60 * 3;  
  31.         }  
  32.   
  33.         if (options.url.indexOf('?') > -1) {//加入时间戳  
  34.             options.url += '&' + new Date().getTime();  
  35.         } else {  
  36.             options.url += '?' + new Date().getTime();  
  37.         }  
  38.   
  39.         if (!options.success) { //没有加入自定义的success回调函数,则调用默认回调函数  
  40.             options.success = function (res, textStatus, jqXHR) {  
  41.                 if (res.status != 0) { //如果返回结果消息状态码非零则表示失败,弹出错误信息  
  42.                     if (res.status == 1) {  
  43.                         window.location.href = "/user/login";  
  44.                     } else {  
  45.                         if (options.fail) {  
  46.                             options.fail.call(this, res, textStatus, jqXHR);  
  47.                             return;  
  48.                         }  
  49.                         $.fn.alert(res.statusInfo);  
  50.                         return;  
  51.                     }  
  52.                 }  
  53.   
  54.                 if (options.ok) { // 如果有自定义ok回调,则在结果码为成功时回调  
  55.                     options.ok.call(this, res, textStatus, jqXHR);  
  56.                 }  
  57.             }  
  58.         }  
  59.         if (!options.error) { // 没有加入自定义的error回调函数,则指定默认回调  
  60.             options.error = function (res, textStatus, jqXHR) {  
  61.                 $.fn.alert("ERROR:" + jqXHR);  
  62.             }  
  63.         }  
  64.   
  65.         return $.ajax(options);  
  66.     }  

5.实例

我们以登录接口为例,先在controller加一个登录方法,不做任何处理,直接抛出BusinessException异常,BusinessException是继承自ConfCenterException类,代码如下:

[java]  view plain  copy
  1. /** 
  2.      * 登录 
  3.      * 
  4.      * @param 
  5.      * @return 
  6.      */  
  7.     @RequestMapping(value = "/login", method = RequestMethod.POST)  
  8.     @ResponseBody  
  9.     public ResultInfo login(HttpServletRequest request, String userName, String password) {  
  10.         throw new BusinessException("test");  
  11.     }  

前端ajax请求如下:

[javascript]  view plain  copy
  1. conf.utils.ajax({  
  2.             url: '/user/login',  
  3.             type: 'POST',  
  4.             async: false,  
  5.             data: data,  
  6.             ok: function (res, textStatus, jqXHR) {  
  7.                 if (res.status == 0) {  
  8.                     window.location.href = "/index";  
  9.                 }  
  10.             }  
  11.         });  

当我们在前端点击登录时,就会弹窗提示,直接显示我们的异常信息,效果如下:

Spring Boot异常统一处理_第1张图片


你可能感兴趣的:(java基础)