Spring Boot异常统一处理
最近在学习自己搭建一个配置中心平台,准备用spring boot来搭建后台web系统,将遇到的问题在此记录。github项目地址:点击打开链接。
我们在用ajax向服务端请求数据时,免不了会有异常。如果不进行统一处理,直接把异常信息抛到前端,界面会很不友好。spring boot可以通过使用@ControllerAdvice来进行统一异常处理,@ExceptionHandler(value = Exception.class)
来指定捕获的异常。
1.自定义异常类
首先,我们自定义一个异常类。在后台业务处理时,如果是可知的业务异常,我们直接抛出此类异常。当捕获到此类异常时,可以直接把异常信息返回到前端。代码如下:
package com.kevin.confcenter.common.exception;
/**
* @Author: kevin
* @Description: 基础异常类
* @Date: Created In 2018/3/10 14:51
*/
public abstract class ConfCenterException extends RuntimeException {
/**
* uid
*/
private static final long serialVersionUID = 8037891447646609768L;
/**
* 默认构造函数
*/
public ConfCenterException() {
}
/**
* 构造函数
* @param errMsg 异常消息
*/
public ConfCenterException(String errMsg) {
super(errMsg);
}
/**
* 构造函数
* @param cause 原始异常
*/
public ConfCenterException(Throwable cause) {
super(cause);
}
/**
* 构造函数
* @param errMsg 异常消息
* @param cause 原始异常
*/
public ConfCenterException(String errMsg, Throwable cause) {
super(errMsg, cause);
}
}
2.自定义返回数据类
我们自定义一个返回结果类,包装返回前端数据信息,包括状态、错误信息、数据等,所有的ajax请求,数据都用此类包装后返回前端。我们统一处理异常时,也会返回这个类对象。这样,前端根据状态码,就可以做出相应的操作,抛出封装好的错误信息或者跳转到指定的页面。代码如下
package com.kevin.confcenter.common.bean.vo;
/**
* 客户端的HTTP调用的应答结果类
*/
public class ResultInfo {
/**
* 应答结果状态码——成功
*/
public static final int RESULT_CODE_SUCCESS = 0;
/**
* 应答结果状态码——通用错误
*/
public static final int RESULT_CODE_COMMONERR = 9999;
/**
* session过期
*/
public static final int RESULT_SESSION_TIMEOUT = 1;
/**
* 返回状态
*/
private int status = RESULT_CODE_SUCCESS;
/**
* 返回状态描述
*/
private String statusInfo = "SUCCESS"; // 操作结果描述信息
/**
* 返回数据
*/
private Object data;// 操作返回数据绑定
/**
* 返回一个默认的错误结果
*
* @return 错误结果
*/
public static ResultInfo error() {
ResultInfo res = new ResultInfo(RESULT_CODE_COMMONERR, "ERROR");
return res;
}
/**
* 返回一个带错误信息的错误结果
*
* @param errorMessage 错误信息
* @return 错误结果
*/
public static ResultInfo errorMessage(String errorMessage) {
ResultInfo res = new ResultInfo(RESULT_CODE_COMMONERR, errorMessage);
return res;
}
/**
* session过期
*
* @return
*/
public static ResultInfo sessionTimeout() {
ResultInfo res = new ResultInfo(RESULT_SESSION_TIMEOUT, "登录超时");
return res;
}
/**
* 返回一个带错误信息和数据的错误结果
*
* @param errorMessage 错误信息
* @param data 数据
* @return 错误结果
*/
public static ResultInfo errorMessage(String errorMessage, Object data) {
ResultInfo res = new ResultInfo(RESULT_CODE_COMMONERR, errorMessage);
res.setData(data);
return res;
}
/**
* 返回一个带状态和信息的结果
*
* @param status 状态
* @param info 信息
* @return 返回结果
*/
public static ResultInfo result(int status, String info) {
ResultInfo res = new ResultInfo();
res.status = status;
res.statusInfo = info;
return res;
}
/**
* 返回一个带状态,信息和数据的结果
*
* @param status 状态
* @param info 信息
* @param data 数据
* @return 返回结果
*/
public static ResultInfo result(int status, String info, Object data) {
ResultInfo res = new ResultInfo();
res.status = status;
res.statusInfo = info;
res.data = data;
return res;
}
/**
* 返回一个成功结果
*
* @return 成功结果
*/
public static ResultInfo success() {
ResultInfo res = new ResultInfo();
return res;
}
/**
* 返回一个带数据的成功结果
*
* @param data 数据
* @return 成功结果
*/
public static ResultInfo success(Object data) {
ResultInfo res = new ResultInfo();
res.setData(data);
return res;
}
/**
* 返回一个带信息的成功结果
*
* @param message 提示信息
* @return 成功结果
*/
public static ResultInfo successMessage(String message) {
ResultInfo res = new ResultInfo(RESULT_CODE_SUCCESS, message);
return res;
}
/**
* 默认构造函数
*/
public ResultInfo() {
}
/**
* 带状态和信息的构造函数
*
* @param status 状态
* @param statusInfo 提示信息
*/
public ResultInfo(int status, String statusInfo) {
this.status = status;
this.statusInfo = statusInfo;
}
/**
* 带状态,信息和数据的构造函数
*
* @param status 状态
* @param statusInfo 提示信息
* @param data 数据
*/
public ResultInfo(int status, String statusInfo, Object data) {
super();
this.status = status;
this.statusInfo = statusInfo;
this.data = data;
}
public Object getData() {
return data;
}
public int getStatus() {
return status;
}
public String getStatusInfo() {
return statusInfo;
}
public void setData(Object data) {
this.data = data;
}
public void setStatus(int status) {
this.status = status;
}
public void setStatusInfo(String statusInfo) {
this.statusInfo = statusInfo;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((data == null) ? 0 : data.hashCode());
result = prime * result + status;
result = prime * result + ((statusInfo == null) ? 0 : statusInfo.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ResultInfo other = (ResultInfo) obj;
if (data == null) {
if (other.data != null) {
return false;
}
} else if (!data.equals(other.data)) {
return false;
}
if (status != other.status) {
return false;
}
if (statusInfo == null) {
if (other.statusInfo != null) {
return false;
}
} else if (!statusInfo.equals(other.statusInfo)) {
return false;
}
return true;
}
}
3.异常统一处理
使用@ControllerAdvice来进行统一异常处理,@ExceptionHandler(value = Exception.class)
来指定捕获的异常。代码如下:
package com.kevin.confcenter.admin.extend;
import com.kevin.confcenter.common.bean.vo.ResultInfo;
import com.kevin.confcenter.common.exception.ConfCenterException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @Author: kevin
* @Description: 异常统一处理
* @Date: Created In 2018/4/16 10:25
*/
@ControllerAdvice
public class ExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandler.class);
@org.springframework.web.bind.annotation.ExceptionHandler(value = Exception.class)
@ResponseBody
public ResultInfo handler(Exception e) {
if (e instanceof ConfCenterException) {
return ResultInfo.errorMessage(e.getMessage());
} else {
LOGGER.error("exception:{}", e.getMessage(), e);
return ResultInfo.errorMessage("服务器内部错误");
}
}
}
4.前端ajax封装
前端封装ajax方法,可以实现防重复提交和针对错误码进行相应的处理。代码如下:
// 系统全局的ajax队列
ajax_queue: [],
ajax: function (options) {
if (options.lu_ajax_id) {
// 检查之前的req是否已经完成
if (conf.utils.ajax_queue.contains(options.lu_ajax_id)) {
$.fn.alert('不要频繁重复操作,请稍后再试.');
return;
}
// complete回调,用于移除之前的ajax queue中的请求标记
options.complete = function (jqXHR, textStatus) {
var index = conf.utils.ajax_queue.indexOf(options.lu_ajax_id);
if (index > -1) {
conf.utils.ajax_queue.splice(index, 1);
}
}
conf.utils.ajax_queue.push(options.lu_ajax_id);
}
if (!options.dataType) {
options.dataType = "json";
}
if (!options.timeout) {
options.timeout = 1000 * 60 * 60;
}
if (!options.timeout) {
options.timeout = 1000 * 60 * 3;
}
if (options.url.indexOf('?') > -1) {//加入时间戳
options.url += '&' + new Date().getTime();
} else {
options.url += '?' + new Date().getTime();
}
if (!options.success) { //没有加入自定义的success回调函数,则调用默认回调函数
options.success = function (res, textStatus, jqXHR) {
if (res.status != 0) { //如果返回结果消息状态码非零则表示失败,弹出错误信息
if (res.status == 1) {
window.location.href = "/user/login";
} else {
if (options.fail) {
options.fail.call(this, res, textStatus, jqXHR);
return;
}
$.fn.alert(res.statusInfo);
return;
}
}
if (options.ok) { // 如果有自定义ok回调,则在结果码为成功时回调
options.ok.call(this, res, textStatus, jqXHR);
}
}
}
if (!options.error) { // 没有加入自定义的error回调函数,则指定默认回调
options.error = function (res, textStatus, jqXHR) {
$.fn.alert("ERROR:" + jqXHR);
}
}
return $.ajax(options);
}
5.实例
我们以登录接口为例,先在controller加一个登录方法,不做任何处理,直接抛出BusinessException异常,BusinessException是继承自ConfCenterException类,代码如下:
/**
* 登录
*
* @param
* @return
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public ResultInfo login(HttpServletRequest request, String userName, String password) {
throw new BusinessException("test");
}
前端ajax请求如下:
conf.utils.ajax({
url: '/user/login',
type: 'POST',
async: false,
data: data,
ok: function (res, textStatus, jqXHR) {
if (res.status == 0) {
window.location.href = "/index";
}
}
});
当我们在前端点击登录时,就会弹窗提示,直接显示我们的异常信息,效果如下:
还没有评论,来说两句吧...