This is the 14th day of my participation in the August More Text Challenge. For details, see:August is more challenging

background

In the process of project development, whether it is the operation process of the database, the processing process of the business layer, or the processing process of the control layer, it is inevitable to encounter a variety of predictable and unpredictable exceptions to be dealt with. If exceptions are handled separately for each process, the code coupling degree of the system will become very high. In addition, the development workload will increase and not be uniform, which also increases the maintenance cost of the code.

In view of this actual situation, we need to decouple all types of exception handling from each processing process, which not only ensures the single function of related processing process, but also realizes the unified processing and maintenance of exception information. At the same time, we do not want to throw the exception directly to the user. We should handle the exception, encapsulate the error message, and return a friendly message to the user.

The development of preparation

  • The environmentJDK:Its 15.0.1(You can also make adjustments and suggestions according to your responseJDK8And above)
  • Spirng boot:2.5.2

MavenRely on

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.5.2</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.5.2</version>
    </dependency>
</dependencies>
Copy the code

The development of

Define uniform responsejsonstructure

When the front-end or other services request the interface of the service, the interface needs to return the corresponding JSON data. Generally, the service only needs to return the required parameters in the request. However, in the actual project, we need to encapsulate more information, such as status code, related information message, etc. On the one hand, there can be a unified return structure in the project, which is applicable to the whole project team; on the other hand, it is convenient to combine with global exception handling information. Because normally in exception handling information we need to feed back the status code and exception content to the caller.

/** Java * responsebody ** @author * @date 2021/8/13 */ @data @noargsconstructor @allargsconstructor public class Body<T> { Private int code = 0; private int code = 0; Private String message = "ok"; / / Private String message = "ok"; /** * Response */ private T content; public Body(T content) { if (content instanceof BaseException) { BaseException baseException = (BaseException) content; this.message = baseException.getMessage(); this.code = baseException.getCode(); this.content = null; } else { this.content = content; }}}Copy the code

The response class needs to contain the body of the response, along with some headers and status codes for the HTTP response, and so on

/** * Response class **@authorEvil foam white *@date2021/8/13 * /

public class Response<T> extends ResponseEntity<Body<T>> {

    public Response(HttpStatus status) {
        super(new Body<>(), status);
    }

    public Response(T t, HttpStatus status) {
        super(new Body<>(t), status);
    }

    public Response(Body<T> body, HttpStatus status) {
        super(body, status);
    }

    public Response(MultiValueMap<String, String> headers, HttpStatus status) {
        super(new Body<>(), headers, status);
    }

    public Response(Body<T> body, MultiValueMap<String, String> headers, HttpStatus status) {
        super(body, headers, status);
    }

    public Response(Body<T> body, MultiValueMap<String, String> headers, int rawStatus) {
        super(body, headers, rawStatus); }}Copy the code

Define a global custom exception class

A custom exception class is designed to handle exceptions that are caught globally and to generate a better response when returned.

/** * Custom exception class **@authorEvil foam white *@date2021/8/13 * /

@Data
@EqualsAndHashCode(callSuper = true)
public class BaseException extends RuntimeException {
    private int code;
    private String message;

    public BaseException(GlobalExceptionEnum error) {
        this.code = error.getCode();
        this.message = error.getMessage();
    }

    /** * Obtain the HTTP status code based on code. The first three bits of code are the same as the HTTP status code@returnHTTP status code */
    public HttpStatus getHttpStatus(a) {
        // Check whether code is 3 digits
        if (!this.verifyCode()) {
            // Return 400 if not
            return HttpStatus.BAD_REQUEST;
        }
        String codeStr = String.valueOf(this.code).substring(0.3);
        int statusCode = Integer.parseInt(codeStr);

        HttpStatus httpStatus = HttpStatus.resolve(statusCode);

        if (Objects.isNull(httpStatus)) {
            // Return 400 if not
            return HttpStatus.BAD_REQUEST;
        }
        return httpStatus;
    }

    /** * Check code the code is at least 3 digits **@returnTrue, otherwise false */
    public boolean verifyCode(a) {
        return this.code >= 100; }}Copy the code

Predefined exception error

Enumeration definitions are used here to define errors that we can predict in advance, so that we can use them during development.

The first three digits of code are represented by HTTP status codes, and some exception information is classified. It can be expanded based on service requirements.

The Http status code describe
400 INVALID REQUEST The user made a request with an error. The server did not create or modify data. The operation is idempotent.
401 Unauthorized Indicates that the user is not authenticated (incorrect token, user name, or password).
403 Forbidden Indicates that the user is authenticated (as opposed to error 401), but access is denied (no access to the resource).
404 NOT FOUND The request made by the user is for a record that does not exist, the server has not acted on it, and the action is idempotent.
410 Gone The requested resource is permanently deleted and will not be retrieved.
422 Unprocesable entity A validation error occurred while creating an object.
500 INTERNAL SERVER ERROR The server has an error and the user will not be able to determine whether the request was made successfully.
/** * custom exception code **@authorEvil foam white *@date2021/8/13 * /
public enum GlobalExceptionEnum {

    REQUEST_INVALID_PARAM(4001."invalid param"."Invalid parameter"),

    NOT_LOGGED_IN(4011."not logged in"."Not logged in"),

    PASSWORD_INCORRECT(4012."password incorrect"."Password error"),

    AUTH_EXCEPTION(4031."auth exception"."Permission error"),

    NOT_FOUNT(4041."not found"."Not found"),

    DELETED(4101."deleted"."Deleted"),

    EXISTED(4221."existed"."Existing"),

    INTERNAL_SERVER_ERROR(5001."internal server error"."Internal system error"),

    UNKNOWN_SERVER_ERROR(5002."unknown server error"."System unknown error"),

    NULL_POINTER_EXCEPTION(5003."null pointer exception"."Null pointer exception");


    /** * Error code */
    private int code;
    /** * Error message */
    private String message;
    /** * Error description */
    private String description;

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

    public BaseException getException(a) {
        return new BaseException(this); }}Copy the code

