专栏名称: ImportNew
伯乐在线旗下账号,专注Java技术分享,包括Java基础技术、进阶技能、架构设计和Java技术领域动态等。
目录
相关文章推荐
Java编程精选  ·  Controller层代码这么写,简洁又优雅! ·  11 小时前  
Java编程精选  ·  字节员工自曝:在强调一遍OD ... ·  昨天  
芋道源码  ·  再见了SpringBoot,后端AI已成气候! ·  昨天  
51好读  ›  专栏  ›  ImportNew

统一异常处理介绍及实战

ImportNew  · 公众号  · Java  · 2023-08-02 17:40

正文

请到「今天看啥」查看全文


static void notNull ( @Nullable Object object , String message ) {
if ( object == null ) {
throw new IllegalArgumentException(message);
}
}
}

可以看到, Assert 其实就是帮我们把 if {...} 封装了一下,是不是很神奇。虽然很简单,但不可否认的是编码体验至少提升了一个档次。那么我们能不能模仿 org.springframework.util.Assert ,也写一个断言类,不过断言失败后抛出的异常不是 IllegalArgumentException 这些内置异常,而是我们自己定义的异常。下面让我们来尝试一下。

Assert

public interface Assert {
    /**
     * 创建异常
     * @param args
     * @return
     */

    BaseException newException(Object... args);

    /**
     * 创建异常
     * @param t
     * @param args
     * @return
     */

    BaseException newException(Throwable t, Object... args);

    /**
     * 

断言对象obj非空。如果对象obj为空,则抛出异常
     *
     * @param obj 待判断对象
     */


    default void assertNotNull(Object obj) {
        if (obj == null) {
            throw newException(obj);
        }
    }

    /**
     * 

断言对象obj非空。如果对象obj为空,则抛出异常
     * 

异常信息message支持传递参数方式,避免在判断之前进行字符串拼接操作
     *
     * @param obj 待判断对象
     * @param args message占位符对应的参数列表
     */


    default void assertNotNull(Object obj, Object... args) {
        if (obj == null) {
            throw newException(args);
        }
    }
}

注:

  1. 这里只给出 Assert 接口的部分源码,更多断言方法请参考源码。
  2. BaseException 是所有自定义异常的基类。
  3. 在接口中定义默认方法是Java8的新语法。

上面的 Assert 断言方法是使用接口的默认方法定义的,然后有没有发现当断言失败后,抛出的异常不是具体的某个异常,而是交由2个 newException 接口方法提供。因为业务逻辑中出现的异常基本都是对应特定的场景,比如根据用户id获取用户信息,查询结果为 null ,此时抛出的异常可能为 UserNotFoundException ,并且有特定的异常码(比如7001)和异常信息“用户不存在”。所以具体抛出什么异常,有 Assert 的实现类决定。

看到这里,您可能会有这样的疑问,按照上面的说法,那岂不是有多少异常情况,就得有定义等量的断言类和异常类,这显然是反人类的,这也没想象中高明嘛。别急,且听我细细道来。

善解人意的Enum

自定义异常 BaseException 有2个属性,即 code message ,这样一对属性,有没有想到什么类一般也会定义这2个属性?没错,就是枚举类。且看我如何将 Enum Assert 结合起来,相信我一定会让你眼前一亮。如下:

public interface IResponseEnum {
    int getCode();
    String getMessage();
}
/**
 * 

业务异常


 * 

业务处理时,出现异常,可以抛出该异常


 */

public class BusinessException extends  BaseException {

    private static final long serialVersionUID = 1L;

    public  BusinessException(IResponseEnum responseEnum, Object[] args, String message) {
        super(responseEnum, args, message);
    }

    public BusinessException(IResponseEnum responseEnum, Object[] args, String message, Throwable cause) {
        super(responseEnum, args, message, cause);
    }
}
public interface BusinessExceptionAssert extends IResponseEnumAssert {

    @Override
    default BaseException newException(Object... args) {
        String msg = MessageFormat.format(this.getMessage(), args);

        return new BusinessException(this, args, msg);
    }

    @Override
    default BaseException newException(Throwable t, Object... args) {
        String msg = MessageFormat.format(this.getMessage(), args);

        return new BusinessException(this, args, msg, t);
    }

}
@Getter
@AllArgsConstructor
public enum ResponseEnum implements BusinessExceptionAssert {

    /**
     * Bad licence type
     */

    BAD_LICENCE_TYPE(7001"Bad licence type."),
    /**
     * Licence not found
     */

    LICENCE_NOT_FOUND(7002"Licence not found.")
    ;

    /**
     * 返回码
     */

    private int code;
    /**
     * 返回消息
     */

    private String message;
}

看到这里,有没有眼前一亮的感觉,代码示例中定义了两个枚举实例: BAD_LICENCE_TYPE LICENCE_NOT_FOUND ,分别对应了 BadLicenceTypeException LicenceNotFoundException 两种异常。以后每增加一种异常情况,只需增加一个枚举实例即可,再也不用每一种异常都定义一个异常类了。然后再来看下如何使用,假设 LicenceService 有校验 Licence 是否存在的方法,如下:

    /**
     * 校验{@link Licence}存在
     * @param licence
     */

    private void checkNotNull(Licence licence) {
        ResponseEnum.LICENCE_NOT_FOUND.assertNotNull(licence);
    }

若不使用断言,代码可能如下:

    private void checkNotNull(Licence licence) {
        if (licence == null) {
            throw new LicenceNotFoundException();
            // 或者这样
            throw new BusinessException(7001"Bad licence type.");
        }
    }

使用枚举类结合(继承) Assert ,只需根据特定的异常情况定义不同的枚举实例,如上面的 BAD_LICENCE_TYPE LICENCE_NOT_FOUND ,就能够针对不同情况抛出特定的异常(这里指携带特定的异常码和异常消息),这样既不用定义大量的异常类,同时还具备了断言的良好可读性,当然这种方案的好处远不止这些,请继续阅读后文,慢慢体会。

注:上面举的例子是针对特定的业务,而有部分异常情况是通用的,比如:服务器繁忙、网络异常、服务器异常、参数校验异常、404等,所以有 CommonResponseEnum ArgumentResponseEnum ServletResponseEnum ,其中 ServletResponseEnum 会在后文详细说明。

二、定义统一异常处理器类

@Slf4j
@Component
@ControllerAdvice
@ConditionalOnWebApplication
@ConditionalOnMissingBean(UnifiedExceptionHandler.class)
public class UnifiedExceptionHandler 
{
    /**
     * 生产环境
     */

    private final static String ENV_PROD = "prod";

    @Autowired
    private UnifiedMessageSource unifiedMessageSource;

    /**
     * 当前环境
     */

    @Value("${spring.profiles.active}")
    private String profile;

    /**
     * 获取国际化消息
     *
     * @param e 异常
     * @return
     */

    public String getMessage(BaseException e) {
        String code = "response." + e.getResponseEnum().toString();
        String message = unifiedMessageSource.getMessage(code, e.getArgs());

        if (message == null || message.isEmpty()) {
            return






请到「今天看啥」查看全文