? ? ? ? 业务开发过程中涉及大量的异常处理,通常采用@ControllerAdvice搭配@ExceptionHandler来处理各种异常,但是抛出异常的过程任然避免不了大量的if...else判断,刚好今天看到一篇文章(为什么不建议用try catch处理异常?)采用自定义Assert+枚举+自定义异常的方式优雅的解决了该问题,现整理实现思路:
- 异常最重要的两个信息code和message,为了表示不同的业务异常信息,采用枚举的方式是个不错的选择。因为不同的业务异常有不同的划分方式,所以我们采用面向接口编程的思想,将IResponseEnum作为BaseException的组合元素之一。
public interface IResponseEnum {
int getCode();
String getMessage();
} - 全局异常引入IResponseEnum作为自己的属性,保证抛出的异常信息全部为服务自己定义。
/**
* @ClassName: BaseException
* @Author: whp
* @Description: 基础异常类
* @Date: 2022/3/4 11:20
* @Version: 1.0
*/
@Getter
public class BaseException extends RuntimeException{
protected IResponseEnum responseEnum;
protected Object[] args;
public BaseException(IResponseEnum responseEnum){
super(responseEnum.getMessage());
this.responseEnum=responseEnum;
}
public BaseException(int code,String msg){
super(msg);
this.responseEnum=new IResponseEnum() {
@Override
public int getCode() {
return code;
}
@Override
public String getMessage() {
return msg;
}
};
}
public BaseException(IResponseEnum responseEnum,Object[] args,String message){
super(message);
this.responseEnum=responseEnum;
this.args=args;
}
public BaseException(IResponseEnum responseEnum,Object[] args,String message,Throwable cause){
super(message,cause);
this.responseEnum=responseEnum;
this.args=args;
}
} - 定义Assert,一共有两个作用:1>添加判断逻辑,例如:判断对象是否为空,AssertTrue判断是否为真等;2> 抛出自定义异常。
/**
* @ClassName: Assert
* @Author: whp
* @Description: 自定义断言
* @Date: 2022/3/4 11:25
* @Version: 1.0
*/
public interface Assert {
BaseException newException(Object ... args);
BaseException newException(Throwable t,Object... args);
default void assertNotNull(Object obj){
if(obj==null){
throw newException(obj);
}
}
default void assertNotNull(Object obj,Object... args){
if(obj==null){
throw newException(args);
}
}
} - 定义BusinessExceptionAssert同时实现Assert判断逻辑和枚举类区分不同业务异常逻辑。
/**
* @ClassName: Assert
* @Author: whp
* @Description: 自定义断言
* @Date: 2022/3/4 11:25
* @Version: 1.0
*/
public interface Assert {
BaseException newException(Object ... args);
BaseException newException(Throwable t,Object... args);
default void assertNotNull(Object obj){
if(obj==null){
throw newException(obj);
}
}
default void assertNotNull(Object obj,Object... args){
if(obj==null){
throw newException(args);
}
}
}
- 使用@Controlleradvice统一处理抛出的异常类:
*/
@ControllerAdvice
@ResponseBody
@Slf4j
public class ExceptionHandlerAdvice {
private static final String ENV_PROD="online";
@Value("${spring.profiles.active}")
private String profile;
@ExceptionHandler(value = BaseException.class)
public Response commonException(BaseException exception, WebRequest request){
return new Response().failure().code(exception.getResponseEnum().getCode()).message(exception.getMessage());
}
/**
* Controller上一层相关异常
*
* @param e 异常
* @return 异常结果
*/
@ExceptionHandler({
NoHandlerFoundException.class,
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
HttpMediaTypeNotAcceptableException.class,
ServletRequestBindingException.class,
ConversionNotSupportedException.class,
MissingServletRequestPartException.class,
AsyncRequestTimeoutException.class
})
@ResponseBody
public Response handleServletException(Exception e) {
log.error(e.getMessage(), e);
int code = ServletResponseEnum.SEVER_EXCEPTION.getCode();
try {
ServletResponseEnum servletExceptionEnum = ServletResponseEnum.valueOf(e.getClass().getSimpleName());
return new Response().failure().code(servletExceptionEnum.getCode()).message(servletExceptionEnum.getMessage());
} catch (IllegalArgumentException e1) {
log.error("class [{}] not defined in enum {}", e.getClass().getName(), ServletResponseEnum.class.getName());
}
return new Response().failure().code(code).message(ServletResponseEnum.SEVER_EXCEPTION.getMessage());
}
} ? ? ? ?当然,全局异常的应用也要按照不同的服务进行区分,例如在API层异常结果是要返回给用户的,需要将异常信息展示为对用户友好的语言。服务间调用则需要让对应开发能够快速明确问题。相关代码实现可以参考:GitHub - whpHarper/spring-boot-study: 企业级spring boot框架
问题思考:
1. 全局异常可以分为业务到达接口前异常(例如:NoHandlerFoundException状态码对应404,HttpRequestMethodNotSupportedException状态码对应405)、参数校验异常、自定义业务异常等,各类异常如何返回客户端或对应调用方,需要合理规划下。
2. 业务异常定义构造函数携带参数throwable的目的是什么?
答:可以封装将不必要的异常不在接口返回,容易追踪最开始问题点。
|