springboot2.x uses @RestControllerAdvice to implement universal exception capture

Article directory

    • demo address
    • achieve effect
    • introduce
    • Basic class preparation
      • 1. General enumeration and error status enumeration
      • 2. Define universal return results
      • 3. Customized business exceptions
    • Unified exception catching
    • test

demo address

demo project address

Achieve results

  • When we enter 1, the normal response result is returned
  • When we enter 2, an exception is thrown, caught and the general result is returned.
  • You can see that the data structures of both are exactly the same

java general exception capture effect display

Introduction

Many times, when a javaweb project encounters some unpredictable errors during operation, an exception will be thrown. For example, below we directly throw a runtime exception in the interface:

Please add image description
The response of the corresponding interface becomes as shown below:

Please add image description
The returned results at this time are often very different from the data structure of the response results agreed with the front end, and it is not convenient for us to troubleshoot errors, and it is not good for the user experience. At this time, we need to configure a unified exception capture , returns a unified data structure for various exceptions, so that the front end can display the corresponding errors. We can also record the corresponding logs after catching the exceptions to facilitate subsequent troubleshooting.

Basic class preparation

1. General enumeration and error status enumeration

We first need to communicate with the front end about the unified data structure of return results and some common error states. Here we use an error state enumeration to display some errors in the business. In order to have better scalability of the enumeration, here we encapsulate it first A basic enumeration class is as follows:

  • You can refer to these videos of mine to explain the benefits of encapsulating universal enumerations:
  • Encapsulating generic enumeration 1
  • Encapsulation general enumeration 2 (encapsulation option generation)
  • Encapsulating general enumeration 3 (getting enumeration option interface by type)
/**
 * @Author: lzp
 * @description: Universal enumeration
 * @Date: 2022/9/24
 */
public interface BaseEnum<p> {<!-- -->

/**
* Get title
*/
String getTitle();

/**
* Get value
*/
P getValue();

/**
* Get the enumeration object through value
* Use generics to optimize this method on 2022-12-09
*
* @param enumClass enumeration class object
* @param value value
* @return
*/
static <T extends BaseEnum> T valueOf(Class<T> enumClass, Object value) {<!-- -->
if (value == null) {<!-- -->
return null;
}
T[] enumConstants = enumClass.getEnumConstants();
if (enumConstants == null) {<!-- -->
return null;
}
for (T enumConstant : enumConstants) {<!-- -->
if (value.equals(enumConstant.getValue())) {<!-- -->
return enumConstant;
}
}
return null;
}


}

Next, we implement the general enumeration interface and define the general status code enumeration as follows:

  • In this way, all predictable exceptions in our system can be defined in the error code enumeration
  • We can name the error codes of the corresponding business modules within a specified range. For example, we control user-related errors between 10001 ~ 10100.
import lombok.Getter;
import online.longzipeng.mywebdemo.enums.BaseEnum;

/**
 * @Author: lzp
 * @Date:2023/10/30
 * @description: General error status code
 */
@Getter
public enum ErrorCodeEnum implements BaseEnum<Integer> {<!-- -->

//General error status code
SUCCESS(0, "Success"),
ERROR(-1, "Failure"),
    
//User-related exceptions 10001 ~ 10100
USER_LOGIN_ERROR(1001,"Wrong account or password!"),
;

public final Integer value;
public final String title;

ErrorCodeEnum(Integer value, String title) {<!-- -->
this.value = value;
this.title = title;
}

}

2. Define universal return results

We have agreed with the front-end on a common data structure for return results, such as code, msg, and data, which respectively represent the response status code, error message, and return data of the interface:

  • In order to universally prevent any data type, here we use generics to specify the response data
  • In order to facilitate universal returns, for example, generally modifying the interface does not require returning data. At this time, we can specify to call the static method Result.success();
package online.longzipeng.mywebdemo.commen;

import lombok.Data;
import online.longzipeng.mywebdemo.commen.exception.ErrorCodeEnum;

import java.io.Serializable;

/**
 * Generic response
 *
 * @author lzp
 */
