2016-01-22 35 views
11

我更新的春天平台版本1.1.3.RELEASE到2.0.1.RELEASE一个应用程序,它从颠簸4.1.7 Spring框架的版本4.2.4,和杰克逊从2.4.6到2.6.4。似乎没有一直在春季或杰克逊的处理的定制HttpMessageConverter实现任何显著的变化,但我的自定义JSON序列化失败发生,我一直无法确定原因。下面的工作在前面的春天平台释放细粒:自杰克逊HttpMessageConverter不再工作在春季4.2

型号

​​

型号包装

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

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 
} 

现在我得到这个异常(这是由春天捕获和记录),使任何类型的请求时出现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]) 

configureMessageConverters方法执行,但它看起来不像自定义转换器在请求期间被使用过。是否有可能另一个消息转换器可以阻止这个人得到我的回应?我的理解是压倒一切的configureMessageConverters将阻止比正在使用的手动注册之外的其它转换器。

除了通过Spring Platform更新依赖版本之外,未对此代码的工作版本和非工作版本进行更改。在文档中我只是缺少JSON序列化中的任何更改吗?

编辑

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

  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测试请求方面发生了变化。

+1

下降'configureMessageConverters'一个断点,并确保它的执行。 – chrylis

+0

@chrylis:已经试过这个,配置方法正在执行。 – woemler

+0

你能提供完整的堆栈跟踪吗? –

回答

9

你的配置没问题。 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#写

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

方法writeInternal(...) CA从AbstractGenericHttpMessageConverter#write(...)内部有三个参数 - (T t, Type type, HttpOutputMessage outputMessage)。您正在重写只有2个参数的writeInternal(...)的重载版本 - (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)

+0

这样做的窍门!非常感谢!令人费解的是,他们为什么决定改变这种方法的功能,但至少我现在已经更好地了解了现在发生了什么。 – woemler

+0

诀窍是将Spring日志设置为ALL,以获取关于发生的事情的好故事:) – Bnrdo