注: 限于篇幅问题,省略了一些代码实现、文档、一些次要的方法、以及一些衍生方法
背景
https://gitee.com/oschina/bullshit-codes/blob/master/java/BadException.java
- 异常不能复用!异常不能复用!异常不能复用!
- 其他问题都是毛毛雨啦
少说废话,看代码
- 先有个统一的响应体
@lombok.Data
@RequiredArgsConstructor(staticName = "of")
@SuppressWarnings({"WeakerAccess", "unused"})
public class ResultBody<T> {
@JsonIgnore
private final int httpStatusHint;
private final boolean success;
private final String code;
private final String message;
private final T data;
public static <T> ResultBody<T> success() { ... }
public static <T> ResultBody<T> success(T data) { ... }
public static <T> ResultBody<T> failed(String code, String message) { ... }
// 其他的 success(...) failed(...) 略 ...
@JsonIgnore
public boolean isFailed() {
return !isSuccess();
}
}
- 再有个统一的业务异常类
@lombok.Getter
@SuppressWarnings({"WeakerAccess", "unused"})
public class BusinessException extends RuntimeException {
protected final int statusHint;
protected final String code;
public BusinessException(String code, String message) { ... }
public BusinessException(int statusHint, String code, String message) { ... }
public BusinessException(String code, String message, Throwable cause) { ... }
public BusinessException(int statusHint, String code, String message, Throwable cause) {
// 需要的可以考虑关闭 writableStackTrace 提高性能
super(message, cause, true, true);
this.statusHint = statusHint;
this.code = code;
}
public <T> ResultBody<T> toResultBody() {
return ResultBody.failed(statusHint, code, getMessage());
}
// 其他方法略 ...
}
- 接下来, 关键点!
@SuppressWarnings({"unused"})
public interface IErrors {
// 为啥不是 `getName` 而是 `name` ? 别着急, 往后看
String name();
int getStatusHint();
default String format(String code, Object[] args) {
// Tip: 这里还可以处理国际化问题, 喜欢的话, 还可以用其他的占位符语法
return MessageFormat.format(code, args);
}
default <T> ResultBody<T> result(String pattern, Object... args) {
return ResultBody.failed(getStatusHint(), name(), format(pattern, args));
}
default BusinessException exception(String pattern, Object... args) {
return new BusinessException(getStatusHint(), name(), format(pattern, args));
}
default void whenNull(Object obj, String pattern, Object... args) {
if (obj == null) {
throw exception(pattern, args);
}
}
default void whenFalse(boolean expr, String pattern, Object... args) {
if (!expr) {
throw exception(pattern, args);
}
}
// 其他 whenXxx(...) 方法略
}
- 之后,随便定一个枚举,继承
IErrors
就可以了
@Getter
@RequiredArgsConstructor
public enum Errors implements IErrors {
BAD_REQUEST(400),
UNAUTHORIZED(401),
NOT_FOUND(404),
ILLEGAL_ARG(400),
NOT_SUPPORTED(500),
SYSTEM(500);
private final int statusHint;
}
是的,
Errors
是个枚举类,我们用枚举的 name 当做 Error Code 再合适不过了
- 舒服了, 舒服了!
// 我们可以直接返回 ResultBody<T> 实例
return Errors.ILLEGAL_ARG
.result("非法的参数:{0} = '{1}'", "name", name);
// 可以使用异常
if(bean == null){
throw Errors.NOT_FOUND
.exception("您所请求的资源不存在:id={0}", id);
}
// 还有更舒服的
Errors.NOT_FOUND
.whenNull(bean, "您所请求的资源不存在:id={0}", id);
Errors.BAD_REQUEST
.whenFalse(bean.isEnable(), "无法完成您的请求,因为该资源已被禁用:id={0}", id);
- 当然,如果就这么完了,你肯定会质疑
IErrors
这个接口的必要性,请看:
// 像这样,每个模块都可以有自己的 Errors
@Getter
@RequiredArgsConstructor
public enum MyErrors implements IErrors {
// 当然是根据自己的业务来定义啦
PASSWORD_EXPIRED(400),
ACCOUNT_DISABLED(400),
INVALID_TOKEN(401);
private final int statusHint;
}
是不是很方便?!
如果有任何意见或建议, 欢迎在评论区留言。”