7

In the Spring Boot project, we can implement the ResponseBodyAdvice<T> RestControllerAdvice to ensure that the Spring MVC interface has a unified return format to ensure that the front-end students can encapsulate a unified data receiving tool. However, many online articles did not give more explanations on the details of the actual development. Today, Fat Brother will share my experience in mining pits, which is also considered as a summary.

Control range

I remember in the previous Swagger3 , if we do not specify the range, Swagger not be able to identify the meta information of the interface. Therefore, if you use Swagger, you must specify its scope. Here you can specify its scope by specifying the scan package:

@RestControllerAdvice("cn.felord.controller")

If your Spring MVC controller has a unified parent controller,

@RestController
@RequestMapping("/foo")
public class FooController extends BaseController {
 //todo 省略
}    

It can also be like this:

@RestControllerAdvice(assignableTypes = BaseController.class)

whitelist

Some interfaces may not be able to use a unified return body according to business needs or protocol needs, such as payment notification responses. This requires a whitelist-like mechanism to bypass the unified return body controller notification class. We can use the following methods of ResponseBodyAdvice<T>

boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);

If this method returns false it means that the encapsulation logic of the unified return body is not executed. Here I recommend annotation implementation. Define a mark annotation, which can be defined on a class or method:

@Documented
@Inherited
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreRestBody {
}

Then the above supports method is implemented like this:

@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
    return !returnType.hasMethodAnnotation(IgnoreRestBody.class);
}

If all methods under a Controller are bypassed, mark this annotation on the controller class; if you just want to ignore a method, mark it on that method.

The problem of returning independent strings

For some interfaces, we will return a string:

@GetMapping("/get")
public String getStr(){
    //返回了一个字符串
    return "felord.cn";
}

We want this string to be processed by the unified return body, similar to this:

{
    code: 200,
    data: "felord.cn",
    msg: "返回成字符串",
}

But you will find that the desired effect is not achieved, and a type conversion exception will be thrown. This is because when our Spring MVC interfaces return data, based on Content-Type to select a HttpMessageConverter to deal with, but the string does not represent Content-Type the case of priority use StringHttpMessageConverter , has led to a translation exception, need to be set to MappingJackson2HttpMessageConverter with Jackson , the corresponding configuration of Spring MVC is as follows:

@Configuration(proxyBeanMethods = false)
public class SpringMvcConfiguration implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 解决 String 统一封装RestBody的问题
        converters.add(0, new MappingJackson2HttpMessageConverter());
    }
}

Well, it worked! Do you think it's over? You will find that your JSON serialization is not executed according to the strategy you set. Because you have new the one that uses system initialization. Solution for the the Spring the IoC in ObjectMapper injected into MappingJackson2HttpMessageConverter go. Or you use the Debug debugging the system default MappingJackson2HttpMessageConverter location, such as my index 7 , can be configured as follows:

@Configuration(proxyBeanMethods = false)
public class SpringMvcConfiguration implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 解决 String 统一封装RestBody的问题
        HttpMessageConverter<?> httpMessageConverter = converters.get(7);
        if (!(httpMessageConverter instanceof MappingJackson2HttpMessageConverter)) {
            // 确保正确,如果有改动就重新debug
            throw new RuntimeException("MappingJackson2HttpMessageConverter is not here");
        }
        converters.add(0, httpMessageConverter);
    }
}

Data type problem

An Android development classmate once said, data in your unified structure is an array:

{
    code: 200,
    data: ['a','b'],
    msg: "返回成字符串",
}

Subsequent if data adds other attributes that are not related to the array, it will be incompatible. You should ensure that this data is a Map . Yes, this is also a problem. In fact, it is found that it is not only an array. If it is a primitive type such as int , long String , it is necessary to add an additional judgment whether body is possible to change the type of data

private boolean checkPrimitive(Object body) {
    Class<?> clazz = body.getClass();
    return clazz.isPrimitive()
            || clazz.isArray()
            || Collection.class.isAssignableFrom(clazz)
            || body instanceof Number
            || body instanceof Boolean
            || body instanceof Character
            || body instanceof String;
}

Then we add a judgment to the implementation of ResponseBodyAdvice<T>

      // 增强扩展性
        if (checkPrimitive(body)) {
            return RestBody.okData(Collections.singletonMap("result", body));
        }

The problem is solved.

to sum up

Today, I shared some details of the unified return body in Spring Boot, hoping to help you solve some of the same problems encountered in actual development. more attention to: 16098d2f1e1194 Code Farmer Little share more useful programming knowledge.

Follow the public account: Felordcn for more information

personal blog: https://felord.cn


码农小胖哥
3.8k 声望8k 粉丝