How to gracefully handle exceptions | JD Cloud technical team

Author: Jingdong Retail Qin Haoran

1. What is an exception

According to the severity of the error, the Java language derives two factions, Error and Exception, from the throwale root class.

Error:

A hardware or operating system error encountered during program execution. Errors are fatal to the program and will prevent the program from running. Common errors include memory overflow, abnormal operation of the jvm virtual machine itself, and no main method in the calss file. Programs are inherently incapable of handling errors and must rely on external intervention. Error is an internal error of the system, which is thrown by jvm and handed over to the system for processing.

Exception:

During the normal operation of the program, unexpected situations can be expected. For example, the database connection is interrupted, the null pointer, and the array subscript is out of bounds. The occurrence of an exception can cause the program to terminate abnormally, or it can be detected in advance, caught and disposed of, so that the program can continue to run. Exception (exception) is divided into compilation exception (checked exception) and runtime exception (unchecked exception) according to its nature.

◦ Compilation exception:

Also called checkable exceptions, usually caused by grammatical errors and environmental factors (external resources). Such as input and output abnormal IOException, database operation SQLException. Its characteristic is that the Java language mandates that all non-runtime exceptions be caught and handled. Strengthen the robustness and security of the program through behavioral norms.

◦ Runtime exception:

Also called unchecked exception RuntimeException, these exceptions are generally caused by program logic errors, that is, semantic errors. Such as arithmetic exceptions, null pointer exceptions NullPointerException, subscript out of bounds IndexOutOfBoundsException. Runtime exceptions should be exposed during program testing and debugged by the programmer without being caught.

Second, the way of handling exceptions

In the code, the most common way we handle exceptions is: try-catch

        try {
            // 业务逻辑
            
        } catch (Exception e) {
            // 捕获到异常的逻辑
        }

Or further distinguish the exception type:

        try {
            // 业务逻辑
            
        } catch (IOException ie) {
            // 捕获到IO异常的逻辑
            
        } catch (Exception e) {
            // 捕获到其他异常的逻辑
        }

3. How to throw an exception

We can usually control the code flow by throwing an exception, and then uniformly catch the exception at the gateway to return the error code. This can simplify the code flow control to a certain extent, as follows:

    @Override
    public UserVO queryUser(Long id) {
        UserDO userDO = userMapper.queryUserById(id);
        if (Objects.isNull(userDO)) {
            throw new RuntimeException("用户不存在");    //用户不存在抛出异常
        }
        return userDO.toVo();
    }  

Although the above method of throwing an exception simplifies the code flow, there is no way to subdivide the specific error type when there are multiple error scenarios. Such as: the error that the user does not exist, the error that the user does not have permission;

As smart as you are, you must have thought of custom exceptions, as follows:

    @Override
    public UserVO queryUser(Long id) {
        UserDO userDO = userMapper.queryUserById(id);
        if (Objects.isNull(userDO)) {
            throw new UserNotFoundException();    //用户不存在抛出对应异常
        }
        if(!checkLicence(userDO)) {
            throw new BadLicenceException();    //用户无权限抛出对应异常
        }
        return userDO.toVo();
    }

Indeed, custom exceptions can solve the problem of wrong scene segmentation. Furthermore, we can customize exceptions for different stages of the system process and different business types, but this requires customizing a large number of exceptions;

4. How to gracefully throw an exception

The above method can distinguish error scenarios, but there are still some shortcomings. Such as: poor readability, need to define a large number of custom exceptions;

Then we will optimize the above problem below;

Use assertions to increase code readability;

    @Override
    public UserVO queryUser(Long id) {
        UserDO userDO = userMapper.queryUserById(id);
        Assert.notNull(userDO, "用户不存在");    //用断言进行参数的非空校验
        return userDO.toVo();
    }

Although the assertion code is concise and readable, it lacks the ability to clearly distinguish error scenarios like the above-mentioned custom exceptions, which leads to our ultimate solution: custom assertions;

custom assertion;

We use the custom assertion method to combine the advantages of the above custom exceptions and assertions. After the assertion fails, we throw the exception we have formulated. code show as below:

• Custom exception base class

@Getter
@Setter
public class BaseException extends RuntimeException {

    // 响应码
    private IResponseEnum responseEnum;

    // 参数信息
    private Object[] objs;

    public BaseException(String message, IResponseEnum responseEnum, Object[] objs) {
        super(message);
        this.responseEnum = responseEnum;
        this.objs = objs;
    }

