当前位置: 首页 > 知识库问答 >
问题:

Spring Jackson:在响应对象中包装响应体

简学文
2023-03-14

这可能是一个奇怪的问题,尽管我想知道为什么以前没有人提出过这个问题。。。因此,如果有任何无知,请纠正我。

首先,我将Jackson与Spring和@ResponseBody注释结合使用。目前,对于每个请求处理程序,我都返回一个“Response”包装器对象,这是客户机所期望的。这个包装非常简单:

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

问题是,我不喜欢显式地包装所有请求处理程序的每个返回值。我也不喜欢在单元测试中打开这些响应包装。

相反,我想知道是否有可能返回实际的_数据,并在其他地方截取和包装这些数据。

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

例如,像这样的东西将是惊人的(请注意,@FetchReport和@响应Wrapper是由建议的注释组成的):

@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个答案

郦祺
2023-03-14

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

Jackson不会正确序列化使用@JsonTypeInfo/@JsonSubTypes的多态类型,如果在运行时类型的值未知:例如,如果您有一个类似类响应包装器的通用容器类型

如果您只是实现ResponseBodyAdvice并从beforeBodyWrite()返回一个新的包装值,那么Spring将不再知道您的完整泛型类型及其专门化,它将把您的响应序列化为ResponseWrapper

解决此问题的唯一方法是从AbstractJackson2HttpMessageConverter扩展并重写writeInternal()。请参见此处的方法如何处理类型:https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java#L437
您还需要使用AbstractMappingJacksonResponseBodyAdvice和您自己的自定义MappingJacksonValue实现控制器建议,其中包括自定义HttpMessageConverter将使用的类型targetType

响应包装器

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

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

包装用具

@Component
public class WrappingAdvice extends AbstractMappingJacksonResponseBodyAdvice {
    

    @Override
    protected MappingJacksonValue getOrCreateContainer(Object body) {
        MappingJacksonValue cnt = super.getOrCreateContainer(body);
        if (cnt instanceof MyMappingJacksonValue) {
            return cnt;
        }

        return new MyMappingJacksonValue(cnt);
    }

    @Override
    protected void beforeBodyWriteInternal(
            MappingJacksonValue bodyContainer, MediaType contentType,
            MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {

        MyMappingJacksonValue cnt = (MyMappingJacksonValue) bodyContainer;

        Type targetType = getTargetType(bodyContainer.getValue(), returnType);

        cnt.setValue(new ResponseWrapper(cnt.getValue()));
        cnt.setTargetType(TypeUtils.parameterize(
                ResponseWrapper.class,
                targetType));
    }

    /**
     * This is derived from AbstractMessageConverterMethodProcessor
     */
    private Type getTargetType(Object value, MethodParameter returnType) {
        if (value instanceof CharSequence) {
            return String.class;
        }

        Type genericType;
        if (HttpEntity.class.isAssignableFrom(returnType.getParameterType())) {
            genericType = ResolvableType.forType(returnType.getGenericParameterType()).getGeneric().getType();
        } else {
            genericType = returnType.getGenericParameterType();
        }

        return GenericTypeResolver.resolveType(genericType, returnType.getContainingClass());
    }

    public static class MyMappingJacksonValue extends MappingJacksonValue {
        private Type targetType;

        public MyMappingJacksonValue(MappingJacksonValue other) {
            super(other.getValue());
            setFilters(other.getFilters());
            setSerializationView(other.getSerializationView());
        }

        public Type getTargetType() {
            return targetType;
        }

        public void setTargetType(Type targetType) {
            this.targetType = targetType;
        }
    }
}

JsonHttpMessageBodyConzer

@Component
public class JsonHttpMessageBodyConverter extends AbstractJackson2HttpMessageConverter {

    // omitted all constructors

    @Override
    protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        if (object instanceof WrapAPIResponseAdvice.MyMappingJacksonValue) {
            type = ((WrapAPIResponseAdvice.MyMappingJacksonValue) object).getTargetType();
        }

        super.writeInternal(object, type, outputMessage);
    }
}

羊舌青青
2023-03-14

对于任何想了解更多关于这个话题的信息的人:我也面临着同样的问题,多亏了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);
    }
}

此解决方案将控制器中返回的所有对象包装在一个(本例中)JSend响应类中,这为您节省了在所有控制器方法中返回JSend响应的麻烦。

谢高峯
2023-03-14

嗯,看起来我在找Spring的“响应身体建议”和“抽象映射杰克逊响应身体建议”。

 类似资料:
  • 来自API的Im有两个不同的响应。然后我判断对象,但是我如何将消息的内容返回到字符串呢?下面的示例返回Exception: Json响应: Json响应: 模型:

  • 我已经通过HTTP脚本记录器记录了一个脚本,但当我运行脚本时,我得到的对象移动到这里错误的登录事务的采样器上。当在记录的xml中搜索相同的请求时,我可以看到它给出了一个有效的输出。有人能告诉我为什么当我运行脚本时,只有它显示这个错误。请参考以下快照。 来自录制xml的请求:录制的\u请求\u正文 来自录制xml的请求头:录制的请求头 录制xml的请求-响应:录制的请求-响应 脚本请求:脚本请求正文

  • 概述 Django 使用Request 对象和Response 对象在系统间传递状态。 当请求一个页面时,Django会建立一个包含请求元数据的 HttpRequest 对象。 当Django 加载对应的视图时,HttpRequest 对象将作为视图函数的第一个参数。每个视图会返回一个HttpResponse 对象。 本文档对HttpRequest 和HttpResponse 对象的API 进行说

  • Koa Response对象是节点的vanilla响应对象之上的抽象,提供了对日常HTTP服务器开发有用的附加功能。 Koa响应对象嵌入在上下文对象中, this 。 每当我们收到请求时,让我们注销响应对象。 var koa = require('koa'); var router = require('koa-router'); var app = koa(); var _ = router()

  • 当响应被关闭时,容器必须立即刷出响应缓冲区中的所有剩余的内容到客户端。以下事件表明 servlet 满足了请求且响应对象即将关闭: servlet 的 service 方法终止。 响应的 setContentLength 或 setContentLengthLong 方法指定了大于零的内容量,且已经写入到响应。 sendError 方法已调用。5.6 sendRedirect 方法已调用。 Asy

  • res对象表示Express应用程序在收到HTTP请求时发送的HTTP响应。 响应对象属性 以下是与响应对象关联的一些属性的列表。 Sr.No. 属性和描述 1 res.app 此属性包含对使用中间件的快速应用程序实例的引用。 2 res.headersSent 布尔属性,指示应用程序是否为响应发送了HTTP标头。 3 res.locals 包含作用于请求的响应局部变量的对象 响应对象方法 res