自定义 Jackson HttpMessageConverter 在 Spring 4.2 中不再工作

2024-04-16

我正在将应用程序从 Spring Platform 版本 1.1.3.RELEASE 更新到 2.0.1.RELEASE,这会将 Spring Framework 版本从 4.1.7 升级到 4.2.4,将 Jackson 从 2.4.6 升级到 2.6.4。 Spring 或 Jackson 对自定义的处理似乎没有任何重大变化HttpMessageConverter实现,但我的自定义 JSON 序列化未能发生,而且我无法确定原因。以下内容在之前的 Spring Platform 版本中运行良好:

Model

@JsonFilter("fieldFilter")
public class MyModel { 
    /*model fields and methods*/ 
}

模型包装器

public class ResponseEnvelope {

    private Set<String> fieldSet;
    private Set<String> exclude;
    private Object entity;

    public ResponseEnvelope(Object entity) {
        this.entity = entity;
    }

    public ResponseEnvelope(Object entity, Set<String> fieldSet, Set<String> exclude) {
        this.fieldSet = fieldSet;
        this.exclude = exclude;
        this.entity = entity;
    }

    public Object getEntity() {
        return entity;
    }

    @JsonIgnore
    public Set<String> getFieldSet() {
        return fieldSet;
    }

    @JsonIgnore
    public Set<String> getExclude() {
        return exclude;
    }

    public void setExclude(Set<String> exclude) {
        this.exclude = exclude;
    }

    public void setFieldSet(Set<String> fieldSet) {
        this.fieldSet = fieldSet;
    }

    public void setFields(String fields) {
        Set<String> fieldSet = new HashSet<String>();
        if (fields != null) {
            for (String field : fields.split(",")) {
                fieldSet.add(field);
            }
        }
        this.fieldSet = fieldSet;
    }
}

控制器

@Controller
public class MyModelController {

    @Autowired MyModelRepository myModelRepository;

    @RequestMapping(value = "/model", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
    public HttpEntity find(@RequestParam(required=false) Set<String> fields, @RequestParam(required=false) Set<String> exclude){
        List<MyModel> objects = myModelRepository.findAll();
        ResponseEnvelope envelope = new ResponseEnvelope(objects, fields, exclude);
        return new ResponseEntity<>(envelope, HttpStatus.OK);
    }
}

Custom HttpMessageConverter

public class FilteringJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {

    private boolean prefixJson = false;

    @Override
    public void setPrefixJson(boolean prefixJson) {
        this.prefixJson = prefixJson;
        super.setPrefixJson(prefixJson);
    }

    @Override
    protected void writeInternal(Object object, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {

        ObjectMapper objectMapper = getObjectMapper();
        JsonGenerator jsonGenerator = objectMapper.getFactory().createGenerator(outputMessage.getBody());

        try {

            if (this.prefixJson) {
                jsonGenerator.writeRaw(")]}', ");
            }

            if (object instanceof ResponseEnvelope) {

                ResponseEnvelope envelope = (ResponseEnvelope) object;
                Object entity = envelope.getEntity();
                Set<String> fieldSet = envelope.getFieldSet();
                Set<String> exclude = envelope.getExclude();
                FilterProvider filters = null;

                if (fieldSet != null && !fieldSet.isEmpty()) {
                    filters = new SimpleFilterProvider()
                            .addFilter("fieldFilter", SimpleBeanPropertyFilter.filterOutAllExcept(fieldSet))
                                .setFailOnUnknownId(false);
                } else if (exclude != null && !exclude.isEmpty()) {
                    filters = new SimpleFilterProvider()
                            .addFilter("fieldFilter", SimpleBeanPropertyFilter.serializeAllExcept(exclude))
                                .setFailOnUnknownId(false);
                } else {
                    filters = new SimpleFilterProvider()
                            .addFilter("fieldFilter", SimpleBeanPropertyFilter.serializeAllExcept())
                                .setFailOnUnknownId(false);
                }

                objectMapper.setFilterProvider(filters);
                objectMapper.writeValue(jsonGenerator, entity);

            } else if (object == null){
                jsonGenerator.writeNull();
            } else {
                FilterProvider filters = new SimpleFilterProvider().setFailOnUnknownId(false);
                objectMapper.setFilterProvider(filters);
                objectMapper.writeValue(jsonGenerator, object);
            }

        } catch (JsonProcessingException e){
            e.printStackTrace();
            throw new HttpMessageNotWritableException("Could not write JSON: " + e.getMessage());
        }

    }
}

配置

@Configuration
@EnableWebMvc
public class WebServicesConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FilteringJackson2HttpMessageConverter jsonConverter = new FilteringJackson2HttpMessageConverter();
        jsonConverter.setSupportedMediaTypes(MediaTypes.APPLICATION_JSON);
        converters.add(jsonConverter);
    }

    // Other configurations
}

现在,在发出任何类型的请求时,我收到此异常(由 Spring 捕获并记录)和 500 错误:

