前言

你是否在项目中或大部分开源项目中看见如下代码?

public Result<List<Entity>> getxxx(){
    ...
    return Result.ok(data);
} 

上述代码是一个非常简单的controller代码实例,使用通用的返回值包装数据。

但是这种写法写一次两次可能说还可以,但是在你的项目中出现大量的Result<泛型>这就使得你的项目变得愈发臃肿,controller中也存在这大量的重复代码。

那么有没有一种办法去解决这种统一返回值的处理呢?

答案是:有的!

ResponseBodyAdvice

ResponseBodyAdvice是SpringMVC提供的一个接口,方便开发者处理你的响应体,也就是我们后端接口返回的json数据/xml数据。

public interface ResponseBodyAdvice<T> {

	/**
	 * Whether this component supports the given controller method return type
	 * and the selected {@code HttpMessageConverter} type.
	 * @param returnType the return type
	 * @param converterType the selected converter type
	 * @return {@code true} if {@link #beforeBodyWrite} should be invoked;
	 * {@code false} otherwise
	 */
	boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);

	/**
	 * Invoked after an {@code HttpMessageConverter} is selected and just before
	 * its write method is invoked.
	 * @param body the body to be written
	 * @param returnType the return type of the controller method
	 * @param selectedContentType the content type selected through content negotiation
	 * @param selectedConverterType the converter type selected to write to the response
	 * @param request the current request
	 * @param response the current response
	 * @return the body that was passed in or a modified (possibly new) instance
	 */
	@Nullable
	T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
			Class<? extends HttpMessageConverter<?>> selectedConverterType,
			ServerHttpRequest request, ServerHttpResponse response);

}

ResponseBodyAdvice提供了两个方法需要我们去实现

  • supports (是否支持/允许进行返回值处理)

  • beforeBodyWrite (写入ResponseBody之前的方法,需返回具体返回的ResponseBody对象)

如何使用

在SpringBoot项目使用ResponseBodyAdvice需要确保你的pom中成功加载了Spring-web(SpringMVC)。

以gradle为实例,需要在你的build.gradle.kts中添加Spring-web。

dependencies {
    ...
    implementation("org.springframework.boot:spring-boot-starter-web")
    ... //其他依赖
}

使用Java/Kotlin创建class ApiResultAdvice并实现ResponseBodyAdvice接口,并指定泛型为Object(因为Java所有类的父类均为Object,我们要对所有返回值进行处理,故此这里是Object)

@RestControllerAdvice
public class ApiResultAdvice implements ApiResultAdvice<Object> {
    
    private final ObjectMapper objectMapper;    

    @Autowired
    public ApiResultAdvice(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper
    }

    boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        //根据项目需求
        return true;
    }

    T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
			Class<? extends HttpMessageConverter<?>> selectedConverterType,
			ServerHttpRequest request, ServerHttpResponse response) {
        if (Void.class.equals(returnType.parameterType)) {
            return Result.ok(xxx); //void类型直接返回
        }
        if (body instanceof String) {
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            return objectMapper.writeValueAsString(Result.ok(xxx)); //处理字符串返回,自动包装成Json
        }
        return Result.ok(xxx);
    }
}
@RestControllerAdvice
class ApiResultAdvice(
    val objectMapper: ObjectMapper
) : ResponseBodyAdvice<Any> {
    override fun supports(returnType: MethodParameter, converterType: Class<out HttpMessageConverter<*>>): Boolean {
        //根据项目需求
        return true;
    }

    override fun beforeBodyWrite(
        body: Any?,
        returnType: MethodParameter,
        selectedContentType: MediaType,
        selectedConverterType: Class<out HttpMessageConverter<*>>,
        request: ServerHttpRequest,
        response: ServerHttpResponse
    ): Any? {
        // 处理无返回值(即 void)类型的方法
        if (returnType.parameterType == Void::class.java) {
            return Result.success(message)
        }
        // 处理 String 类型的返回值
        if (body is String) {
            // 用于重置字符串的响应类型为 application/json
            response.headers.contentType = MediaType.APPLICATION_JSON
            // 将统一对象转换成 json 字符串
            return objectMapper.writeValueAsString(Result.success(message, body))
        }
        // 其他情况
        return Result.success(message, body)
    }
}

使用上述代码就可以将你的api接口包装成返回值统一的接口了,还减少了大量冗余代码。

是不是很神奇呢?赶快在你的项目中试一试吧!