前言
在项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。 那么,能不能将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护?答案是肯定的。
一、自定义异常
1.用@ControllerAdvice+@ExceptionHandler实现全局异常处理
通常在Controller层需要去捕获service层的异常,防止返回一些不友好的错误信息到客户端,但如果Controller层每个方法都用模块化的try-catch代码去捕获异常,会很难看也难维护。
异常处理最好是解耦的,并且都放在一个地方集中管理。Spring能够较好的处理这种问题
不多说,直接上干货:
/**
* @program: com.exception
* @description: 全局处理异常
* @author: WangZhiJun
* * @create: 2019-07-24 16:16
**/
@ControllerAdvice
class ExceptionHander {
private static final Logger logger = LoggerFactory.getLogger(ExceptionHander.class);
@ExceptionHandler(value = Exception.class)
public ResponseEntity handleException(HttpServletRequest request, Exception e) {
logger.error(dumpThrowable(e));
ResultBean bean = new ResultBean();
//自定义国际化异常,根据需要添加相关业务
if (e instanceof I18nException) {
final I18nException exception = (I18nException) e;
bean.setCode(exception.getErrorCode());
bean.setError(exception.getI18nMessage().toString());
} else if(e instanceof MissingServletRequestParameterException) {
//请求参数缺失异常
MissingServletRequestParameterException missingParameterException = (MissingServletRequestParameterException) e;
bean.setError(missingParameterException.getParameterName() + " is null");
} else if (e instanceof SQLException || e instanceof DuplicateKeyException) {
//SQL相关异常
bean.setError(ErrorCode.SQL.SQL_ERROR);
} else if (e instanceof MyBatisSystemException || e instanceof UncategorizedSQLException) {
//Mybatis或者JDBC异常
bean.setError(ErrorCode.SQL.MYBATIS_ERROR);
} else {
//等等等异常,根据需要添加
bean.setCode(ErrorCode.ERROR);
bean.setError(e);
}
return new ResponseEntity(bean, HttpStatus.OK);
}
//将信息打印在控制台
private String dumpThrowable(Throwable t) {
if (t == null) {
return null;
}
try {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
sw.close();
pw.close();
return "\r\n" + sw.toString() + "\r\n";
} catch (Exception e) {
e.printStackTrace();
}
return "内部错误:" + t.getMessage();
}
}
/**
* @program: com.exception
* @description: 全局异常
* @author: WangZhiJun
* * @create: 2019-07-24 16:16
**/
public class I18nException extends Exception {
private final ErrorCode.ErrorCodeMessage error;
private final int code;
private final I18nMessage message;
public I18nException(ErrorCode.ErrorCodeMessage message) {
super(message.getMessage().toString());
this.error = message;
this.message = message.getMessage();
this.code = message.getCode();
}
public ErrorCode.ErrorCodeMessage getErrorCodeMessage() {
return error;
}
public I18nMessage getI18nMessage() {
return message;
}
public int getErrorCode() {
return code;
}
}
/**
* @program: com.exception
* @description: 异常码
* @author: WangZhiJun
* * @create: 2019-07-24 16:16
**/
public class ErrorCode {
public static final int ERROR = 0x0000;
public static final int SUCCESS = 0x0001;
public static final int ERROR_PARAMETER = 0x0011;
public interface ErrorCodeMessage extends Serializable {
/**
* 获取错误码
* @return int
*/
int getCode();
/**
* 获取错误消息
* @return I18nMessage
*/
I18nMessage getMessage();
}
/**
* SQL相关异常
*/
public enum SQL implements ErrorCodeMessage {
.......
}
/**
* 文件相关异常
*/
public enum File implements ErrorCodeMessage {
.......
}
/**
* 权限相关异常
*/
public enum Authentication implements ErrorCodeMessage {
/**
* 没有权限
*/
PERMISSION_DENIED(0x1101, I18nMessage.PERMISSION_DENIED);
private final int code;
private final I18nMessage message;
Authentication(int code, I18nMessage message) {
this.code = code;
this.message = message;
}
@Override
public int getCode() {
return code;
}
@Override
public I18nMessage getMessage() {
return message;
}
public Authentication setMessage(String fmt, Object... args) {
if (message != null) {
message.setMessage(fmt, args);
}
return this;
}
@Override
public String toString() {
return String.valueOf(message);
}
}
}
/**
* @program: com.exception
* @description: 异常信息
* @author: WangZhiJun
* * @create: 2019-07-24 16:16
**/
public enum I18nMessage implements Serializable {
/**
* 异常信息
*/
FILE_NOT_FOUND("file_not_found"),
/**
* 权限相关异常
*/
PERMISSION_DENIED("permission_denied"),
/**
* 数据库相关异常
*/
SQL_ERROR("sql_error"),
MYBATIS_ERROR("mybatis_error");
// 校验
VALID_ERROR("valid_error");
/**
* **************************** 方法 ******************************************
*/
private final String key;
private String message;
I18nMessage(String key) {
this.key = key;
}
public I18nMessage setMessage(String fmt, Object... obj) {
try {
this.message = String.format(fmt, obj);
} catch (Exception e) {
}
return this;
}
}
结果
当在业务层某处要抛出此异常时控制到会打印错误信息
界面提示信息也会比较友好而不会直接提示原生错误信息
二、数据校验异常处理
用@RequestBody+@Valid进行数据绑定和数据校验。
下面简单演示添加一个用户时对用户数据进行验证,无需在业务层进行单独频繁的验证
/**
* @program: com.exception
* @description: 用户bean
* @author: WangZhiJun
* * @create: 2019-07-24 18:10
**/
@Data
public class UserBean {
@NotNull(message = "userId_not_null", groups = {Add.class})
private String userId;
@NotBlank(message = "password_length_limit", groups = {Add.class})
private String username;
/**
* 密码校验长度大于零,且在标有Add.class的参数上
*/
@NotBlank(message = "password_length_limit", groups = {Add.class})
private String password;
/**
* 年龄限制在最少15岁
*/
@Min(value = 15,message = "age_limit", groups = {Add.class})
private Integer age;
/**
* 性别只能填男或者女(正则)
*/
@Pattern(regexp = "[男|女]", message = "sex_is_boy_or_girl", groups = {Add.class})
private String sex;
}
通过@RequestBody将请求body中的json与对象(UserBean)绑定,使用@Valid进行对象数据验证。如果不使用@Valid,UserBean中的@NotNull等注解不生效。
而在对象数据中每个属性上都有group值,@Valid中也有这个值,这样的话就可以随意控制数据数据校验的位置
当使用上述数据进行接口调用时,由于性别正则校验失败,会抛出异常信息
数据校验失败时,Spring框架会抛出 MethodArgumentNotValidException 异常,利用第一大点提到的全局异常捕获,在ExceptionHander加入校验异常捕获的方法即可对校验异常进行处理
/**
* 用于处理校验失败的异常
*/
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class, ConstraintViolationException.class})
public ResponseEntity<ResultBean> bindException(Exception e) {
StringBuilder errorMessage = new StringBuilder(I18nMessage.VALID_ERROR.toString()).append(":");
String message;
if (e instanceof ConstraintViolationException) {
ConstraintViolationException ce = (ConstraintViolationException) e;
for (ConstraintViolation<?> messageInfo : ce.getConstraintViolations()) {
message = messageInfo.getMessage();
errorMessage.append(StringUtils.isEmpty(message) ? messageInfo.getMessage() : message).append(", ");
}
} else {
BindingResult bindingResult;
if (e instanceof MethodArgumentNotValidException) {
MethodArgumentNotValidException methodArgumentNotValidException = (MethodArgumentNotValidException) e;
bindingResult = methodArgumentNotValidException.getBindingResult();
} else {
BindException bindException = (BindException) e;
bindingResult = bindException.getBindingResult();
}
for (FieldError fieldError : bindingResult.getFieldErrors()) {
message = fieldError.getDefaultMessage();
errorMessage.append(StringUtils.isEmpty(message) ? fieldError.getDefaultMessage() : message).append(", ");
}
}
errorMessage.deleteCharAt(errorMessage.lastIndexOf(","));
return new ResponseEntity<>(new ResultBean().setError(errorMessage.toString().trim()), HttpStatus.OK);
}
此时再次调用:
/**
* @program: com.exception
* @description: Http 统一数据响应结果
* @author: WangZhiJun
* * @create: 2019-07-24 16:16
**/
@Data
public class ResultBean<T> implements Serializable {
private static final long serialVersionUID = 0;
/**
* 错误码(可用于条件判断)
*/
private int code = ErrorCode.ERROR;
/**
* 提示信息(不要用于条件判断)
*/
private String message;
/**
* 数据
*/
private T data;
public int getCode() {
return code;
}
public ResultBean<T> setCode(int code) {
this.code = code;
return this;
}
public String getMessage() {
return message;
}
public ResultBean<T> setMessage(I18nMessage message) {
this.message = message == null ? null : message.toString();
return this;
}
public T getData() {
return data;
}
public ResultBean<T> setData(T data) {
this.data = data;
return this;
}
public ResultBean<T> setSuccess() {
setCode(ErrorCode.SUCCESS);
return this;
}
public ResultBean<T> setError(ErrorCode.ErrorCodeMessage message) {
setCode(message.getCode());
setMessage(message.getMessage());
return this;
}
public ResultBean<T> setError(Throwable throwable) {
String message = throwable != null ? throwable.getMessage() : null;
return setError(StringUtils.isEmpty(message) && throwable != null ? throwable.getClass().getSimpleName() : message);
}
public ResultBean<T> setError(String message) {
setCode(ErrorCode.ERROR);
this.message = message;
return this;
}
}
大家可以根据自己公司封装的数据响应类对其他类进行一定的修改。
是不是觉得很方便,这样在写业务逻辑的时候,就不需要烦恼要返回不同的异常处理结果,随时随地可以抛出自己想要抛出的异常,只需要Errcode.java和I18nMessage中加入相应的异常码和异常信息即可!
看到这里的话点个赞关注我给我动力哦