[main] WARN  o.s.w.s.m.s.DefaultHandlerExceptionResolver - Failed to write HTTP message: 
  org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: 
  Can not resolve PropertyFilter with id 'fieldFilter'; 
  no FilterProvider configured (through reference chain:
  org.oncoblocks.centromere.web.controller.ResponseEnvelope["entity"]->java.util.ArrayList[0]); 
  nested exception is com.fasterxml.jackson.databind.JsonMappingException: 
  Can not resolve PropertyFilter with id 'fieldFilter'; 
  no FilterProvider configured (through reference chain: 
  org.oncoblocks.centromere.web.controller.ResponseEnvelope["entity"]->java.util.ArrayList[0])

The configureMessageConverters方法执行,但看起来在请求期间从未使用过自定义转换器。是否有可能另一个消息转换器阻止此消息转换器到达我的响应?我的理解是压倒一切configureMessageConverters将阻止使用除手动注册的转换器之外的转换器。

除了通过 Spring 平台更新依赖版本之外,此代码的工作版本和非工作版本之间没有进行任何更改。我在文档中遗漏的 JSON 序列化是否有任何更改?

Edit

进一步的测试会产生奇怪的结果。我想测试一下以检查以下内容:

  1. 是我的习惯HttpMessageConverter实际上正在注册吗?
  2. 另一个转换器是否会覆盖/取代它?
  3. 这只是我的测试设置的问题吗?

因此,我添加了一个额外的测试并查看了输出:

@Autowired WebApplicationContext webApplicationContext;

@Before
public void setup(){
    mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}

@Test
public void test() throws Exception {
    RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter) webApplicationContext.getBean("requestMappingHandlerAdapter");
    List<EntrezGene> genes = EntrezGene.createDummyData();
    Set<String> exclude = new HashSet<>();
    exclude.add("entrezGeneId");
    ResponseEnvelope envelope = new ResponseEnvelope(genes, new HashSet<String>(), exclude);
    for (HttpMessageConverter converter: adapter.getMessageConverters()){
        System.out.println(converter.getClass().getName());
        if (converter.canWrite(ResponseEnvelope.class, MediaType.APPLICATION_JSON)){
            MockHttpOutputMessage message =  new MockHttpOutputMessage();
            converter.write((Object) envelope, MediaType.APPLICATION_JSON, message);    
            System.out.println(message.getBodyAsString());
        }
    }
}

...而且效果很好。我的envelope对象及其内容已正确序列化和过滤。因此,要么请求处理在到达消息转换器之前存在问题,要么请求处理方式发生了变化MockMvc正在测试请求。


你的配置没问题。之所以writeInternal()未从您的自定义转换器调用是因为您覆盖了错误的方法。

查看源代码4.2.4.RELEASE

AbstractMessageConverterMethodProcessor#writeWithMessageConverters

protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
            ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    ...
    ((GenericHttpMessageConverter<T>) messageConverter).write(returnValue, returnValueType, selectedMediaType, outputMessage);
    ...
}

AbstractGenericHttpMessageConverter#write

public final void write(final T t, final Type type, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
    ...
    writeInternal(t, type, outputMessage);
    ...
}

方法writeInternal(...)从内部呼唤AbstractGenericHttpMessageConverter#write(...)有三个参数 -(T t, Type type, HttpOutputMessage outputMessage)。您正在覆盖重载版本writeInternal(...)只有 2 个参数 -(T t, HttpOutputMessage outputMessage).

不过在版本上4.1.7.RELEASE,情况并非如此,因此是问题的根本原因。这writeInternal(...)此版本中使用的是您已重写的另一个重载方法(具有 2 个参数的方法)。这解释了为什么它运行良好4.1.7.RELEASE.

@Override
public final void write(final T t, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
    ...
    writeInternal(t, outputMessage);
    ...
}

所以,要解决你的问题,而不是覆盖writeInternal(Object object, HttpOutputMessage outputMessage), 覆盖writeInternal(Object object, Type type, HttpOutputMessage outputMessage)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

