The springboot backend returns data in a unified format and handles exceptions in a unified manner

The springboot backend returns data in a unified format and handles exceptions in a unified manner

Template

Scenario:

  • The data type from the back end to the front end may be basic data type, String string, object, array, or exception prompt. The front end gets the data you returned to show or give error prompts, but it can't say that each interface handles these exception prompts, such as no login or some business exceptions.

analysis:

  • Based on the above scenario, what we need to do is to do a layer of unified processing before the back-end returns the results. Return a unified object, such as ResponseVO, including code, msg and data; The front end performs unified processing according to the returned code
    • code=0, success is returned, and the returned data is on data
    • code=1 or other, the back-end exception is returned, which may be a business exception or a program exception. The error message is placed on msg
  • If you do not log in, the back end returns 403. At this time, the front end calls the back end interface and returns there to do unified processing, unified prompt or others according to the error code. If successful, send the returned data to the corresponding calling method.

realization:

In the primary version, we return a map, and then put code, msg and data into it through the map

    @RequestMapping("/test")
    public Map<String,Object> test(){
        Map<String,Object> map = new HashMap<>();
        map.put("code","0");
        map.put("msg","success");
        map.put("data","test");
        return map;
    }
  • Return result:
  • We can see from the above picture that it meets our needs and returns code, msg and our data.
  • But the problem comes. We have to write a map for each method. Is it troublesome to put these data in it? How can we fish after spending so much time writing this? Therefore, we have our advanced version after a small optimization

Advanced version, unified encapsulation: define a unified return object ResponseVO, and write success and failure methods in ResponseVO

@Data
public class ResponseVO implements Serializable {
/**
    * Response status code, 0-success, non-0-failure
    */
   private Integer code = 0;
   /**
    * Return result description
    */
   private String msg = "success";
   /**
    * JSON Format response data
    */
   private Object data;
   /**
    * Return success
    * @param data
    * @return
    */
   public static ResponseVO success(Object data){
   	ResponseVO response = new ResponseVO();
   	response.setCode(0);
   	response.setMsg("success");
   	response.setData(data);
   	return response;
   }
}
  • At this time, the controller call becomes the following. Is it much simpler
@RequestMapping("/test1")
    public ResponseVO test1(){
        return ResponseVO.success("Test 1");
    }
  • Although it is much simpler now, you still need to write ResponseVO on each method Success () or ResponseVO Fail(), and the return value of each method becomes ResponseVO. We don't know their meaning. Is there a unified treatment, that is, I can return what I should return. The controller layer doesn't care about these? Of course, the answer is yes, so there is the final version below.

Final version, ResponseBodyAdvice

  • Next, we need to use ResponseBodyAdvice. From the literal meaning, it means to return the body section, which is to uniformly process the data returned by the Controller. Therefore, we just need to implement this interface and do unified processing on it. It has two interfaces. We only need to process it in the beforeBodyWrite method. The only thing to pay attention to is that when returning the String type, special processing is required, otherwise conversion errors will be reported. After unified encapsulation, you don't have to care about the return type.
@RestControllerAdvice
public class ResponseHandler implements ResponseBodyAdvice<Object> {
	private Log log = LogFactory.getLog(ResponseHandler.class);
	@Override
	public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
		return true;
	}
	@Override
	public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
								  Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
								  ServerHttpResponse response) {
		ResponseVO respVo = null;
		if (body instanceof ResponseVO) {
			respVo = (ResponseVO) body;
		}else {
			respVo = new ResponseVO();
			respVo.setData(body);
		}
		//If the returned string type is, first judge whether the HttpMessageConverter can support the corresponding return type, and then use ResponseBodyAdvice to encapsulate it
		//At this time, the incoming message is not of String type, so it will be reported that it cannot be converted into ResponseVO object. There are two methods: one is to directly return json String, and the other is
		//One is to make additional configuration with your own WebConfig
		if (body instanceof String){
			return JSONUtil.toJsonStr(respVo);
		}
		return respVo;
	}
}

Question 1. What if there is a special interface that does not need to return this format?

  • We can use another method of the ResponseBodyAdvice interface to make the return value of your method not processed in this unified return format. The best way is to specify an annotation and add this annotation to the methods that need to be ignored. The implementation method is as follows
  1. Define annotation IgnoreResponseHandler
@Documented
@Inherited
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreResponseHandler {

}
  1. Ignored in the supports method of ResponseBodyAdvice
@RestControllerAdvice
public class ResponseHandler implements ResponseBodyAdvice<Object> {
	private Log log = LogFactory.getLog(ResponseHandler.class);
	@Override
	public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
		return !returnType.hasMethodAnnotation(IgnoreResponseHandler.class);
	}
}
  • Use to return the result, which is ignored
    @RequestMapping("/test5")
    @IgnoreResponseHandler
    public String test5(){
        return "Test 1";
    }

Unified processing of returned business exceptions

  • If we want to handle exceptions uniformly, we need to use the annotation @ ExceptionHandler(value = Exception.class). With this annotation, we will enter this method when throwing exceptions
	@ExceptionHandler(value = Exception.class)
	public ResponseVO onException(HttpServletRequest request, Exception ex) {
		ResponseVO resp = null;
		if (ex instanceof AppException) {
			resp = new ResponseVO((AppException) ex);
		} else {
			AppException exception = new AppException(9999,"Unknown exception");
			resp = new ResponseVO(exception);
			log.error("Unknown exception:", ex);
		}
		return resp;
	}
  • use
@RequestMapping("/test3")
    public String test3(){
        throw new AppException("Test exception");
    }

Posted by jwwceo on Thu, 12 May 2022 11:19:50 +0300