2016-01-22 5 views
11

Ich aktualisiere eine Anwendung von Spring Platform Version 1.1.3.RELEASE zu 2.0.1.RELEASE, die die Spring Framework-Version von 4.1.7 zu 4.2.4 und Jackson bumps von 2.4.6 bis 2.6.4. Es scheint keine wesentlichen Änderungen in Spring oder Jacksons Handhabung von benutzerdefinierten Implementierungen gegeben zu haben, aber meine benutzerdefinierte JSON-Serialisierung tritt nicht auf und ich konnte nicht feststellen, warum. Die folgenden funktioniert gut in der vorherigen Version Frühlings-Plattform:Benutzerdefinierte Jackson HttpMessageConverter funktioniert nicht mehr in Frühling 4.2

Modell

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

Modell Wrapper

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

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

    } 
} 

Konfiguration

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

Jetzt habe ich diese Ausnahme bin immer (die von Frühling gefangen und protokolliert) und einen 500-Fehler, wenn jede Art von Anfrage zu machen:

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

Die configureMessageConverters Methode wird ausgeführt, aber es sieht nicht so aus, als ob ein benutzerdefinierter Converter während Anfragen verwendet wird. Ist es möglich, dass ein anderer Nachrichtenkonverter verhindert, dass dieser meine Antwort erreicht? Mein Verständnis war, dass das Überschreiben configureMessageConverters verhindern würde, dass andere Konverter als die manuell registrierten verwendet werden.

Es wurden keine Änderungen zwischen den funktionierenden und nicht funktionierenden Versionen dieses Codes vorgenommen, abgesehen von der Aktualisierung von Abhängigkeitsversionen über die Spring Platform. Gab es Änderungen in der JSON-Serialisierung, die ich gerade in der Dokumentation vermisse?

bearbeiten

Weitere Tests Ausbeuten seltsame Ergebnisse. Ich wollte testen, um die folgenden Dinge zu überprüfen:

  1. Ist meine benutzerdefinierte HttpMessageConverter tatsächlich registriert?
  2. Überschreibt ein anderer Konverter es/ersetzt es?
  3. Ist das nur ein Problem mit meiner Testkonfiguration?

Also, habe ich einen zusätzlichen Test und nahm einen Blick auf die Ausgabe:

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

... und es funktioniert gut. Mein envelope Objekt und seine Inhalte werden serialisiert und korrekt gefiltert. Entweder gibt es ein Problem mit der Verarbeitung von Anforderungen, bevor es die Nachrichtenkonverter erreicht, oder es hat sich geändert, wie MockMvc Anforderungen testet.

+1

Tropfen einen Haltepunkt in 'configureMessageConverters' und stellen Sie sicher, dass es ausgeführt wird. – chrylis

+0

@chrylis: Habe das schon probiert, die Konfigurationsmethode wird ausgeführt. – woemler

+0

Können Sie einen vollständigen StackTrace bereitstellen? –

Antwort

9

Ihre Konfiguration ist in Ordnung. Der Grund, warum writeInternal() nicht von Ihrem benutzerdefinierten Konverter aufgerufen wird, liegt daran, dass Sie die falsche Methode überschreiben.

an der Codequelle 4.2.4.RELEASE Suchen

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 # schreiben

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

Die ca Methode writeInternal(...) gefüllte aus innerhalb AbstractGenericHttpMessageConverter#write(...) hat drei Argumente - (T t, Type type, HttpOutputMessage outputMessage). Sie überschreiben die überladene Version von writeInternal(...), die nur 2 Argumente hat - (T t, HttpOutputMessage outputMessage).

In der Version 4.1.7.RELEASE ist dies jedoch nicht der Fall, daher die Ursache Ihres Problems. Die in dieser Version verwendete writeInternal(...) ist die andere überladene Methode (die Methode mit 2 Argumenten), die Sie überschrieben haben. Dies erklärt, warum es in funktioniert.

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

Also, Ihr Problem zu lösen, anstatt writeInternal(Object object, HttpOutputMessage outputMessage) vorran, außer Kraft setzen writeInternal(Object object, Type type, HttpOutputMessage outputMessage)

+0

Das hat den Trick! Ich danke dir sehr! Es ist rätselhaft, warum sie beschlossen haben, die Funktionalität dieser Methode zu ändern, aber zumindest habe ich eine bessere Vorstellung davon, was jetzt unter der Haube passiert. – woemler

+0

Der Trick besteht darin, die Spring-Logs auf ALL zu setzen, um eine nette Geschichte von dem zu bekommen, was sich im Inneren abspielt :) – Bnrdo

Verwandte Themen