自定义 Jackson HttpMessageConverter 在 Spring 4.2 中不再工作 的相关文章

  • 如果从 Java 启动,子进程将忽略 SIGQUIT

    举这个简单的例子 public class Main public static void main String args throws Exception Runtime getRuntime exec sleep 1000 This
  • 如何使用 JRuby 创建 Java 小程序?

    我想使用 JRuby 创建一个 Java 小程序 也就是说 我想创建一个 Java 小程序 其中包含由 JRuby 运行的 Ruby 代码来完成所有 GUI 操作 我正在寻找一个简单的示例来说明如何开始 查看这些链接 来自我们代码库的 JR
  • jQuery JSON 请求得到“200 OK”答案,但没有内容

    我正在使用 jQuery 通过访问者的 IP 地址获取其位置 有一项很棒的服务叫做免费地理IP http freegeoip appspot com 我需要做的就是在 URL 末尾添加 json 或 xml 然后添加 IP 地址 它将返回所
  • 在 LibGDX 中保存和检索图像文件

    如何在 LibGDX 中保存和检索图像文件 我想将图像文件保存在 AndroidApplication 类的本地存储中 并在我的 Core 项目中检索它 Libgdx 中的文件处理在libGDX 维基 https github com li
  • 为什么我的 android 项目中 onStart() 方法在 onCreate 之前运行?

    根据 Activity 的生命周期 onCreate 在应用创建时会被调用一次 然后 onStart 方法在整个 Activity 生命周期中可能会被调用多次 然而这并不是发生在我身上的事情 我的 onCreate 方法中有以下代码 mRe
  • org.dozer.MappingException:找不到字段的读取或写入方法

    org dozer MappingException 找不到字段的读取或写入方法 tarShipMethodCode lmCourier courierName 在类 class com essilor ong domain invento
  • 启用 WCF 数据服务默认接受/返回 JSON

    我有一个 WCF 数据服务 我希望默认情况下为所有操作返回 JSON 我可以在配置 通过服务属性中设置它吗 为了通过 format 标签启用 json 如下所示 host 8038 YourService svc format json 将
  • 如何在Java中读取文件的最后“n”个字节

    如何在不使用 RandomAccessFile 的情况下从文件中读取最后 n 个字节 我的文件中的最后 6 个字节包含写回文件时的重要信息 我需要写入原始文件 然后将最后 6 个字节附加到其他地方 有什么指导吗 谢谢 你必须使用随机存取文件
  • 如何在RecyclerView中实现setOnScrollListener

    当用户到达列表中可见的项目时 如何在底部显示进度栏 我已经编写了一个代码 其中我使用网络服务获取数据 现在我想填充部分记录 因为我有大约630 条记录在我的 JSON 中 这是我用来从 JSON 获取数据并填充到 RecyclerView
  • 模拟网络断开连接以在本地测试分布式应用程序分区

    我有几个在本地主机上运行的分布式应用程序实例 每个实例都通过某些端口与其他实例通信 所有实例一起构成一个整体 我实际上是在谈论动物园管理员 http hadoop apache org zookeeper 在 Linux 上运行 现在我想编
  • 使用 Scanner 类输入

    我从过去的经历中了解到的是nextInt or nextDouble 将继续搜索 直到在同一行或下一行中找到整数或双精度数 这并不重要 同时通过扫描器类读取字符串作为输入next 考虑空格之前的那些字符串并将光标保持在同一行 其中nextL
  • 如何使用Netbeans的不确定进度条样式?

    我正在使用 Nimbus 外观和感觉编写 Java 应用程序 不幸的是 Nimbus 外观和感觉的不确定 JProgressBars 的外观是AWFUL 见下文 另一方面 我注意到 Netbeans 与 Nimbus 的外观和感觉有不同的不
  • 如何阻止TreeItem选择?

    我正在与一个TreeTableView JavaFX 8 有一些树节点必须禁用才能选择 我已经尝试过选择活动 但它不起作用 请查找以下代码以获取更多信息 treeTableView getSelectionModel selectedIte
  • 像耐心/克朗代克纸牌游戏一样拖动节点

    我正在做克朗代克游戏 逻辑一切正常 我只是在使用 javafx 中的 UI 时遇到问题 我一直在尝试从 桌面堆 周围移动 拖动卡片 但没有达到预期的结果 我的卡片是一个 ImageView 里面有一个图像 这些卡片位于窗格内 Pane ta
  • 使用 FileOutputStream 创建 UTF-8 PDF 文件

    我正在使用 JasperReports 和 DynamicReports 以及这段 java 代码来创建包含 utf 8 字符的 pdf 格式的报告 问题是生成的 pdf 文件根本不包含 utf 8 字符 就像它们已被替换为 使用 Outp
  • Java 中客户端/服务器传输的压缩字符串

    我使用专有的客户端 服务器消息格式来限制我可以通过网络发送的内容 我无法发送序列化对象 我必须将消息中的数据存储为字符串 我发送的数据是大的逗号分隔值 我想在将数据作为字符串打包到消息中之前对其进行压缩 我尝试使用 Deflater Inf
  • JPA:@JoinColumn 和 @PrimaryKeyJoinColumn 之间的区别?

    两者之间的确切区别是什么 JoinColumn and PrimaryKeyJoinColumn You use JoinColumn对于属于外键一部分的列 典型的列可能如下所示 例如 在具有附加属性的连接表中 ManyToOne Join
  • Django (JSONField) 和 tastypie

    我通过使用 JSONField 在 mysql 中创建了一个 TextField django 类型的表 这就是我的模型的样子 from django db import models from json field import JSON
  • Maven 依赖项插件无法解析插件的手动指定依赖项

    我遇到了一个问题maven dependency plugin Maven版本3 2 3 maven dependency plugin版本2 10 我正在尝试引入插件依赖项 我创建了一个简单的项目
  • 在谷歌应用程序引擎中使用低级 API 进行数据存储?是不是很糟糕?

    关于如何使用数据存储的低级 api 的文档很少 而关于 JPA 和 JDO 以及如何转换为 JPA 和 JDO 的文档却很多 我的问题是 根据 JPA 或 JDO 规范进行编码而不是直接访问数据存储的低级 api 有什么优势吗 乍一看 这似

随机推荐