    public BaseException(String message, Throwable cause, IResponseEnum responseEnum, Object[] objs) {
        super(message, cause);
        this.responseEnum = responseEnum;
        this.objs = objs;
    }
}

• Custom assertion interface

public interface MyAssert {

    /**
     * 创建自定义异常
     *
     * @param objs 参数信息
     * @return 自定义异常
     */
    BaseException newException(Object... objs);

    /**
     * 创建自定义异常
     *
     * @param msg  描述信息
     * @param objs 参数信息
     * @return 自定义异常
     */
    BaseException newException(String msg, Object... objs);

    /**
     * 创建自定义异常
     *
     * @param t    接收验证异常
     * @param msg  描述信息
     * @param objs 参数信息
     * @return 自定义异常
     */
    BaseException newException(Throwable t, String msg, Object... objs);


    /**
     * 校验非空
     *
     * @param obj 被验证对象
     */
    default void assertNotNull(Object obj, Object... objs) {
        if (obj == null) {
            throw newException(objs);
        }
    }

    /**
     * 校验非空
     *
     * @param obj 被验证对象
     */
    default void assertNotNull(Object obj, String msg, Object... objs) {
        if (obj == null) {
            throw newException(msg, objs);
        }
    }
}

From the above code, we can see the basic design, which is to throw our custom exception after our custom assertion fails.

The following are specific implementation cases:

• Custom business exception class, inherited from exception base class

public class BusinessException extends BaseException {

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

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

}

• Response code enumeration interface definition

public interface IResponseEnum {

    /**
     * 返回code码
     *
     * @return code码
     */
    String getCode();

    /**
     * 返回描述信息
     *
     * @return 描述信息
     */
    String getMsg();
}

• Custom business exception class assertion definition, realize the definition of the corresponding custom exception after the custom assertion fails;

public interface BusinessExceptionAssert extends IResponseEnum, MyAssert {

    @Override
    default BaseException newException(Object... args) {
        return new BusinessException(this, args, this.getMsg());    //断言失败后,抛出自定义异常
    }

    @Override
    default BaseException newException(String msg, Object... args) {
        return new BusinessException(this, args, msg);              //断言失败后,抛出自定义异常
    }

    @Override
    default BaseException newException(Throwable t, String msg, Object... args) {
        return new BusinessException(this, args, msg, t);           //断言失败后,抛出自定义异常
    }
}

• Use enumeration to replace BadLicenceException and UserNotFoundException custom exceptions.

public enum ResponseEnum implements IResponseEnum, BusinessExceptionAssert {

    BAD_LICENCE("0001", "无权访问"),

    USER_NOT_FOUND("1001", "用户不存在"),
    ;

    private final String code, msg;

    ResponseEnum(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    @Override
    public String getCode() {
        return code;
    }

    @Override
    public String getMsg() {
        return msg;
    }
}

use case

A custom exception is thrown when the custom assertion fails

    @Override
    public UserVO queryUser(Long id) {
        UserDO userDO = userMapper.queryUserById(id);
        ResponseEnum.USER_NOT_FOUND.assertNotNull(userDO);    //自定义断言失败抛出自定义异常
        return userDO.toVo();
    }

Unified catch exceptions at the gateway to identify abnormal scenarios

    public static void main(String[] args) {
        UserService userService = new UserServiceImpl(new UserMapperImpl());
        UserController userController = new UserController(userService);
        try {
            UserVO vo = userController.queryUser(2L);               //执行业务逻辑
        } catch (BusinessException e) {
            System.out.println(e.getResponseEnum().getCode());      //出现异常,错误code:1001
            System.out.println(e.getMessage());                     //出现异常,错误msg:用户不存在
        }
    }

5. How to handle exceptions gracefully

Unified processing of exceptions at the gateway is a routine operation, so I won’t go into details here. A simple example is as follows:

@ControllerAdvice
public class BusinessExceptionHandler {
    
    @ExceptionHandler(value = BusinessException.class)
    @ResponseBody
    public Response handBusinessException(BaseException e) {
        return new Response(e.getResponseEnum().getCode(), e.getResponseEnum().getMsg());    //统一处理异常
    }
}

In summary, we adopt the method of custom assertion, which combines the advantages of high readability of the assertion and the advantages of custom exceptions to distinguish error scenarios. Moreover, there are new error scenarios, we only need to add corresponding enumerations in the error code enumeration.

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4090830/blog/8694561