background
In today’s distributed, microservice prevailing, most projects have adopted the microservice framework, front and back end separation mode. The front end and the back end interact. The front end requests the URL path according to the convention and passes in relevant parameters. The back end server receives the request, processes services and returns data to the front end. It is necessary to maintain a set of perfect and standard interfaces, which can not only improve docking efficiency, but also make my code look more concise and elegant.
When using uniform return results, there is also a case where an error is reported by a program as a result of runtime exceptions, some of which we throw in our business and some of which we cannot predict in advance.
Therefore, we need to define a unified global exception, catch all exceptions in the Controller, handle them appropriately, and return them as a result.
Unified Interface Return
Define API return code enumeration classes
public enum ResultCode {
/* Success status code */
SUCCESS(200."Success"),
/* Error status code */
NOT_FOUND(404."Requested resource does not exist"),
INTERNAL_ERROR(500."Server internal error"),
PARAMETER_EXCEPTION(501."Request parameter verification exception"),
/* Service status code */
USER_NOT_EXIST_ERROR(10001."User does not exist"),;private Integer code;
private String message;
public Integer code(a) {
return this.code;
}
public String message(a) {
return this.message;
}
ResultCode(Integer code, String message) {
this.code = code;
this.message = message; }}Copy the code
Defines the API uniform return body for a normal response
@Data
public class Result<T> implements Serializable {
private Integer code;
private String message;
private boolean success = true;
private T data;
@JsonIgnore
private ResultCode resultCode;
private Result(a) {}public void setResultCode(ResultCode resultCode) {
this.resultCode = resultCode;
this.code = resultCode.code();
this.message = resultCode.message();
}
public Result(ResultCode resultCode, T data) {
this.code = resultCode.code();
this.message = resultCode.message();
this.data = data;
}
public static <T> Result<T> success(a) {
Result<T> result = new Result<>();
result.setResultCode(ResultCode.SUCCESS);
return result;
}
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setResultCode(ResultCode.SUCCESS);
result.setData(data);
returnresult; }}Copy the code
The API uniform return body that defines the exception response
@Data
public class ErrorResult implements Serializable {
private Integer code;
private String message;
private boolean success = false;
@JsonIgnore
private ResultCode resultCode;
public static ErrorResult error(a) {
ErrorResult result = new ErrorResult();
result.setResultCode(ResultCode.INTERNAL_ERROR);
return result;
}
public static ErrorResult error(String message) {
ErrorResult result = new ErrorResult();
result.setCode(ResultCode.INTERNAL_ERROR.code());
result.setMessage(message);
return result;
}
public static ErrorResult error(Integer code, String message) {
ErrorResult result = new ErrorResult();
result.setCode(code);
result.setMessage(message);
return result;
}
public static ErrorResult error(ResultCode resultCode, String message) {
ErrorResult result = new ErrorResult();
result.setResultCode(resultCode);
result.setMessage(message)
returnresult; }}Copy the code
Write custom annotations that wrap the returned results
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD}) // Applies to methods and classes (interfaces)
@Documented
public @interface ResponseResult {
}
Copy the code
Define return result interceptors
@Component
public class ResponseResultInterceptor implements HandlerInterceptor {
/* Use the uniform return body identifier */
private static final String RESPONSE_RESULT_ANNOTATION = "RESPONSE-RESULT-ANNOTATION";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// The method bean that is processing the request
if (handler instanceof HandlerMethod) {
final HandlerMethod handlerMethod = (HandlerMethod) handler;
// Get the current class
finalClass<? > clazz = handlerMethod.getBeanType();// Get the current method
final Method method = handlerMethod.getMethod();
// Determine whether class objects are annotated
if (clazz.isAnnotationPresent(ResponseResult.class)) {
// Set the return body of the request, which needs to be wrapped, passed down, and determined in the ResponseBodyAdvice interface
request.setAttribute(RESPONSE_RESULT_ANNOTATION, clazz.getAnnotation(ResponseResult.class));
}
// Determine whether methods are annotated
else if (method.isAnnotationPresent(ResponseResult.class)) {
// Set the return body of the request, which needs to be wrapped, passed down, and determined in the ResponseBodyAdvice interfacerequest.setAttribute(RESPONSE_RESULT_ANNOTATION, method.getAnnotation(ResponseResult.class)); }}return true; }}Copy the code
The WebMvc configuration class interceptor registrar adds the return result interceptor
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/** * Add custom interceptor */
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ResponseResultInterceptor()).addPathPatterns("/ * *"); }}Copy the code
Write the response body processor
/** * handles the response body uniformly, wrapped in the result. success static method, * when used by the API interface can return the original type */
@RestControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
/* Use the uniform return body identifier */
private static final String RESPONSE_RESULT_ANNOTATION = "RESPONSE-RESULT-ANNOTATION";
@Override
public boolean supports(MethodParameter methodParameter, Class
> aClass) {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(sra).getRequest();
ResponseResult responseResult = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANNOTATION);
// Determine whether the returned body needs processing
returnresponseResult ! =null;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class
> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
// The exception response body returns the code+message body directly
if (body instanceof ErrorResult) {
return body;
}
// The normal response body returns the Result wrapped code+message+data body
returnResult.success(body); }}Copy the code
Interface call
@api (" User Management ")
@RestController
@RequestMapping("user")
@ResponseResult // Applies to all interfaces on the class
public class UserController {
@Autowired
private UserService userService;
@ResponseResult // apply to the method
@apiOperation (" Query user by ID ")
@GetMapping("one")
public User selectOne(Long id) {
// Since the ResponseResultHandler has already wrapped the returned data in result. success,
// Return the original type
return this.userService.queryById(id);
}
@ResponseResult
@apiOperation (" Query all users ")
@GetMapping("all")
public List<User> selectAll(Page page) {
// Since the ResponseResultHandler has already wrapped the returned data in result. success,
// Return the original type
return this.userService.queryAllByLimit(page); }}Copy the code
The test results
Global exception handling
Write a custom exception base class
@Data public class BaseException extends RuntimeException { private static final int BASE_EXCEPTION_CODE = ResultCode.INTERNAL_ERROR.code(); private static final String BASE_EXCEPTION_MESSAGE = ResultCode.INTERNAL_ERROR.message(); private Integer code; private String message; public BaseException() { super(BASE_EXCEPTION_MESSAGE); this.code = BASE_EXCEPTION_CODE; this.message = BASE_EXCEPTION_MESSAGE; } public BaseException(String message) { super(message); this.code = BASE_EXCEPTION_CODE; this.message = message; } public BaseException(ResultCode resultCode) { super(resultCode.message()); this.code = resultCode.code(); this.message = resultCode.message(); } public BaseException(Throwable cause) { super(cause); this.code = BASE_EXCEPTION_CODE; this.message = BASE_EXCEPTION_MESSAGE; } public BaseException(String message, Throwable cause) { super(message, cause); this.code = BASE_EXCEPTION_CODE; this.message = message; } public BaseException(Integer code, String message) { super(message); this.code = code; this.message = message; } public BaseException(Integer code, String message, Throwable cause) { super(message, cause); this.code = code; this.message = message; }}Copy the code
Write custom business exception classes
public class BizException extends BaseException {
public BizException(ResultCode resultCode) {
super(resultCode); }}Copy the code
Define global exception handling classes
The @ExceptionHandler annotation is used to uniformly handle a class of exceptions
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/** * Handle custom base exceptions */
@ExceptionHandler(BaseException.class)
public ErrorResult baseException(BaseException e) {
if (StringUtils.isEmpty(e.getCode())) {
return ErrorResult.error(e.getMessage());
}
return ErrorResult.error(e.getCode(), e.getMessage());
}
/** * Handle custom service exceptions */
@ExceptionHandler(BizException.class)
public ErrorResult bizException(BizException e) {
if (StringUtils.isEmpty(e.getCode())) {
return ErrorResult.error(e.getMessage());
}
return ErrorResult.error(e.getCode(), e.getMessage());
}
/** * Handle all exceptions except for non-custom exceptions */
@ExceptionHandler(Exception.class)
public ErrorResult handleException(Exception e) {
log.error(e.getMessage(), e);
return ErrorResult.error(e.getMessage());
}
Validation framework: Ignores the parameter exception handler */
@ExceptionHandler(MissingServletRequestParameterException.class)
public ApiResult<String> parameterMissingExceptionHandler(MissingServletRequestParameterException e) {
log.error(e.getMessage(), e);
return ErrorResult.error(PARAMETER_EXCEPTION, "Request parameters" + e.getParameterName() + "Can't be empty.");
}
Validation framework: Missing request body exception handler */
@ExceptionHandler(HttpMessageNotReadableException.class)
public ErrorResult parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {
log.error(e.getMessage(), e);
return ErrorResult.error(PARAMETER_EXCEPTION, "Parameter body cannot be empty");
}
Validation framework: Parameter Validation exception handler */
@ExceptionHandler(MethodArgumentNotValidException.class)
public ErrorResult parameterExceptionHandler(MethodArgumentNotValidException e) {
log.error(e.getMessage(), e);
// Get the exception information
BindingResult exceptions = e.getBindingResult();
// Determine if there is an error message in the exception, if there is, use the message in the exception, otherwise use the default message
if (exceptions.hasErrors()) {
List<ObjectError> errors = exceptions.getAllErrors();
if(! errors.isEmpty()) {// All error parameters are listed here
FieldError fieldError = (FieldError) errors.get(0);
returnErrorResult.error(PARAMETER_EXCEPTION, fieldError.getDefaultMessage()); }}return ErrorResult.error(PARAMETER_EXCEPTION, "Request parameter verification exception"); }}Copy the code
Interface call
@ResponseResult
@GetMapping
public User update(a) {
// Non-custom runtime exception
long id = 10 / 0;
return userService.queryById(id);
}
@ResponseResult
@PostMapping
public User insert(a) {
// Throw a custom base exception
throw new BaseException();
}
@ResponseResult
@DeleteMapping
public boolean delete(a) {
// Throw custom business exceptions
throw new BizException(USER_NOT_EXIST_ERROR);
}
Copy the code