Wie können wir den internen Jackson-Mapper bei Verwendung von RestTemplate konfigurieren?

78

Ich möchte die SerializationConfig.Feature ... -Eigenschaften des von Spring RestTemplate verwendeten Jackson-Mappers aktualisieren. Jede Idee, wie ich darauf zugreifen kann oder wo ich es konfigurieren kann / sollte.

Usman Ismail
quelle

Antworten:

97

Der Standardkonstruktor RestTemplateregistriert eine Reihe von HttpMessageConverters:

this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter());
this.messageConverters.add(new SourceHttpMessageConverter());
this.messageConverters.add(new XmlAwareFormHttpMessageConverter());
if (jaxb2Present) {
    this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if (jacksonPresent) {
    this.messageConverters.add(new MappingJacksonHttpMessageConverter());
}
if (romePresent) {
    this.messageConverters.add(new AtomFeedHttpMessageConverter());
    this.messageConverters.add(new RssChannelHttpMessageConverter());
}

Das MappingJacksonHttpMessageConverterwiederum erstellt ObjectMapperInstanz direkt. Sie können diesen Konverter entweder finden und ersetzen ObjectMapperoder einen neuen registrieren. Das sollte funktionieren:

@Bean
public RestOperations restOperations() {
    RestTemplate rest = new RestTemplate();
    //this is crucial!
    rest.getMessageConverters().add(0, mappingJacksonHttpMessageConverter());
    return rest;
}

@Bean
public MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter() {
    MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter();
    converter.setObjectMapper(myObjectMapper());
    return converter;
}

@Bean
public ObjectMapper myObjectMapper() {
    //your custom ObjectMapper here
}

In XML ist es etwas in dieser Richtung:

<bean id="restOperations" class="org.springframework.web.client.RestTemplate">
    <property name="messageConverters">
        <util:list>
            <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
                <property name="objectMapper" ref="customObjectMapper"/>
            </bean>
        </util:list>
    </property>
</bean>

<bean id="customObjectMapper" class="org.codehaus.jackson.map.ObjectMapper"/>

Beachten Sie, dass der Übergang nicht wirklich 1: 1 ist. Ich muss eine messageConvertersListe explizit in XML erstellen, während @Configurationich mit dem Ansatz auf eine vorhandene Liste verweisen und sie einfach ändern kann. Das sollte aber funktionieren.

Tomasz Nurkiewicz
quelle
Ich wäre Ihnen dankbar, wenn Sie dies in XML übersetzen könnten. Ich möchte dies wirklich versuchen, bin mir aber nicht sicher, wie ich es konfigurieren soll. Danke vielmals!
Justin
9
Ich bin mir nicht sicher, ob dies nur auf einen Versionsunterschied zurückzuführen ist, aber ich glaube, der korrekte Name für die Jackson-Konverterklasse lautet MappingJackson * 2 * HttpMessageConverter
nbrooks
2
Ich würde wahrscheinlich vermeiden, einen neuen MappingJacksonHttpMessageConverter am Anfang der vorhandenen Standardkonverter hinzuzufügen, aber stattdessen den vorhandenen Jackson-Nachrichtenkonverter ersetzen. Andernfalls haben Sie 2 Jackson-Nachrichtenkonverter in der Liste.
Mvogiatzis
Ich habe versucht, es bei Index 0 hinzuzufügen, und ich habe einige sehr seltsame JSON-Analyse-Ausnahmen erhalten. Das Entfernen der vorhandenen funktionierte besser als von @mvogiatzis vorgeschlagen
Mads Hoel
Ich war ändern müssen RestOperations -> RestTemplatein @BeanDefinition auf diese Arbeiten machen
Stand - alone
29

Wenn Sie Spring IOC nicht verwenden, können Sie Folgendes tun (Java 8):

ObjectMapper objectMapper = new ObjectMapper();
// configure your ObjectMapper here

RestTemplate restTemplate = new RestTemplate();    

MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
messageConverter.setPrettyPrint(false);
messageConverter.setObjectMapper(objectMapper);
restTemplate.getMessageConverters().removeIf(m -> m.getClass().getName().equals(MappingJackson2HttpMessageConverter.class.getName()));
restTemplate.getMessageConverters().add(messageConverter);
Matt Sidesinger
quelle
1
Warum müssen Sie entfernen und erneut hinzufügen, wenn Sie nur feststellen können, dass es iteriert?
lqbweb
1
restTemplate.getMessageConverters (). removeIf (m -> m.getClass (). isAssignableFrom (MappingJackson2HttpMessageConverter.class));
Rüdiger Schulz
14

RestTemplate initialisiert seine Standardnachrichtenkonverter. Sie sollten die MappingJackson2HttpMessageConverterdurch Ihre eigene Bean ersetzen , die den Objekt-Mapper verwenden sollte, den Sie verwenden möchten. Das hat bei mir funktioniert:

@Bean
public RestTemplate restTemplate() {
    final RestTemplate restTemplate = new RestTemplate();

    //find and replace Jackson message converter with our own
    for (int i = 0; i < restTemplate.getMessageConverters().size(); i++) {
        final HttpMessageConverter<?> httpMessageConverter = restTemplate.getMessageConverters().get(i);
        if (httpMessageConverter instanceof MappingJackson2HttpMessageConverter){
            restTemplate.getMessageConverters().set(i, mappingJackson2HttpMessageConverter());
        }
    }

    return restTemplate;
}

@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setObjectMapper(myObjectMapper());
    return converter;
}

