2016-01-22 9 views
11

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シリアル化は失敗しており、理由を特定できませんでした。以下は、前の春プラットフォームのリリースでは正常に動作します:カスタムジャクソンHttpMessageConverterはSpring 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プラットフォームによる依存バージョンの更新以外に変更はありません。 JSONのシリアライゼーションに変更がありましたか?

編集

さらなる試験は、奇妙な結果が得られます。

  1. 私のカスタムHttpMessageConverterは実際に登録されていますか?
  2. もう1つのコンバータがオーバーライド/オーバーライドしていますか?
  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(...)には3つの引数 - (T t, Type type, HttpOutputMessage outputMessage)が含まれています。 writeInternal(...)のオーバーロードされたバージョンは、2つの引数-(T t, HttpOutputMessage outputMessage)だけをオーバーライドしています。

ただし、バージョン4.1.7.RELEASEでは問題ではないため、問題の根本原因です。このバージョンで使用されているwriteInternal(...)は、オーバーライドしたもう1つのオーバーロードされたメソッド(引数が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

関連する問題