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:
- Ist meine benutzerdefinierte
HttpMessageConverter
tatsächlich registriert? - Überschreibt ein anderer Konverter es/ersetzt es?
- 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.
Tropfen einen Haltepunkt in 'configureMessageConverters' und stellen Sie sicher, dass es ausgeführt wird. – chrylis
@chrylis: Habe das schon probiert, die Konfigurationsmethode wird ausgeführt. – woemler
Können Sie einen vollständigen StackTrace bereitstellen? –