The @RestControllerAdvice and ResponseBodyAdvice handle controller layer interface returns uniformly

1. Theoretical knowledge

To return the contents of the controller layer uniformly, we use @controllerAdvice ResponseBodyAdvice

  • @RestControllerAdvice

A handy comment with @ControllerAdvice and @responseBody annotations itself, and the type that carries this annotation is considered a controller notification but in short, it’s an enhancement of the methods in the Controller layer

  • ResponseBodyAdvice

Allow the response to be customized after the @responseBody or ResponseEntity controller method executes, but before the body is written using HttpMessageConverter

The ResponseBodyAdvice interface is a ResponseBodyAdvice interface that processes the reponse data after the controller layer method executes and before the response is returned to the front end. The ResponseBodyAdvice interface is a ResponseBodyAdvice interface that processes the response data after the controller layer method executes and before the response is returned to the front end data.

2. Give examples

  • 2.1 Write uniform return data format code
@RestControllerAdvice @Slf4j public class UnifiedAdvice implements ResponseBodyAdvice { @Override public boolean supports(MethodParameter returnType, Class converterType) {// If @responsenotIntercept is added then AnnotatedElement does not have to do a uniform interception AnnotatedElement = returnType.getParameterType(); ResponseNotIntercept annotation = AnnotationUtils.findAnnotation(annotatedElement, ResponseNotIntercept.class); return annotation == null; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body instanceof CommonResult) return body; CommonResult<Object> objectCommonResult = new CommonResult<>(ResultCode.SUCCESS, body); Return objectCommonResult; // Return objectCommonResult; }}Copy the code

Note: Implementing the ResponseBodyAdvice interface requires overriding the supports,beforeBodyWrite method;

Supports: supports the given controller method return type and the selected HttpMessageConverter type; If not, the data will not be processed in a uniform manner. As in the above code, the @responsenotIntercept annotation will not be intercepted (@responsenotIntercept is a custom annotation).

Parameters: returnType: returnType; ConverterType: the selected converterType

Return: Calls beforeBodyWrite if true is returned

BeforeBodyWrite: called after HttpMessageConverter is selected and before its write method is called.

Parameters: Body: the data you pass in; ReturnType: The type returned by the controller layer method; SelectedContentType: Content type selected through content negotiation; SelectedConverterType: Selects the type of converter to write the response; Request/Reponse: Indicates the current request and response

Return: incoming data or modified (possibly new) instances

  • 2.2 Writing contrller layer methods
@RestController @Slf4j @RequestMapping("/baseInfo") public class BaseInfoController { @Autowired private BaseInfoService  baseInfoService; /** * Add basic information * @param info * @return */ @postMapping ("/addBaseInfo") public BaseInfo addBaseInfo(@requestBody BaseInfo) info){ BaseInfo baseInfo = baseInfoService.addBaseInfo(info); return baseInfo; }}Copy the code

The control layer returns an object, and the business layer data layer method omits only the results returned after execution to do unified processing:

  • 2.3 Return objects uniformly
@Data @Slf4j public class CommonResult<T> { private String code; private String message; private T Data; public CommonResult() { } public CommonResult(T data) { Data = data; } /** * The default value is null * @param rc */ public CommonResult(rc) {this(rc, null); } public CommonResult(ResultCode rc, T data) { this.code = rc.getCode(); this.message = rc.getMsg(); this.Data = data; } public static<T> CommonResult<T> errorResult(ResultCode rc,T data){ CommonResult<T> commonResult = new CommonResult<>(); commonResult.code = rc.getCode(); commonResult.message = rc.getMsg(); commonResult.Data = data == null? (T) "" :data; log.error("{}",commonResult); return commonResult; }}Copy the code

ResultCode is its own custom enumeration classes such as:

3. Analysis of common errors

But is that all right? Actually otherwise what happens if you return a string in a controller layer method? Please look at:

@postMapping ("/test") public String addBaseInfo(){return "I return a String "; }Copy the code

Console error:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.ClassCastException: com.flscode.publicApi.Response.CommonResult cannot be cast to java.lang.String

Caused by: java.lang.ClassCastException: com.flscode.publicApi.Response.CommonResult cannot be cast to java.lang.String

To solve

Analysis: Normal data return:String data return break point:Reason: By default, SpringMVC registers some of its ownHttpMessageConvertorOrder (from ByteArrayHttpMessageConverter, StringHttpMessageConverter, ResourceHttpMessageConverter respectively, SourceHttpMessageConverter, AllEncompassingFormHttpMessageConverter), back-end services using the form of a Restful API, before and after the end of the specification is usually the json format, for SpringMVC cabinMappingJackson2HttpMessageConverterAfter the Jackson package is introduced into the dependency, the container willMappingJackson2HttpMessageConverterAutomatically register to the end of the Converter chain (this end is from:Blog.csdn.net/weixin_4433…

So as you can see from the two figures above, the method here is to loop through the HttpMessageConverter collection, using the converter if it is available, and when you return a string of data, Because StringHttpMessageConverter will be traversed to first, then will think StringHttpMessageConverter can use, so will be an error when ResponseBodyAdvice encapsulated data

Revised:

@Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body instanceof CommonResult) return body; CommonResult<Object> objectCommonResult = new CommonResult<>(ResultCode.SUCCESS, body); if (body instanceof String){ String s = JSON.toJSONString(new CommonResult<>(ResultCode.SUCCESS, body)); return s; } // Return objectCommonResult; }Copy the code

The controller layer:

@PostMapping(value = "/test",produces = "application/json; Charset = utF-8 ") public String test(){return "I return a String "; }Copy the code

Return result:

4. To summarize

In our separate development, the back end usually returns a common format to the front end. Every time we write a method at the controller layer, we need to wrap CommonResult, which is a lot of unnecessary code. So, every time we write a method at the controller layer, Instead of encapsulating the CommonResult object every time, we’ll encapsulate the CommonResult object for uniform interception: Use @RestControllerAdvice and ResponseBodyAdvice to do the same processing, and note that the return string of the method is processed accordingly, for reasons mentioned in # 3.