自定义Jackson HttpMessageConverter不再适用于Spring 4.2 [英] Custom Jackson HttpMessageConverter no longer works in Spring 4.2

查看:783
本文介绍了自定义Jackson HttpMessageConverter不再适用于Spring 4.2的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在将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版本中运行良好:

I am updating an application from Spring Platform version 1.1.3.RELEASE to 2.0.1.RELEASE, which bumps the Spring Framework version from 4.1.7 to 4.2.4, and Jackson from 2.4.6 to 2.6.4. There does not seem to have been any significant changes in Spring or Jackson's handling of custom HttpMessageConverter implementations, but my custom JSON serialization is failing to occur, and I have not been able to determine why. The following works fine in the previous Spring Platform release:

模型

@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);
    }
}

自定义 HttpMessageConverter

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错误:

Now I am getting this exception (which is caught by Spring and logged) and a 500 error when making any sort of request:

[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])

执行 configureMessageConverters 方法,但它看起来不像自定义转换器在请求期间使用过。是否有可能另一个消息转换器阻止此消息转换到我的响应?我的理解是,重写 configureMessageConverters 会阻止使用手动注册的转换器以外的其他转换器。

The configureMessageConverters method executes, but it does not look like custom converter is ever utilized during requests. Is it possible that another message converter could be preventing this one from reaching my response? My understanding was that overriding configureMessageConverters would prevent converters other than the manually registered ones from being used.

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

No changes have been made between the working and non-working versions of this code, besides updating dependency versions via the Spring Platform. Has there been any change in the JSON serialization that I am just missing in the documentation?

编辑

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

Further testing yields strange results. I wanted to test to check the following things:


  1. 我的自定义 HttpMessageConverter 实际上正在注册?

  2. 是否有另一个转换器覆盖/取代它?

  3. 这只是我的测试设置有问题吗?

  1. Is my custom HttpMessageConverter actually being registered?
  2. Is another converter overriding/superseding it?
  3. Is this a problem with my test setup only?

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

So, I added an extra test and took a look at the output:

@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());
        }
    }
}

...它的工作原理精细。我的信封对象及其内容被序列化并正确过滤。因此,请求处理在到达消息转换器之前存在问题,或者 MockMvc 正在测试请求的方式发生了变化。

...and it works fine. My the envelope object and its contents are serialized and filtered correctly. So either there is an issue with the request handling before it reaches the message converters, or there has been a change in how MockMvc is testing requests.

推荐答案

您的配置正常。不从您的自定义转换器调用 writeInternal()的原因是因为您覆盖了错误的方法。

Your configuration is ok. The reason why writeInternal() is not called from your custom converter is because you are overriding the wrong method.

查看的源代码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,类型类型,HttpOutputMessage outputMessage)。你重写了只有2个参数的 writeInternal(...)的重载版本 - (T t,HttpOutputMessage outputMessage)

The method writeInternal(...) called from within AbstractGenericHttpMessageConverter#write(...) has three arguments - (T t, Type type, HttpOutputMessage outputMessage). You are overriding the overloaded version of writeInternal(...) that has only 2 arguments - (T t, HttpOutputMessage outputMessage).

但是,在版本 4.1.7.RELEASE 中,情况并非如此,因此根你的问题的原因。此版本中使用的 writeInternal(...)是您重写的另一个重载方法(带有2个参数的方法)。这解释了为什么它在 4.1.7.RELEASE 中正常工作。

However, in version 4.1.7.RELEASE, it is not the case, hence the root cause of your problem. The writeInternal(...) used in this version is the other overloaded method (the method with 2 arguments) that you have overriden. This explains why it is working fine in 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)

So, to solve your problem, instead of overriding writeInternal(Object object, HttpOutputMessage outputMessage), override writeInternal(Object object, Type type, HttpOutputMessage outputMessage)

这篇关于自定义Jackson HttpMessageConverter不再适用于Spring 4.2的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