关于java:你这代码写得真丑满屏的trycatch全局异常处理不会吗

60次阅读

共计 17729 个字符,预计需要花费 45 分钟才能阅读完成。

背景

软件开发过程中,不可避免的是须要解决各种异样,就我本人来说,至多有一半以上的工夫都是在解决各种异常情况,所以代码中就会呈现大量的 try {…} catch {…} finally {…} 代码块,不仅有大量的冗余代码,而且还影响代码的可读性。比拟上面两张图,看看您当初编写的代码属于哪一种格调?而后哪种编码格调您更喜爱?

俊俏的 try catch 代码块

优雅的 Controller

下面的示例,还只是在 Controller 层,如果是在 Service 层,可能会有更多的 try catch 代码块。这将会重大影响代码的可读性、“好看性”。

所以如果是我的话,我必定偏差于第二种,我能够把更多的精力放在业务代码的开发,同时代码也会变得更加简洁。

既然业务代码不显式地对异样进行捕捉、解决,而异样必定还是解决的,不然零碎岂不是动不动就解体了,所以必须得有其余中央捕捉并解决这些异样。

那么问题来了,如何优雅的解决各种异样?

什么是对立异样解决

Spring 在 3.2 版本减少了一个注解 @ControllerAdvice,能够与 @ExceptionHandler、@InitBinder、@ModelAttribute 等注解注解配套应用,对于这几个注解的作用,这里不做过多赘述,若有不理解的,能够参考 Spring3.2 新注解 @ControllerAdvice,先大略有个理解。

不过跟异样解决相干的只有注解 @ExceptionHandler,从字面上看,就是 异样处理器 的意思,其理论作用也是:若在某个 Controller 类定义一个异样解决办法,并在办法上增加该注解,那么当呈现指定的异样时,会执行该解决异样的办法,其能够应用 springmvc 提供的数据绑定,比方注入 HttpServletRequest 等,还能够承受一个以后抛出的 Throwable 对象。

然而,这样一来,就必须在每一个 Controller 类都定义一套这样的异样解决办法,因为异样能够是各种各样。这样一来,就会造成大量的冗余代码,而且若须要新增一种异样的解决逻辑,就必须批改所有 Controller 类了,很不优雅。

当然你可能会说,那就定义个相似 BaseController 的基类,这样总行了吧。

这种做法尽管没错,但仍不尽如人意,因为这样的代码有肯定的侵入性和耦合性。简简单单的 Controller,我为啥非得继承这样一个类呢,万一曾经继承其余基类了呢。大家都晓得 Java 只能继承一个类。

那有没有一种计划,既不须要跟 Controller 耦合,也能够将定义的 异样处理器 利用到所有控制器呢?所以注解 @ControllerAdvice 呈现了,简略的说,该注解能够把异样处理器利用到所有控制器,而不是单个控制器。借助该注解,咱们能够实现:在独立的某个中央,比方独自一个类,定义一套对各种异样的解决机制,而后在类的签名加上注解 @ControllerAdvice,对立对 不同阶段的、不同异样 进行解决。这就是对立异样解决的原理。

留神到上面对异样按阶段进行分类,大体能够分成:进入 Controller 前的异样 和 Service 层异样,具体能够参考下图:

不同阶段的异样

指标

毁灭 95% 以上的 try catch 代码块,以优雅的 Assert(断言) 形式来校验业务的异常情况,只关注业务逻辑,而不必破费大量精力写冗余的 try catch 代码块。

对立异样解决实战

在定义对立异样解决类之前,先来介绍一下如何优雅的断定异常情况并抛异样。

用 Assert(断言) 替换 throw exception

想必 Assert(断言) 大家都很相熟,比方 Spring 家族的 org.springframework.util.Assert,在咱们写测试用例的时候常常会用到,应用断言能让咱们编码的时候有一种非个别丝滑的感觉,比方:

 @Test
    public void test1() {
        ...
        User user = userDao.selectById(userId);
        Assert.notNull(user, "用户不存在.");
        ...
    }

    @Test
    public void test2() {
        // 另一种写法
        User user = userDao.selectById(userId);
        if (user == null) {throw new IllegalArgumentException("用户不存在.");
        }
    } 

有没有感觉第一种断定非空的写法很优雅,第二种写法令是绝对俊俏的 if {…} 代码块。那么神奇的 Assert.notNull() 背地到底做了什么呢?上面是 Assert 的局部源码:

public abstract class Assert {public Assert() { }

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

