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

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

 

  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的目的是什么?

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

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring AssertSpring 框架提供的一个断言工具类,用于在程序运行中进行断言判断,如果断言条件不满足,则会抛出异常。但是 Spring Assert 只提供了一些基本的断言方法,如果我们需要进行更加个性化的断言判断,可以通过自定义异常来实现。 自定义异常可以继承自 Spring 的 AbstractThrowableAssert 类,并重写其 fail 方法。在自定义异常中,我们可以根据需求定义不同的异常类型,例如业务异常、参数异常等,并在 fail 方法中指定不同的异常信息。 下面以一个示例来说明如何使用和自定义 Spring Assert 异常: ```java public class CustomAssert extends AbstractThrowableAssert<CustomAssert, Object> { private CustomAssert(Object actual, Class<?> selfType) { super(actual, selfType); } public static CustomAssert assertThat(Object actual) { return new CustomAssert(actual, CustomAssert.class); } public CustomAssert isPositiveNumber() { isNotNull(); if (!(actual instanceof Number) || ((Number) actual).doubleValue() <= 0) { failWithMessage("Expected positive number, but found: %s", actual); } return this; } } ``` 在自定义的 CustomAssert 类中,我们通过继承 AbstractThrowableAssert 类,并实现自己的断言方法 isPositiveNumber。在该方法中,我们首先调用 isNotNull 方法来判断输入值是否为空,然后再进行我们的特定判断,如果判断不成立,就通过 failWithMessage 方法抛出异常。 通过自定义异常,我们可以根据业务需求实现更加具体化的断言功能,并在断言失败时提供更加详细的异常信息。在使用自定义断言时,只需要调用 assertThat 方法来创建 CustomAssert 对象,并链式调用不同的断言方法即可。 总的来说,Spring Assert自定义异常能够帮助我们实现更加灵活和个性化的断言判断,提高代码的可读性和可维护性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值