本文源码地址: https://github.com/nieandsun/NRSC-STUDY
1 简单介绍项目中遇到的情况
项目中碰到了比较复杂的参数校验,于是自定义了校验规则,但前端想要获取到具体失败的原因。
问题也就成了这样: 都是参数异常,但是由于各个参数不一样,且校验失败的原因也不一样,感觉使用太多枚举来定义这些异常也没太大的必要 — 因为这些异常信息几乎没有通用性。
于是想到了像打印log日志一样来定义输出的异常,举例来讲,要抛异常时,直接按照下面的方式进行抛出:
//比较复杂的异常 -- 类似于log.error()方法
//第一个参数为异常code;
//第二个参数是要给前端的提示信息,但有些地方使用了占位符
//其余的参数是占位符实际的值
else if (id == -2) {
throw new ElegantRuntimeException(ResultEnum.COMPLEX_FAILURE.getCode(),
"我是比较{}复杂的异常{}", "=====", "哈哈");
}
2 具体代码实现
仅仅列出了关键代码,具体代码有兴趣的可从github上clone下来看一下。
- ElegantRuntimeException的构造方法
/***
* 含有占位符的异常
* @param code
* @param message
* @param arguments
*/
public ElegantRuntimeException(Integer code, String message, Object... arguments) {
super("自定义异常---之复杂异常");
String formatMsg = FormatStringUtils.formatMessage(message, '{', '}');
//利用arguments替换占位符
this.complexMsg = MessageFormat.format(formatMsg, arguments);
this.code = code;
}
- formatMessage方法的具体实现
package com.nrsc.elegant.util;
import java.util.LinkedList;
public class FormatStringUtils {
private FormatStringUtils() {
}
/***
* 将字符串 我是{},你是{},他是{} ----> 转成 我是{0},你是{1},他是{2}
* @param message
* @param prefix
* @param suffix
* @return
*/
public static String formatMessage(String message, char prefix, char suffix) {
LinkedList<Character> chars = new LinkedList<>();
int j = 0;
for (int i = 0; i < message.length(); i++) {
if (prefix == message.charAt(i) && suffix == message.charAt(i + 1)) {
chars.add(prefix);
chars.add(Character.forDigit(j, 10));
j++;
} else {
chars.add(message.charAt(i));
}
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < chars.size(); i++) {
sb.append(chars.get(i));
}
return sb.toString();
}
}
3 简单测试
测试方法如下:
即当启动程序,访问/getUserById/-2时,会抛出上面自定义的异常。
—> 效果如下,说明我们自定义的异常已经成功。
4 自定义checkedException在真实项目中几乎也必不可少
众所周知,异常主要分为两类:
- 检查式异常(checkedException),也称为非运行时异常
这种异常需要try…catch捕获,或者向上抛出,否则无法通过编译(程序中会有报错)。
- 运行时异常(RuntimeException)
这种异常不需要捕获,也不需要向上抛出,虚拟机会处理。
在实际开发中,我们可能很容易想到要自定义一个运行时异常,然后借助统一异常处理的方式来简化代码的开发 —> 提高代码的质量。
但是自定义检查式异常(checkedException)在真实项目中几乎也必不可少,举个栗子来讲 —>
假设有一个方法A依赖于另一个方法B的运行结果
假如方法B报了异常,而如果该异常没有向上抛出,那A方法将无法捕捉到该异常
那A 方法就会无法判断B方法到底有没有运行成功
因此我在代码里同时自定义了一个checkedException,代码基本和自定义的RuntimeException一致,只是该类继承了Exception类,而自定义的RuntimeException继承了RuntimeException类,代码如下:
package com.nrsc.elegant.exception;
import com.nrsc.elegant.enums.ResultEnum;
import com.nrsc.elegant.util.FormatStringUtils;
import lombok.Getter;
import java.text.MessageFormat;
/***
* @author : Sun Chuan
* @date : 2019/10/18 22:21
* Description:自定义异常
* --- 该异常如果在代码语句里抛出,方法上也必须抛出异常
* --- 当然也可以直接try..catch掉
*
* ---在实际开发中可以发现这种自定义异常几乎也是必不可少的
*/
@Getter
public class ElegantCheckedException extends Exception {
private Integer code;
private String complexMsg;
public ElegantCheckedException(ResultEnum resultEnum) {
super(resultEnum.getMessage());
this.code = resultEnum.getCode();
}
public ElegantCheckedException(Integer code, String message) {
super(message);
this.code = code;
}
public ElegantCheckedException(Integer code, String message, Object... arguments) {
super("自定义异常---之复杂异常");
String formatMsg = FormatStringUtils.formatMessage(message, '{', '}');
this.complexMsg = MessageFormat.format(formatMsg, arguments);
this.code = code;
}
}
此时统一处理自定义异常的方法就变成了下面的样子:
/***
* 自定义异常 --- 自定义异常一般不要设置为ERROR级别,因为我们用自定义的异常主要是为了辅助我们处理业务逻辑,
* --- 它们其实并不能被真正当作异常来看待
* @param e
* @return
*/
@ResponseStatus(HttpStatus.IM_USED)
@ExceptionHandler(value = {ElegantRuntimeException.class})
@ResponseBody
public ResultVO elegantExceptionHandle(ElegantRuntimeException e) {
log.warn("【自定义异常】", e);
if (e.getComplexMsg() != null) {
return ResultVOUtil.error(e.getCode(), e.getComplexMsg());
}
return ResultVOUtil.error(e.getCode(), e.getMessage());
}