Spring + Jackson:在响应对象中包装Response Body

时间:2014-08-19 03:21:24

标签: java spring jackson

这可能是一个奇怪的问题,虽然我想知道为什么它之前没有被问过或提出过......所以如果有任何无知,请纠正我。

首先,我使用Jackson和Spring以及@ResponseBody注释。 目前,对于每个请求处理程序,我都会返回一个"响应"包装器对象,因为这是客户期望的。这个包装很简单:

{ "response": { "data" : ACTUAL_DATA } }

事情是,我不是明确包装所有请求处理程序的每个返回值的粉丝。我也不喜欢在我的单元测试中打开这些响应包装器。

相反,我想知道是否可以按原样返回ACTUAL_DATA,并拦截并将这些数据包装到别处。

如果这实际上是可行的,那么是否可以读取附加到截获的请求处理程序的注释?这样我就可以使用自定义注释来决定如何包装数据。

例如,这样的事情会很棒(注意@FetchResponse和@ResponseWrapper是由建议的注释构成的):

@RequestMapping(...)
@FetchResponse
@ResponseBody
public List<User> getUsers() {
    ...
}

@ResponseWrapper(FetchResponse.class)
public Object wrap(Object value) {
    ResponseWrapper rw = new ResponseWrapper();
    rw.setData(value);
    return rw;
}

任何熟悉此领域的人?或者,为什么这可能是不好的做法?

3 个答案:

答案 0 :(得分:1)

好吧,看起来我正在寻找Spring的“ResponseBodyAdvice”和“AbstractMappingJacksonResponseBodyAdvice”。

答案 1 :(得分:1)

对于有关此主题的更多信息的任何人:我也面临同样的问题,并且由于kennyg的提示,我设法提出以下解决方案:

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

@ControllerAdvice
public class JSendAdvice implements ResponseBodyAdvice<Object> {

    @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) {
        if (body instanceof JSendResponse) {
            return body;
        }

        return new JSendResponse<>().success(body);
    }
}

此解决方案将控制器中返回的所有对象包装在(对于此示例)JSendResponse类中,这样可以省去在所有控制器方法中返回JSendResponses的麻烦。

答案 2 :(得分:0)

我知道答案被接受已经有一段时间了,但我最近偶然发现了 Jackson 的一个问题,这让我发现了使用 ResponseBodyAdvice 的问题。

如果在运行时类型的值未知,Jackson 将不会正确序列化使用 @JsonTypeInfo / @JsonSubTypes 的多态类型:例如,如果您有一个像 {{1} 这样的通用容器类型}}。也就是说,除非您在要求 Jackson 序列化您的值之前向 Jackson 提供该泛型类型的特化,请参阅 Why does Jackson polymorphic serialization not work in lists? 。当您返回一个 T 列表并且 class ResponseWrapper<T> { List<T> objects; } 是已知的时,Spring 会为您执行此操作,因为它在方法返回类型中明确提供(如 T)。

如果您只是实现 public List<MyEntity> getAllEntities(); 并从 ResponseBodyAdvice 返回一个新的包装值,那么 Spring 将不再知道您的完整泛型类型及其特化,它会将您的响应序列化为 {{1} } 而不是 beforeBodyWrite()

解决此问题的唯一方法是从 ResponseWrapper<?> 扩展并覆盖 ResponseWrapper<MyEntity>。在此处查看该方法如何处理类型:https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java#L437
并且您还需要使用 AbstractJackson2HttpMessageConverter 和您自己的自定义 writeInternal() 实现控制器通知,其中包括自定义 HttpMessageConverter 将使用的 AbstractMappingJacksonResponseBodyAdvice

响应包装器

MappingJacksonValue

包装建议

Type targetType

JsonHttpMessageBodyConverter

public class ResponseWrapper<T> {
    @Nullable Error error;
    T result;

    public ResponseWrapper(T result) {
        this.result = result;
    }
}