    /**
     * <p> 断言对象 <code>obj</code> 非空。如果对象 <code>obj</code> 为空,则抛出异样
     *
     * @param obj 待判断对象
     */
    default void assertNotNull(Object obj) {if (obj == null) {throw newException(obj);
        }
    }

    /**
     * <p> 断言对象 <code>obj</code> 非空。如果对象 <code>obj</code> 为空,则抛出异样
     * <p> 异样信息 <code>message</code> 反对传递参数形式,防止在判断之前进行字符串拼接操作
     *
     * @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();}
/**
 * <p> 业务异样 </p>
 * <p> 业务解决时,出现异常,能够抛出该异样 </p>
 */
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 IResponseEnum, Assert {

    @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,
            HttpMessageNotReadableException.class, 
            HttpMessageNotWritableException.class,
            // BindException.class,
            // MethodArgumentNotValidException.class
            HttpMediaTypeNotAcceptableException.class,
            ServletRequestBindingException.class,
            ConversionNotSupportedException.class,
            MissingServletRequestPartException.class,
            AsyncRequestTimeoutException.class
    })
    @ResponseBody
    public ErrorResponse handleServletException(Exception e) {log.error(e.getMessage(), e);
        int code = CommonResponseEnum.SERVER_ERROR.getCode();
        try {ServletResponseEnum servletExceptionEnum = ServletResponseEnum.valueOf(e.getClass().getSimpleName());
            code = servletExceptionEnum.getCode();} catch (IllegalArgumentException e1) {log.error("class [{}] not defined in enum {}", e.getClass().getName(), ServletResponseEnum.class.getName());
        }

        if (ENV_PROD.equals(profile)) {
            // 当为生产环境, 不适宜把具体的异样信息展现给用户, 比方 404.
            code = CommonResponseEnum.SERVER_ERROR.getCode();
            BaseException baseException = new BaseException(CommonResponseEnum.SERVER_ERROR);
            String message = getMessage(baseException);
            return new ErrorResponse(code, message);
        }

        return new ErrorResponse(code, e.getMessage());
    }


    /**
     * 参数绑定异样
     *
     * @param e 异样
     * @return 异样后果
     */
    @ExceptionHandler(value = BindException.class)
    @ResponseBody
    public ErrorResponse handleBindException(BindException e) {log.error("参数绑定校验异样", e);

        return wrapperBindingResult(e.getBindingResult());
    }

    /**
     * 参数校验异样,将校验失败的所有异样组合成一条错误信息
     *
     * @param e 异样
     * @return 异样后果
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    @ResponseBody
    public ErrorResponse handleValidException(MethodArgumentNotValidException e) {log.error("参数绑定校验异样", e);

        return wrapperBindingResult(e.getBindingResult());
    }

    /**
     * 包装绑定异样后果
     *
     * @param bindingResult 绑定后果
     * @return 异样后果
     */
    private ErrorResponse wrapperBindingResult(BindingResult bindingResult) {StringBuilder msg = new StringBuilder();

        for (ObjectError error : bindingResult.getAllErrors()) {msg.append(",");
            if (error instanceof FieldError) {msg.append(((FieldError) error).getField()).append(":");
            }
            msg.append(error.getDefaultMessage() == null ? "" : error.getDefaultMessage());

        }

        return new ErrorResponse(ArgumentResponseEnum.VALID_ERROR.getCode(), msg.substring(2));
    }

    /**
     * 未定义异样
     *
     * @param e 异样
     * @return 异样后果
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ErrorResponse handleException(Exception e) {log.error(e.getMessage(), e);

        if (ENV_PROD.equals(profile)) {
            // 当为生产环境, 不适宜把具体的异样信息展现给用户, 比方数据库异样信息.
            int code = CommonResponseEnum.SERVER_ERROR.getCode();
            BaseException baseException = new BaseException(CommonResponseEnum.SERVER_ERROR);
            String message = getMessage(baseException);
            return new ErrorResponse(code, message);
        }

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

能够看到,下面将异样分成几类,实际上只有两大类,一类是 ServletException、ServiceException,还记得上文提到的 按阶段分类 吗,即对应 进入 Controller 前的异样 和 Service 层异样;而后 ServiceException 再分成自定义异样、未知异样。对应关系如下:

  • 进入 Controller 前的异样: handleServletException、handleBindException、handleValidException
  • 自定义异样: handleBusinessException、handleBaseException
  • 未知异样: handleException

接下来别离对这几种异样处理器做具体阐明。

异样处理器阐明

handleServletException

一个 http 申请,在达到 Controller 前,会对该申请的申请信息与指标控制器信息做一系列校验。这里简略说一下:

  • NoHandlerFoundException:首先依据申请 Url 查找有没有对应的控制器,若没有则会抛该异样,也就是大家十分相熟的 404 异样;
  • HttpRequestMethodNotSupportedException:若匹配到了(匹配后果是一个列表,不同的是 http 办法不同,如:Get、Post 等),则尝试将申请的 http 办法与列表的控制器做匹配,若没有对应 http 办法的控制器,则抛该异样;
  • HttpMediaTypeNotSupportedException:而后再对申请头与控制器反对的做比拟,比方 content-type 申请头,若控制器的参数签名蕴含注解 @RequestBody,然而申请的 content-type 申请头的值没有蕴含 application/json,那么会抛该异样(当然,不止这种状况会抛这个异样);
  • MissingPathVariableException:未检测到门路参数。比方 url 为:/licence/{licenceId},参数签名蕴含 @PathVariable(“licenceId”),当申请的 url 为 /licence,在没有明确定义 url 为 /licence 的状况下,会被断定为:短少门路参数;
  • MissingServletRequestParameterException:短少申请参数。比方定义了参数 @RequestParam(“licenceId”) String licenceId,但发动申请时,未携带该参数,则会抛该异样;
  • TypeMismatchException: 参数类型匹配失败。比方:接管参数为 Long 型,但传入的值确是一个字符串,那么将会呈现类型转换失败的状况,这时会抛该异样;
  • HttpMessageNotReadableException:与下面的 HttpMediaTypeNotSupportedException 举的例子齐全相同,即申请头携带了 ”content-type: application/json;charset=UTF-8″,但接管参数却没有增加注解 @RequestBody,或者申请体携带的 json 串反序列化成 pojo 的过程中失败了,也会抛该异样;
  • HttpMessageNotWritableException:返回的 pojo 在序列化成 json 过程失败了,那么抛该异样;
  • HttpMediaTypeNotAcceptableException:未知;
  • ServletRequestBindingException:未知;
  • ConversionNotSupportedException:未知;
  • MissingServletRequestPartException:未知;
  • AsyncRequestTimeoutException:未知;

handleBindException

参数校验异样,后文具体阐明。

handleValidException

参数校验异样,后文具体阐明。

handleBusinessException、handleBaseException

解决自定义的业务异样,只是 handleBaseException 解决的是除了 BusinessException 意外的所有业务异样。就目前来看,这 2 个是能够合并成一个的。

handleException

解决所有未知的异样,比方操作数据库失败的异样。

注:下面的 handleServletException、handleException 这两个处理器,返回的异样信息,不同环境返回的可能不一样,认为这些异样信息都是框架自带的异样信息,个别都是英文的,不太好间接展现给用户看,所以对立返回 SERVER_ERROR 代表的异样信息。

异于常人的 404

上文提到,当申请没有匹配到控制器的状况下,会抛出 NoHandlerFoundException 异样,但其实默认状况下不是这样,默认状况下会呈现相似如下页面:

Whitelabel Error Page

这个页面是如何呈现的呢?实际上,当呈现 404 的时候,默认是不抛异样的,而是 forward 跳转到 /error 控制器,spring 也提供了默认的 error 控制器,如下:

BasicErrorController

那么,如何让 404 也抛出异样呢,只需在 properties 文件中退出如下配置即可:

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

如此,就能够异样处理器中捕捉它了,而后前端只有捕捉到特定的状态码,立刻跳转到 404 页面即可

捕捉 404 对应的异样

对立返回后果

在验证对立异样处理器之前,顺便说一下对立返回后果。说白了,其实是对立一下返回后果的数据结构。code、message 是所有返回后果中必有的字段,而当须要返回数据时,则须要另一个字段 data 来示意。

所以首先定义一个 BaseResponse 来作为所有返回后果的基类;

而后定义一个通用返回后果类 CommonResponse,继承 BaseResponse,而且多了字段 data;

为了辨别胜利和失败返回后果,于是再定义一个 ErrorResponse;

最初还有一种常见的返回后果,即返回的数据带有分页信息,因为这种接口比拟常见,所以有必要独自定义一个返回后果类 QueryDataResponse,该类继承自 CommonResponse,只是把 data 字段的类型限度为 QueryDdata,QueryDdata 中定义了分页信息相应的字段,即 totalCount、pageNo、pageSize、records。

其中比拟罕用的只有 CommonResponse 和 QueryDataResponse,然而名字又贼鬼死长,何不定义 2 个名字超简略的类来代替呢?于是 R 和 QR 诞生了,当前返回后果的时候只需这样写:new R<>(data)、new QR<>(queryData)。

所有的返回后果类的定义这里就不贴出来了

验证对立异样解决

因为这一套对立异样解决能够说是通用的,所有能够设计成一个 common 包,当前每一个新我的项目 / 模块只需引入该包即可。所以为了验证,须要新建一个我的项目,并引入该 common 包。我的项目构造如下:

我的项目构造
当前只需这样引入即可

引入 common 包

次要代码

上面是用于验证的次要源码:

@Service
public class LicenceService extends ServiceImpl<LicenceMapper, Licence> {

    @Autowired
    private OrganizationClient organizationClient;

    /**
     * 查问{@link Licence} 详情
     * @param licenceId
     * @return
     */
    public LicenceDTO queryDetail(Long licenceId) {Licence licence = this.getById(licenceId);
        checkNotNull(licence);

        OrganizationDTO org = ClientUtil.execute(() -> organizationClient.getOrganization(licence.getOrganizationId()));
        return toLicenceDTO(licence, org);
    }

    /**
     * 分页获取
     * @param licenceParam 分页查问参数
     * @return
     */
    public QueryData<SimpleLicenceDTO> getLicences(LicenceParam licenceParam) {String licenceType = licenceParam.getLicenceType();
        LicenceTypeEnum licenceTypeEnum = LicenceTypeEnum.parseOfNullable(licenceType);
        // 断言, 非空
        ResponseEnum.BAD_LICENCE_TYPE.assertNotNull(licenceTypeEnum);

        LambdaQueryWrapper<Licence> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Licence::getLicenceType, licenceType);
        IPage<Licence> page = this.page(new QueryPage<>(licenceParam), wrapper);
        return new QueryData<>(page, this::toSimpleLicenceDTO);
    }

    /**
     * 新增{@link Licence}
     * @param request 申请体
     * @return
     */
    @Transactional(rollbackFor = Throwable.class)
    public LicenceAddRespData addLicence(LicenceAddRequest request) {Licence licence = new Licence();
        licence.setOrganizationId(request.getOrganizationId());
        licence.setLicenceType(request.getLicenceType());
        licence.setProductName(request.getProductName());
        licence.setLicenceMax(request.getLicenceMax());
        licence.setLicenceAllocated(request.getLicenceAllocated());
        licence.setComment(request.getComment());
        this.save(licence);

        return new LicenceAddRespData(licence.getLicenceId());
    }

    /**
     * entity -> simple dto
     * @param licence {@link Licence} entity
     * @return {@link SimpleLicenceDTO}
     */
    private SimpleLicenceDTO toSimpleLicenceDTO(Licence licence) {// 省略}

    /**
     * entity -> dto
     * @param licence {@link Licence} entity
     * @param org {@link OrganizationDTO}
     * @return {@link LicenceDTO}
     */
    private LicenceDTO toLicenceDTO(Licence licence, OrganizationDTO org) {// 省略}

    /**
     * 校验 {@link Licence} 存在
     * @param licence
     */
    private void checkNotNull(Licence licence) {ResponseEnum.LICENCE_NOT_FOUND.assertNotNull(licence);
    }

} 

ps: 这里应用的 DAO 框架是 mybatis-plus。
启动时,主动插入的数据为:

-- licence
INSERT INTO licence (licence_id,  organization_id, licence_type, product_name, licence_max, licence_allocated)
VALUES (1, 1, 'user','CustomerPro', 100,5);
INSERT INTO licence (licence_id,  organization_id, licence_type, product_name, licence_max, licence_allocated)
VALUES (2, 1, 'user','suitability-plus', 200,189);
INSERT INTO licence (licence_id,  organization_id, licence_type, product_name, licence_max, licence_allocated)
VALUES (3, 2, 'user','HR-PowerSuite', 100,4);
INSERT INTO licence (licence_id,  organization_id, licence_type, product_name, licence_max, licence_allocated)
VALUES (4, 2, 'core-prod','WildCat Application Gateway', 16,16);

-- organizations
INSERT INTO organization (id, name, contact_name, contact_email, contact_phone)
VALUES (1, 'customer-crm-co', 'Mark Balster', 'mark.balster@custcrmco.com', '823-555-1212');
INSERT INTO organization (id, name, contact_name, contact_email, contact_phone)
VALUES (2, 'HR-PowerSuite', 'Doug Drewry','doug.drewry@hr.com', '920-555-1212'); 

开始验证

捕捉自定义异样

获取不存在的 licence 详情:http://localhost:10000/licence/5。胜利响应的申请:licenceId=1

校验非空

捕捉 Licence not found 异样

Licence not found

2. 依据不存在的 licence type 获取 licence 列表:http://localhost:10000/licenc…。可选的 licence type 为:user、core-prod。

校验非空

捕捉 Bad licence type 异样

Bad licence type

捕捉进入 Controller 前的异样

拜访不存在的接口:http://localhost:10000/licenc…

捕捉 404 异样

http 办法不反对:http://localhost:10000/licence

PostMapping

捕捉 Request method not supported 异样

Request method not supported

3 校验异样 1:http://localhost:10000/licenc…

getLicences

LicenceParam

捕捉参数绑定校验异样

licence type cannot be empty

4. 校验异样 2:post 申请,这里应用 postman 模仿。

addLicence

LicenceAddRequest

申请 url 即后果

捕捉参数绑定校验异样

注:因为参数绑定校验异样的异样信息的获取形式与其它异样不一样,所以才把这 2 种状况的异样从 进入 Controller 前的异样 独自拆出来,上面是异样信息的收集逻辑:

异样信息的收集

捕捉未知异样

假如咱们当初轻易对 Licence 新增一个字段 test,但不批改数据库表构造,而后拜访:http://localhost:10000/licence/1。

减少 test 字段

捕捉数据库异样

Error querying database

小结

能够看到,测试的异样都可能被捕捉,而后以 code、message 的模式返回。每一个我的项目 / 模块,在定义业务异样的时候,只需定义一个枚举类,而后实现接口 BusinessExceptionAssert,最初为每一种业务异样定义对应的枚举实例即可,而不必定义许多异样类。应用的时候也很不便,用法相似断言。

扩大

在生产环境,若捕捉到 未知异样 或者 ServletException,因为都是一长串的异样信息,若间接展现给用户看,显得不够业余,于是,咱们能够这样做:当检测到以后环境是生产环境,那么间接返回“网络异样”。

生产环境返回“网络异样”

能够通过以下形式批改以后环境:

批改以后环境为生产环境

总结

应用 断言 和 枚举类 相结合的形式,再配合对立异样解决,根本大部分的异样都可能被捕捉。为什么说大部分异样,因为当引入 spring cloud security 后,还会有认证 / 受权异样,网关的服务降级异样、跨模块调用异样、近程调用第三方服务异样等,这些异样的捕捉形式与本文介绍的不太一样,不过限于篇幅,这里不做具体阐明,当前会有独自的文章介绍。

另外,当须要思考国际化的时候,捕捉异样后的异样信息个别不能间接返回,须要转换成对应的语言,不过本文已思考到了这个,获取音讯的时候曾经做了国际化映射,逻辑如下:

获取国际化音讯

最初总结,全局异样属于老成长谈的话题,心愿这次通过手机的我的项目对大家有点指导性的学习。大家依据理论状况自行批改。

也能够采纳以下的 jsonResult 对象的形式进行解决,也贴出来代码.

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 没有登录
     * @param request
     * @param response
     * @param e
     * @return
     */
    @ExceptionHandler(NoLoginException.class)
    public Object noLoginExceptionHandler(HttpServletRequest request,HttpServletResponse response,Exception e)
{log.error("[GlobalExceptionHandler][noLoginExceptionHandler] exception",e);
        JsonResult jsonResult = new JsonResult();
        jsonResult.setCode(JsonResultCode.NO_LOGIN);
        jsonResult.setMessage("用户登录生效或者登录超时, 请先登录");
        return jsonResult;
    }

    /**
     *  业务异样
     * @param request
     * @param response
     * @param e
     * @return
     */
    @ExceptionHandler(ServiceException.class)
    public Object businessExceptionHandler(HttpServletRequest request,HttpServletResponse response,Exception e)
{log.error("[GlobalExceptionHandler][businessExceptionHandler] exception",e);
        JsonResult jsonResult = new JsonResult();
        jsonResult.setCode(JsonResultCode.FAILURE);
        jsonResult.setMessage("业务异样, 请分割管理员");
        return jsonResult;
    }

    /**
     * 全局异样解决
     * @param request
     * @param response
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public Object exceptionHandler(HttpServletRequest request,HttpServletResponse response,Exception e)
{log.error("[GlobalExceptionHandler][exceptionHandler] exception",e);
        JsonResult jsonResult = new JsonResult();
        jsonResult.setCode(JsonResultCode.FAILURE);
        jsonResult.setMessage("零碎谬误, 请分割管理员");
        return jsonResult;
    }
} 

起源:https://www.cnblogs.com/juren…

正文完
 0