Defining exception handlers

Define an abstract class in the Controller layer. Anything that needs to catch exceptions needs to inherit from this class.

/** * All controllers that need to handle exceptions should inherit from this class **@authorEvil foam white *@date2021/8/13 * /

public abstract class AbstractController {}Copy the code

We define a global Exception handler. When an Exception is found in an inherited AbstractController class, the current handler will catch it, and @ExceptionHandler will locate our handler methods for exceptions (custom exceptions, null pointer exceptions, and exceptions).

Since Exception is the parent class, all exceptions inherit from it, so we can just intercept Exception once and for all. However, in the project, we usually intercept some common exceptions in detail, but it is not good for us to troubleshoot or locate problems. In a real project, you can write the intercept Exception at the bottom of GlobalExceptionHandler. If none is found, then intercept the Exception at the end to ensure friendly output information.

At the same time, we should print the log where the exception was thrown to facilitate locating and resolving the problem.

/** * Custom global exception handler@authorEvil foam white *@date2021/8/13 * /

@ResponseBody
@ControllerAdvice(basePackageClasses = AbstractController.class)
public class GlobalExceptionHandler {

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


    /** * Service exception handling **@paramRequest request *@paramAbnormal e *@returnResponse content */
    @ExceptionHandler(value = BaseException.class)Response<? > handleBaseException(HttpServletRequest request, BaseException e) { logger.info("request url :{}, code:{}, msg: {}", request.getRequestURI(), e.getCode(), e.getMessage());
        return new Response<>(e, e.getHttpStatus());
    }


    /** * Null pointer exception handling **@paramRequest request *@paramAbnormal e *@returnResponse content */
    @ExceptionHandler(value = NullPointerException.class)Response<? > handleNpe(HttpServletRequest request, NullPointerException e) { logger.info("request url :{}, npe, msg: {}", request.getRequestURI(), e.getMessage());
        BaseException exception = new BaseException(GlobalExceptionEnum.NULL_POINTER_EXCEPTION);
        return new Response<>(exception, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    /** * All exception handling */
    @ExceptionHandler(Exception.class)
    publicResponse<? > handleException(HttpServletRequest request, Exception e) { logger.info("request url :{}, msg: {}", request.getRequestURI(), e.getMessage());
        BaseException exception = GlobalExceptionEnum.INTERNAL_SERVER_ERROR.getException();
        return newResponse<>(exception, HttpStatus.INTERNAL_SERVER_ERROR); }}Copy the code

Comments:

  • @controllerAdvice: An enhancer to the @Controller layer that we can use in Spring to declare something global.

  • @ExceptionHandler: Annotates the method used to catch the different types of exceptions thrown by the Controller so that exceptions can be handled globally.

Note:

  • The exception message is not intry-catchBlock capture, all usedthrowthrow

test

Write a login interface, and then do different exception handling for different cases

Error description code The HTTP status code message
Null pointer, nothing is passed 5003 500 null pointer exception
The user name does not match parameter verification 4001 400 invalid param
The user name does not exist 4041 400 not found
Password mistake 4012 401 password incorrect
/** * test controller **@authorEvil foam white *@date2021/8/13 * /

@RestController
@RequestMapping("/users")
public class UserController extends AbstractController {

    public static final String DEFAULT_USERNAME = "wanedemobai";
    public static final String DEFAULT_PASSWORD = "000000";
    public static final int USERNAME_MIN_LENGTH = 6;

    @PostMapping("/login")
    publicResponse<? > login(@RequestBody LoginReq req
    ) {
        if (req.getUsername().length() < USERNAME_MIN_LENGTH) {
            throw GlobalExceptionEnum.REQUEST_INVALID_PARAM.getException();
        }

        if(! DEFAULT_USERNAME.equals(req.getUsername())) {throw GlobalExceptionEnum.NOT_FOUNT.getException();
        }

        if(! DEFAULT_PASSWORD.equals(req.getPassword())) {throw GlobalExceptionEnum.PASSWORD_INCORRECT.getException();
        }
        return newResponse<>(HttpStatus.OK); }}Copy the code

Finally, we tested it with Postman to see if the results were correct

conclusion

Code repository address

  • gitee

Execute the process

  1. Send a request
  2. performcontrollerLayer code
  3. When an exception is found, it will be checked by ourGlobalExceptionHandlerAccording to the exception classification, processing
  4. Return the response