我想更新 Spring RestTemplate 使用的 jackson 映射器的 SerializationConfig.Feature... 属性,知道如何访问它或者我可以/应该在哪里配置它。
默认的
RestTemplate
构造函数注册一组 HttpMessageConverter
:
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());
}
MappingJacksonHttpMessageConverter
依次直接创建ObjectMapper
实例。您可以找到此转换器并替换 ObjectMapper
或在其之前注册一个新转换器。这应该有效:
@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
}
在 XML 中,它是这样的:
<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"/>
请注意,转换并不是真正的 1:1 - 我必须在 XML 中显式创建
messageConverters
列表,而使用 @Configuration
方法我可以引用现有列表并简单地对其进行修改。但这应该有效。
如果您不使用 Spring IOC,您可以执行以下操作(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);
RestTemplate 将初始化其默认消息转换器。您应该将
MappingJackson2HttpMessageConverter
替换为您自己的 bean,该 bean 应该使用您想要使用的对象映射器。这对我有用:
@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
}
要完成其他答案:如果您的
ObjectMapper
只是使用自定义序列化器/反序列化器注册 Jackson Module
,您可能需要直接在现有 ObjectMapper
上从 RestTemplate
的默认 MappingJackson2HttpMessageConverter
注册您的模块,如下所示如下(没有 DI 的示例,但如果使用 DI,则同样适用):
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);
这将允许您完成原始
ObjectMapper
的配置(如Spring的Jackson2ObjectMapperBuilder
所做的那样),而不是替换它。
使用 Spring Boot,就这么简单:
RestTemplate template = new RestTemplateBuilder()
.additionalMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper))
.build()
(使用 Spring Boot 2.2.1 测试)
仅使用当前的 ObjectMapper 更新 SerializationConfig.Feature:
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().forEach(mc -> {
if (mc instanceof MappingJackson2HttpMessageConverter ) {
//whatever you want to change
((MappingJackson2HttpMessageConverter) mc).getObjectMapper().setSerializationInclusion(Include.NON_NULL);
}
});
我写了一个单元测试来回答这个问题。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>be.mteam</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>demo</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@Test
void buildRestTemplateWithReplacedMappingJackson2HttpMessageConverter() {
// a RestTemplateBuilder is used to build a RestTemplate
// having the default message converters
RestTemplateBuilder builder = new RestTemplateBuilder()
.defaultMessageConverters();
RestTemplate restTemplate = builder.build();
// nothing special, the template is built, 5 default converters
// 0 = {ByteArrayHttpMessageConverter@2724}
// 1 = {StringHttpMessageConverter@2725}
// 2 = {ResourceHttpMessageConverter@2726}
// 3 = {AllEncompassingFormHttpMessageConverter@2727}
// 4 = {Jaxb2RootElementHttpMessageConverter@2728}
// 5 = {MappingJackson2HttpMessageConverter@2729}
List<HttpMessageConverter<?>> converters = new ArrayList<>(restTemplate.getMessageConverters());
// using removeIf, MappingJackson2HttpMessageConverter is removed
converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);
// the desired CustomMappingJackson2HttpMessageConverter of type MappingJackson2HttpMessageConverter is added to the list of converters
converters.add(new CustomMappingJackson2HttpMessageConverter());
// the converters are put back into the object restTemplate
restTemplate.setMessageConverters(converters);
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
boolean containsDefaultJackson = messageConverters.stream()
.anyMatch(converter -> converter instanceof MappingJackson2HttpMessageConverter &&
!(converter instanceof CustomMappingJackson2HttpMessageConverter));
boolean containsCustomJackson = messageConverters.stream()
.anyMatch(converter -> converter instanceof CustomMappingJackson2HttpMessageConverter);
// Assert that the default MappingJackson2HttpMessageConverter was replaced
assertThat(containsDefaultJackson).isFalse();
// Assert that the customized MappingJackson2HttpMessageConverter was added
assertThat(containsCustomJackson).isTrue();
// Verify the order: custom converter should replace the default one
assertThat(messageConverters).doesNotHaveDuplicates();
assertThat(messageConverters.stream()
.filter(converter -> converter instanceof MappingJackson2HttpMessageConverter)
.count()).isEqualTo(1);
}