@Bean
public ObjectMapper myObjectMapper() {
    // return your own object mapper
}
mvogiatzis
quelle
Es scheint wichtig zu sein, die Methode so zu benennen, myObjectMapper()aber nicht nur objectMapper(). Aus irgendeinem Grund wird die Methode anscheinend nie aufgerufen und Sie erhalten die Standardeinstellung ObjectMapper.
Hermann
1
Das hat funktioniert, aber scheint die Antwort einen Tippfehler zu enthalten? restTemplate.getMessageConverters().set(i, mappingJackson2HttpMessageConverter);sollte sein restTemplate.getMessageConverters().set(i, mappingJackson2HttpMessageConverter());(beachten Sie die Klammern in MappingJackson2HttpMessageConverter (), was es zu einem Methodenaufruf anstelle einer Referenz macht)
Jens Wegar
True, oder Sie können alternativ das mappingJackson2HttpMessageConverterals Parameter zur restTemplateFunktion hinzufügen und es so verwenden, wie es ist.
mvogiatzis
1
Sie müssen keinen neuen erstellen, sondern können Ihren Mapper einfach festlegen, wenn (httpMessageConverter-Instanz von MappingJackson2HttpMessageConverter) {((MappingJackson2HttpMessageConverter) httpMessageConverter) .setObjectMapper (mapper ()); }
Kalpesh Soni
In Java8 können Sie Streams verwenden oder in Java14 können Sie beispielsweise den Mustervergleich verwenden. Ansonsten schöne deklarative Lösung.
Andrej Buday
6

Um die anderen Antworten vervollständigen: wenn Ihr ObjectMapperRegister nur Jackson Modulemit benutzerdefinierten Serializer / Deserializer, möchten Sie vielleicht Ihr Modul direkt auf das bestehende registrieren ObjectMappervon RestTemplate‚s Standard MappingJackson2HttpMessageConverterwie folgt (Beispiel ohne DI aber das gleiche gilt , wenn DI verwenden):

    SimpleModule module = new SimpleModule();
    module.addSerializer(...);
    module.addDeserializer(...);

    MappingJackson2HttpMessageConverter messageConverter = restTemplate.getMessageConverters().stream()
                    .filter(MappingJackson2HttpMessageConverter.class::isInstance)
                    .map(MappingJackson2HttpMessageConverter.class::cast)
                    .findFirst().orElseThrow( () -> new RuntimeException("MappingJackson2HttpMessageConverter not found"));
    messageConverter.getObjectMapper().registerModule(module);

Auf diese Weise können Sie die Konfiguration des Originals ObjectMapper(wie von Spring's durchgeführt Jackson2ObjectMapperBuilder) abschließen , anstatt es zu ersetzen.

Olivier Gérardin
quelle
6

Mit Spring Boot ist es so einfach wie:

RestTemplate template = new RestTemplateBuilder()
                            .additionalMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper))
                            .build()

(Getestet mit Spring Boot 2.2.1)

Wim Deblauwe
quelle
Mit diesem Ansatz RestTemplatehaben Sie nur einen MessageConverter: MappingJackson2HttpMessageConverter. Aber ich mag es immer noch
stehe allein
additionalMessageConvertersist nicht wirklich additiv. Es wird ein vollständiger Austausch durchgeführt und Sie verlieren andere Konverter. Es steht auf dem Dokument. (Es wird zur Liste der Konverter des Builders hinzugefügt, die die Instanz vollständig ersetzen.)
Ming