Abandon the ugly try-catch and handle exceptions gracefully

As business logic becomes more and more complex, we will encounter various exceptions when writing code. At this time, we need to use try-catch statement to catch and handle exceptions. However, a large number of try-catch statements will make the code bloated and difficult to maintain. Therefore, we need an elegant way to uniformly handle exceptions and reduce try-catch statements in the code.

Compare the two diagrams below to see which style of code you are writing now? Then which coding style do you prefer?

Ugly try catch block:

 

Elegant Controller:

 

So the question is, how do we implement the second exception handling method, and how to handle various exceptions gracefully?

Basic Principles of Exception Handling

Before explaining how to reduce try-catch statements, let's first understand the basic principles of exception handling.

First, exceptions should be caught and handled in a timely manner. If the exception is not caught, the program will crash and throw an unhandled exception, affecting the stability of the system. Therefore, we need to use try-catch statements reasonably in the code to catch exceptions and handle them in the catch block.

Exceptions should be classified and handled. Different types of exceptions need to be handled in different ways. For example, for business logic exceptions, we need to return the exception information to the client, and for system exceptions, we need to record logs and notify the administrator.

Finally, exception handling should be uniform. In an application, we may encounter many different exception types. If each exception needs to be handled separately, the code will become very verbose. Therefore, we need to abstract the logic of exception handling to achieve unified exception handling.

Exception handling in Spring Boot

Spring Boot provides many ways to handle exceptions, such as using @ControllerAdvice annotation to define a global exception handling class, using @ExceptionHandler annotation to handle specific exception types, etc. Next, we will introduce how to use the @ControllerAdvice annotation to achieve unified exception handling.

Define exception handling class

We can use the @ControllerAdvice annotation to define a global exception handling class, and the methods in this class will be automatically called to handle exceptions. Here is an example:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    public ResponseData handleBusinessException(BusinessException e) {
        // 处理业务逻辑异常
        return new ResponseData(e.getCode(), e.getMessage(), null);
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ResponseData handleException(Exception e) {
        // 处理系统异常
        return new ResponseData(500, "系统异常", null);
    }
}

In the above code, we define a global exception handling class GlobalExceptionHandler, which defines two methods: handleBusinessException and handleException. Among them, the handleBusinessException method is used to handle business logic exceptions, and the handleException method is used to handle system exceptions. The @ExceptionHandler annotation specifies the exception type to handle, and the @ResponseBody annotation serializes the returned result into JSON format, which is convenient for the client to process.

custom exception class

In the above code, we use two exception classes, BusinessException and Exception, to distinguish between business logic exceptions and system exceptions. Therefore, we need to define these two exception classes. Here is an example:

public class BusinessException extends RuntimeException {

    private int code;

    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }
}

In the above code, we defined a BusinessException class, inherited from RuntimeException. This class contains a code field, which is used to identify the type of business logic exception.

Throw an exception

In our business logic, if we encounter an abnormal situation, we need to throw the corresponding exception. Here is an example:

javaCopy codepublic User getUserById(Long id) {
    User user = userRepository.findById(id).orElse(null);
    if (user == null) {
        throw new BusinessException(1001, "用户不存在");
    }
    return user;
}

In the above code, if the corresponding user is not found according to the id, we will throw a BusinessException, which contains the error code 1001 and the error message "the user does not exist".

Elegant exception handling

Through the above example, we have implemented a basic unified exception handling, but in actual development, we can also optimize the way of exception handling to make the code more elegant.

Use an enum class to define error codes

In the above example, we defined the error code in BusinessException, but the value of the error code may be repeated, and it is not easy to manage. Therefore, we can use enumeration classes to define error codes. Here is an example:

public enum ErrorCode {

    USER_NOT_FOUND(1001, "用户不存在"),
    PASSWORD_ERROR(1002, "密码错误"),
    ;

    private int code;
    private String message;

    ErrorCode(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

In the above code, we define an ErrorCode enumeration class, which contains multiple error codes and their corresponding error messages.

Use custom annotations to simplify code

In the above example, we need to manually create a BusinessException object in each method that throws an exception, and specify the error code and error message. The amount of code to do so is relatively large, and it is not very elegant. Therefore, we can use custom annotations to simplify the code. Here is an example:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckUser {

    long id() default 0;

}

In the above code, we define a CheckUser annotation to mark the method that needs to check the user. This annotation contains an id attribute, which is used to specify the user's id.

Here is an example using this annotation:

@GetMapping("/{id}")
@CheckUser(id = 1)
public User getUserById(@PathVariable("id") Long id) {
    User user = userService.getUserById(id);
    return user;
}

In the above code, we used the @CheckUser annotation and specified the id as 1, indicating that we need to check whether the user with the id of 1 exists. In the CheckUserAspect aspect, we will process the business logic according to the value of the annotation.

Using AOP to Realize Unified Exception Handling

In the above example, we used the @ExceptionHandler annotation to implement exception handling. However, if we have a lot of Controller methods, each method needs to be annotated, which will make the amount of code larger and less elegant. Therefore, we can use AOP to achieve unified exception handling. Here is an example:

javaCopy code@Aspect
@Component
public class ExceptionAspect {

    private static final Logger logger = LoggerFactory.getLogger(ExceptionAspect.class);

    @Pointcut("execution(public * com.example.springbootdemo.controller..*.*(..))")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object result;
        try {
            result = pjp.proceed();
        } catch (BusinessException e) {
            result = new Response<>(e.getCode(), e.getMessage());
        } catch (Exception e) {
            logger.error("系统异常", e);
            result = new Response<>(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
        }
        return result;
    }

}

In the above code, we define an ExceptionAspect aspect to handle exceptions thrown by all Controller methods. The @Pointcut annotation is used to define the pointcut, representing all public methods. The @Around annotation is used to define surround advice, which means that the advice will be executed before and after the execution of the target method. In that advice, we can handle the exception that was thrown and return the result.

Summarize

Through the above example, we have implemented a simple unified exception handling. In actual development, we can perform some optimizations according to requirements to make the code more concise and elegant. Exception handling is a very important function that needs to be taken seriously during the development process to avoid loopholes and ensure the stability and security of the system.

Guess you like

Origin blog.csdn.net/Dark_orange/article/details/130244437