【异常】——JAVA之全局国际化异常处理
前言
在项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。 那么,能不能将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护?答案是肯定的。
一、自定义异常
1.用@ControllerAdvice+@ExceptionHandler实现全局异常处理
通常在Controller层需要去捕获service层的异常,防止返回一些不友好的错误信息到客户端,但如果Controller层每个方法都用模块化的try-catch代码去捕获异常,会很难看也难维护。
异常处理最好是解耦的,并且都放在一个地方集中管理。Spring能够较好的处理这种问题
不多说,直接上干货:
ExceptionHander:异常处理类,拦截全局异常并处理
/** * @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(); } }
I18nException :自定义国际化异常
/** * @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; } }
ErrorCode :异常信息的返回码,可根据需要添加不同的异常
/** * @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); } } }
I18nMessage :异常的返回信息,可以根据需要进行补充,比如将异常信息映射到xml文件中等
/** * @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进行数据绑定和数据校验。
下面简单演示添加一个用户时对用户数据进行验证,无需在业务层进行单独频繁的验证
UserBean:先看下实体类中进行了哪些校验
/** * @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); }
此时再次调用:
最后附上封装好的数据响应类ResultBean
/** * @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中加入相应的异常码和异常信息即可!
看到这里的话点个赞关注我给我动力哦
作者:土豆是我的最爱
来源链接:https://blog.csdn.net/qq_37141773/article/details/97146721