2016-01-22 22 views
11

Sto aggiornando un'applicazione dalla piattaforma Spring versione 1.1.3.RELEASE a 2.0.1.RELEASE, che esegue il bump della versione di Spring Framework dalla 4.1.7 alla 4.2.4, e Jackson dal 2.4.6 al 2.6.4. Non sembrano esserci stati cambiamenti significativi nella gestione di Spring o Jackson delle implementazioni personalizzate HttpMessageConverter, ma la serializzazione JSON personalizzata non si verifica e non sono stato in grado di determinarne il motivo. Le seguenti opere eccellenti nella precedente versione Primavera Piattaforma:Custom Jackson HttpMessageConverter non funziona più nella primavera 4.2

Modello

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

Modello involucro

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

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

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

    } 
} 

configurazione

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

Ora sto ottenendo questa eccezione (che viene catturato da Primavera e loggati) e un errore 500 quando si effettua qualsiasi tipo di richiesta:

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

Il metodoviene eseguito, ma non sembra che il convertitore personalizzato sia mai stato utilizzato durante le richieste. È possibile che un altro convertitore di messaggi possa impedire a questo di raggiungere la mia risposta? La mia comprensione è stata che l'override di configureMessageConverters avrebbe impedito l'utilizzo di convertitori diversi da quelli registrati manualmente.

Nessuna modifica è stata apportata tra le versioni funzionanti e non funzionanti di questo codice, oltre all'aggiornamento delle versioni di dipendenza tramite la piattaforma Spring. C'è stata qualche modifica nella serializzazione JSON che mi manca nella documentazione?

Modifica

Ulteriori rendimenti di test risultati strani. Volevo testare per verificare le seguenti cose:

  1. È in corso la registrazione della mia HttpMessageConverter personalizzata?
  2. Un altro convertitore lo sovrascrive/lo sostituisce?
  3. Si tratta di un problema solo con la configurazione di prova?

Così, ho aggiunto un test in più e ha dato un'occhiata in uscita:

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

... e funziona benissimo. L'oggetto envelope e il suo contenuto sono serializzati e filtrati correttamente. Quindi, c'è un problema con la gestione delle richieste prima che raggiunga i convertitori di messaggi, oppure c'è stato un cambiamento nel modo in cui MockMvc sta testando le richieste.

+1

goccia un punto di interruzione in 'configureMessageConverters' e assicurarsi che sia in esecuzione. – chrylis

+0

@chrylis: già provato, il metodo di configurazione è in esecuzione. – woemler

+0

Potete fornire lo stacktrace completo? –

risposta

9

La configurazione è ok. Il motivo per cui writeInternal() non viene chiamato dal tuo convertitore personalizzato è perché stai ignorando il metodo sbagliato.

Guardando il codice sorgente di 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 # scrivere

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

Il metodo writeInternal(...) ca Lled from AbstractGenericHttpMessageConverter#write(...) ha tre argomenti - (T t, Type type, HttpOutputMessage outputMessage). Stai ignorando la versione sovraccaricata di writeInternal(...) che ha solo 2 argomenti - (T t, HttpOutputMessage outputMessage).

Tuttavia, nella versione 4.1.7.RELEASE, non è il caso, quindi la causa principale del problema. Lo writeInternal(...) utilizzato in questa versione è l'altro metodo sovraccarico (il metodo con 2 argomenti) che hai sostituito. Questo spiega perché sta funzionando bene in 4.1.7.RELEASE.

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

Così, per risolvere il problema, invece di rilevante writeInternal(Object object, HttpOutputMessage outputMessage), ignorare writeInternal(Object object, Type type, HttpOutputMessage outputMessage)

+0

Questo ha fatto il trucco! Grazie mille! È sconcertante il motivo per cui hanno deciso di cambiare la funzionalità di questo metodo, ma almeno ho un'idea migliore di ciò che sta succedendo ora. – woemler

+0

Il trucco è impostare i log Spring su ALL per ottenere una bella storia di cosa sta accadendo all'interno :) – Bnrdo

Problemi correlati