专栏名称: ImportNew
伯乐在线旗下账号,专注Java技术分享,包括Java基础技术、进阶技能、架构设计和Java技术领域动态等。
目录
相关文章推荐
芋道源码  ·  Spring Boot 中使用 JSON ... ·  15 小时前  
芋道源码  ·  SQLite这么小众的数据库,到底是什么人在用? ·  15 小时前  
Java编程精选  ·  阿里员工吐槽:80%以上的leader认为, ... ·  3 天前  
芋道源码  ·  抱歉,最近我劝各位真的别轻易离职...... ·  昨天  
51好读  ›  专栏  ›  ImportNew

干掉 try catch

ImportNew  · 公众号  · Java  · 2023-09-10 13:47

正文

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


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);
}
}
}

上面的 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 e.getMessage();
        }

        return message;
    }

    /**
     * 业务异常
     *
     * @param e 异常
     * @return 异常结果
     */

    @ExceptionHandler(value = BusinessException.class)
    @ResponseBody
    public ErrorResponse handleBusinessException(BaseException e
{
        log.error(e.getMessage(), e);

        return new ErrorResponse(e.getResponseEnum().getCode(), getMessage(e));
    }

    /**
     * 自定义异常
     *
     * @param e 异常
     * @return 异常结果
     */

    @ExceptionHandler(value = BaseException.class)
    @ResponseBody
    public ErrorResponse handleBaseException(BaseException e
{
        log.error(e.getMessage(), e);

        return new ErrorResponse(e.getResponseEnum().getCode(), getMessage(e));
    }

    /**
     * Controller上一层相关异常
     *
     * @param e 异常
     * @return 异常结果
     */

    @ExceptionHandler({
            NoHandlerFoundException.class,
            HttpRequestMethodNotSupportedException.class,
            HttpMediaTypeNotSupportedException.class,
            MissingPathVariableException.class,
            MissingServletRequestParameterException.class,
            TypeMismatchException.class






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