@ControllerAdvice全局异常处理
Exception,分为运行时异常(RuntimeException)和非运行时异常
可查的异常(checked exceptions): Exception下除了RuntimeException外的异常
不可查的异常(unchecked exceptions):RuntimeException及其子类和错误(Error)
- 1
- 2
- 3
可查的异常在我们编码的时候就会catch解决,运行时异常则是不可控的,比如一些空指针异常,数组越界之类的异常。
代码里到处写try-catch也不太好,这时候就需要利用AOP做全局异常处理。
一、设计方案
- 有多语言支持,需要一个语言本地化工具类(没这个需求的可不要) ,InternationalizationUtil.java
- 定义一个业务异常类,必须继承自RuntimeException, BusinessException.java
- 定义一个业务异常code类,BusinessErrorCode.java
- 定义Controller增强,ExceptionAdvice.java
- 统一返回格式,Result.java
在业务逻辑处理中会有业务逻辑出错的提示,比如提示用户密码错误,余额不足等等。这些信息都需要传给前端,提示给用户。
流程:业务逻辑出错,抛个BusinessException异常,传入异常BusinessErrorCode,ExceptionAdvice捕获异常进行处理,根据Code,调用本地化语言工具类获取到对应语言的提示信息,封装为Result返回给前端。
二、代码
自定义BusinessException业务异常类。注意,必须继承RuntimeException
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 5317403756736254689L;
private int code;
private Object[] args;
public BusinessException(int messageCode) {
super(getCodeMessage(messageCode));
this.code = messageCode;
}
public BusinessException(int messageCode,Object... args) {
super(getCodeMessage(messageCode));
this.code = messageCode;
this.args = args;
}
private static String getCodeMessage(int messageCode) {
List<String> fieldName = new ArrayList<>();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
Class businessErrorCode = classLoader.loadClass("com.demo.common.BusinessErrorCode");
Field[] fields = businessErrorCode.getDeclaredFields();
List<Field> fieldList = Arrays.asList(fields);
fieldList.stream().forEach(field -> {
try {
field.isAccessible();
if (Integer.parseInt(field.get(businessErrorCode).toString()) == messageCode) {
fieldName.add(field.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
});
return fieldName.get(0);
} catch (Exception e) {
e.printStackTrace();
return "FAIL";
}
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public Object[] getArgs() {
return args;
}
public void setArgs(Object[] args) {
this.args = args;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
我这里因为做国际化,考虑到日志信息显示,对code和message做了特殊处理。一般需求可以直接不要这个getCodeMessage()方法。
定义BusinessErrorCode
public class BusinessErrorCode {
/**
* 参数错误!
*/
public static final int PARAMETER_FAIL = 10000;
}
- 1
- 2
- 3
- 4
- 5
- 6
假如service里有这么一段,抛出参数错误的异常
@Override
public void changeDefaultGradeNo(Long defaultGradeNo, Long groupId, Long uid) {
logger.info("groupId:{} defaultGradeNo:{}", groupId, defaultGradeNo);
if (defaultGradeNo == null) {
throw new BusinessException(BusinessErrorCode.PARAMETER_FAIL);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
在Controller不需要对这个service的changeDefaultGradeNo方法做try-catch处理,用AOP知识,写一个异常增强类统一拦截异常,封装Result返回给前端。
定义异常增强类ExceptionAdvice
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {
private Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);
@Autowired
private InternationalizationUtil i18nUtil;
/**
* 处理BusinessException异常返回信息
*
* @param businessException
* @return
*/
@ExceptionHandler(BusinessException.class)
@ResponseBody
public Result handleBusinessException(BusinessException businessException) {
String message = businessException.getMessage();
Integer errorCode = businessException.getCode();
if (StringUtils.isEmpty(errorCode.toString())) {
errorCode = SystemErrorCode.SYSTEM_ERROR;
}
String resultMessage = i18nUtil.i18n(errorCode+"",businessException.getArgs());
logger.info("业务异常:{}-{}-{}", errorCode, message, resultMessage);
return new Result(errorCode, resultMessage);
}
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public Object handle(RuntimeException runtimeException) {
logger.error("运行时异常:", runtimeException);
return new Result(BusinessErrorCode.FAIL, i18nUtil.i18n(SystemErrorCode.SYSTEM_ERROR));
}
@ExceptionHandler(Exception.class)
@ResponseBody
public Object handle(Exception exception) {
logger.error("异常:", exception);
return new Result(BusinessErrorCode.FAIL, i18nUtil.i18n(SystemErrorCode.SYSTEM_ERROR));
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
可以定义好对不同异常的不同处理方式。
关于本地化语言工具类InternationalizationUtil
@Component
public class InternationalizationUtil {
@Autowired
private MessageSource messageSource;
/**
* 根据errorCode和本地化对象Local获取国际化提示信息
*
* @param errorCode
* @return
*/
public String i18n(int errorCode) {
return i18n(String.valueOf(errorCode));
}
public String i18n(String errorCode) {
return messageSource.getMessage(errorCode, null, errorCode, LocaleContextHolder.getLocale());
}
public String i18n(String errorCode, Object[] args) {
return messageSource.getMessage(errorCode, args, LocaleContextHolder.getLocale());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
如果不用spring默认的文件配置,要指定message资源的位置,参考Spring国际化
spring:
profiles:
active: dev
messages:
basename: i18n/messages
encoding: UTF-8
- 1
- 2
- 3
- 4
- 5
- 6
对应的中文,英文资源文件
最后看结果封装类Result
public class Result {
public static final String SUCCESS_MESSAGE = "";
private int code = BusinessErrorCode.OK;
private String message = SUCCESS_MESSAGE;
private Object data;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
这样一套流程走下来,前端看到的就是
{
"code": 10000,
"message":"参数错误",
"data":
}
- 1
- 2
- 3
- 4
- 5
如果不需要国际化,会简单些。