全局异常处理:Assert+枚举+自定义异常

        业务开发过程中涉及大量的异常处理,通常采用@ControllerAdvice搭配@ExceptionHandler来处理各种异常,但是抛出异常的过程任然避免不了大量的if...else判断,刚好今天看到一篇文章(为什么不建议用try catch处理异常?)采用自定义Assert+枚举+自定义异常的方式优雅的解决了该问题,现整理实现思路:

 

全局异常处理:Assert+枚举+自定义异常_第1张图片

  1. 异常最重要的两个信息code和message,为了表示不同的业务异常信息,采用枚举的方式是个不错的选择。因为不同的业务异常有不同的划分方式,所以我们采用面向接口编程的思想,将IResponseEnum作为BaseException的组合元素之一。
    public interface IResponseEnum {
        int getCode();
        String getMessage();
    }
  2. 全局异常引入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;
        }
    }
  3. 定义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);
            }
        }
    }
  4. 定义BusinessExceptionAssert同时实现Assert判断逻辑和枚举类区分不同业务异常逻辑。
    /**
     * @ClassName: BusinessExceptionAssert
     * @Author: whp
     * @Description: 业务异常断言
     * @Date: 2022/3/4 11:33
     * @Version: 1.0
     */
    public interface BusinessExceptionAssert extends IResponseEnum,Assert {
        @Override
        default BaseException newException(Object ... orgs){
            String msg= MessageFormat.format(this.getMessage(),orgs);
            return new BusinessException(this,orgs,msg);
        }
        @Override
        default BaseException newException(Throwable t,Object... orgs){
            String msg=MessageFormat.format(this.getMessage(),orgs);
            return new BusinessException(this,orgs,msg,t);
        }
    }
    
  5. 使用@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的目的是什么?

答:可以封装将不必要的异常不在接口返回,容易追踪最开始问题点。

你可能感兴趣的:(设计模式,springboot,全局异常处理,Assert+枚举)