@Data
public class Result<T> implements Serializable {<!-- -->
private static final long serialVersionUID = 1L;
/**
* Coding: 0 indicates success, other values indicate failure
*/
private int code = ErrorCodeEnum.SUCCESS.value;
/**
\t * Message content
*/
private String msg = ErrorCodeEnum.SUCCESS.title;
/**
*Response data
*/
private T data;

public static <T> Result<T> error() {<!-- -->
return generate(ErrorCodeEnum.ERROR.value, ErrorCodeEnum.ERROR.getTitle());
}

/**
* Quickly generate return results
* @param code status code
* @param msg corresponding message content
*/
public static <T> Result<T> generate(int code, String msg) {<!-- -->
Result<T> result = new Result();
result.setCode(code);
result.setMsg(msg);
return result;
}

public static <T> Result<T> success() {<!-- -->
return new Result<>();
}

public static <T> Result<T> success(T data) {<!-- -->
Result<T> result = new Result<>();
result.setData(data);
return result;
}

}

3. Customized business exception

Because runtime exceptions are special exceptions that do not require explicit throwing and try catch processing, they are very suitable for our custom business exceptions. Then we hope that the Result data structure will be uniformly returned when the business goes wrong. So our custom exception also needs to include the code and msg fields.

package online.longzipeng.mywebdemo.commen.exception;

import lombok.Data;

/**
 * General exception handling
 */
@Data
public class ServiceException extends RuntimeException {<!-- -->
private static final long serialVersionUID = 1L;

/**
\t * error code
*/
private int code;

/**
\t * error message
*/
private String msg;

public ServiceException(int code) {<!-- -->
this.code = code;
}

public ServiceException(int code, String msg) {<!-- -->
this.code = code;
}

public ServiceException(String msg) {<!-- -->
super(msg);
this.code = ErrorCodeEnum.ERROR.value;
this.msg = msg;
}

/**
* Quickly create exceptions using common error enumerations
*/
public ServiceException(ErrorCodeEnum errorCodeEnum) {<!-- -->
this.code = errorCodeEnum.getValue();
this.msg =errorCodeEnum.getTitle();
}
}

Unified exception capture

In springboot2.x, we can achieve unified exception capture and processing through the @ControllerAdvice annotation and @ExceptionHandler annotation. In order to facilitate the return of results, here we use the @RestControllerAdvice annotation to return a response in JSON format, which is used to quickly build RESTful-style programs. .

as follows:

  • Here we directly capture the custom business exception, take the result from it and return the Result object
  • Capture Exception. When an unpredictable exception occurs, capture it here and print the error log.
package online.longzipeng.mywebdemo.commen.exception;

import online.longzipeng.mywebdemo.commen.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * General exception handler
 */
@RestControllerAdvice
public class ServiceExceptionHandler {<!-- -->
private Logger logger = LoggerFactory.getLogger(getClass());

/**
* Handle custom exceptions
*/
@ExceptionHandler(ServiceException.class)
public Result handleRenException(ServiceException e) {<!-- -->
return Result.generate(e.getCode(),e.getMsg());
}

/**
* Handle unknown exceptions
*/
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {<!-- -->
logger.error(e.getMessage(), e);
return Result.error();
}
}

Test

We create a controller for testing and manually throw a business exception in it, as follows:

package online.longzipeng.mywebdemo.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import online.longzipeng.mywebdemo.commen.Result;
import online.longzipeng.mywebdemo.commen.exception.ErrorCodeEnum;
import online.longzipeng.mywebdemo.commen.exception.ServiceException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: lzp
 * @description:
 * @Date: 2023/10/30
 */
@RestController
@RequestMapping("/test")
@Api(tags = "Test Interface")
public class TestController {<!-- -->

@GetMapping("/error")
@ApiOperation("Test exception thrown")
public Result<String> testError(@RequestParam @ApiParam("1 normal 2 throws error") Integer type) {<!-- -->
if (type == 2) {<!-- -->
throw new ServiceException(ErrorCodeEnum.USER_LOGIN_ERROR);
}
return Result.success("Hello~");
}

}

The displayed results are as follows:

  • When we enter 1, the normal response result is returned
  • When we enter 2, the corresponding error is returned
  • You can see that the data structures of both are exactly the same

java general exception capture effect display