-
Notifications
You must be signed in to change notification settings - Fork 1.6k
GH-1781: Configure ObjectMapper in JsonSerializer/JsonDeserializer #1782
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,7 +60,7 @@ | |
* @author Torsten Schleede | ||
* @author Ivan Ponomarev | ||
*/ | ||
public class JsonDeserializer<T> implements Deserializer<T> { | ||
public class JsonDeserializer<T> implements Deserializer<T>, ObjectMapperCustomizer { | ||
|
||
private static final String KEY_DEFAULT_TYPE_STRING = "spring.json.key.default.type"; | ||
|
||
|
@@ -114,7 +114,7 @@ public class JsonDeserializer<T> implements Deserializer<T> { | |
|
||
protected Jackson2JavaTypeMapper typeMapper = new DefaultJackson2JavaTypeMapper(); // NOSONAR | ||
|
||
private ObjectReader reader; | ||
protected CachedObjectReader cachedReader; | ||
|
||
private boolean typeMapperExplicitlySet = false; | ||
|
||
|
@@ -244,6 +244,7 @@ public JsonDeserializer(@Nullable Class<? super T> targetType, ObjectMapper obje | |
|
||
Assert.notNull(objectMapper, "'objectMapper' must not be null."); | ||
this.objectMapper = objectMapper; | ||
this.cachedReader = new CachedObjectReader(); | ||
JavaType javaType = null; | ||
if (targetType == null) { | ||
Class<?> genericType = ResolvableType.forClass(getClass()).getSuperType().resolveGeneric(0); | ||
|
@@ -288,6 +289,7 @@ public JsonDeserializer(@Nullable JavaType targetType, ObjectMapper objectMapper | |
|
||
Assert.notNull(objectMapper, "'objectMapper' must not be null."); | ||
this.objectMapper = objectMapper; | ||
this.cachedReader = new CachedObjectReader(); | ||
initialize(targetType, useHeadersIfPresent); | ||
} | ||
|
||
|
@@ -369,6 +371,7 @@ public void setTypeResolver(JsonTypeResolver typeResolver) { | |
|
||
@Override | ||
public void configure(Map<String, ?> configs, boolean isKey) { | ||
customizeObjectMapper(configs, this.objectMapper); | ||
setUseTypeMapperForKey(isKey); | ||
setUpTypePrecedence(configs); | ||
setupTarget(configs, isKey); | ||
|
@@ -451,10 +454,6 @@ private void initialize(@Nullable JavaType type, boolean useHeadersIfPresent) { | |
Assert.isTrue(this.targetType != null || useHeadersIfPresent, | ||
"'targetType' cannot be null if 'useHeadersIfPresent' is false"); | ||
|
||
if (this.targetType != null) { | ||
this.reader = this.objectMapper.readerFor(this.targetType); | ||
} | ||
|
||
addTargetPackageToTrusted(); | ||
this.typeMapper.setTypePrecedence(useHeadersIfPresent ? TypePrecedence.TYPE_ID : TypePrecedence.INFERRED); | ||
} | ||
|
@@ -527,7 +526,7 @@ public T deserialize(String topic, Headers headers, byte[] data) { | |
this.typeMapper.removeHeaders(headers); | ||
} | ||
if (deserReader == null) { | ||
deserReader = this.reader; | ||
deserReader = this.cachedReader.getReader(this.objectMapper, this.targetType); | ||
} | ||
Assert.state(deserReader != null, "No type information in headers and no default type provided"); | ||
try { | ||
|
@@ -544,7 +543,7 @@ public T deserialize(String topic, @Nullable byte[] data) { | |
if (data == null) { | ||
return null; | ||
} | ||
ObjectReader localReader = this.reader; | ||
ObjectReader localReader = this.cachedReader.getReader(this.objectMapper, this.targetType); | ||
if (this.typeResolver != null) { | ||
JavaType javaType = this.typeResolver.resolveType(topic, data, null); | ||
if (javaType != null) { | ||
|
@@ -718,4 +717,27 @@ private JsonTypeResolver buildTypeResolver(String methodProperty) { | |
}; | ||
} | ||
|
||
protected class CachedObjectReader { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problems are:
Therefore making no assumptions about how costly it is to construct an ObjectWriter. Considering that the current serializer constructs the ObjectWriter just once in the constructor, I took the conservative approach and tried to at least cache the object so that if conditions are right it would still only get constructed once. On the other hand, hashcode does have a cost, so if you don't think it matters to performance, I can pull it out. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK. Probably you just didn't see through my suggestion:
Does it make sense? Sorry for not being clear enough from the beginning. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is certainly simpler, but I'm not familiar enough with all the supported code flows to decide if it's sufficient or not. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think so: you call
And you see: they also set Sounds reasonable? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the flip side, changes to the ObjectMapper may or may not be reflected in the hashcode. If not, then the CachedObjectReader won't work any better (or worse) than a volatile. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, will update. |
||
|
||
private ObjectReader reader = null; | ||
private int mapperHash = -1; | ||
private int typeHash = -1; | ||
|
||
public ObjectReader getReader(ObjectMapper mapper, JavaType type) { | ||
if (null == mapper) { | ||
return null; | ||
} | ||
// If not already cached | ||
if ((null == this.reader) || | ||
(mapper.hashCode() != this.mapperHash) || | ||
((null == type && -1 != this.typeHash) || | ||
((null != type) && (type.hashCode() != this.typeHash)))) { | ||
this.mapperHash = mapper.hashCode(); | ||
this.typeHash = (null == type) ? -1 : type.hashCode(); | ||
this.reader = mapper.readerFor(type); | ||
} | ||
return this.reader; | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
* Copyright 2020-2021 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.springframework.kafka.support.serializer; | ||
|
||
import java.util.Map; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
||
/** | ||
* Provide hook to configure the ObjectMapper used in Json(De)Serializer. | ||
* | ||
* This will get called from the JsonSerializer.configure() function. | ||
* @author Carl Nygard | ||
* @since 2.5.13 | ||
* | ||
*/ | ||
public interface ObjectMapperCustomizer { | ||
|
||
/** | ||
* Customize (or replace) the ObjectMapper used in serialization. | ||
* @param configs map of configuration values | ||
* @param objectMapper configurable (or replaceable) ObjectMapper instance | ||
* @return the configured (or possibly new) ObjectMapper instance. | ||
* @since 2.5.13 | ||
*/ | ||
default ObjectMapper customizeObjectMapper(Map<String, ?> configs, ObjectMapper objectMapper) { | ||
// by default, we do nothing | ||
return objectMapper; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
* Copyright 2016-2021 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.springframework.kafka.support.serializer; | ||
|
||
import java.util.Map; | ||
|
||
import org.springframework.lang.Nullable; | ||
|
||
import com.fasterxml.jackson.core.type.TypeReference; | ||
import com.fasterxml.jackson.databind.DeserializationFeature; | ||
import com.fasterxml.jackson.databind.JavaType; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
||
public class CustomJsonDeserializer<T> extends JsonDeserializer<T> { | ||
|
||
/** | ||
* constructor for custom json serializer. | ||
*/ | ||
public CustomJsonDeserializer() { | ||
super(); | ||
} | ||
public CustomJsonDeserializer(@Nullable JavaType targetType) { | ||
super(targetType); | ||
} | ||
public CustomJsonDeserializer(@Nullable Class<? super T> targetType) { | ||
super(targetType); | ||
} | ||
public CustomJsonDeserializer(@Nullable TypeReference<? super T> targetType) { | ||
super(targetType); | ||
} | ||
|
||
@Override | ||
public ObjectMapper customizeObjectMapper(Map<String, ?> configs, ObjectMapper mapper) { | ||
mapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); | ||
return mapper; | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggested rewording - the Apache Foundation require us to mention Apache whenever we reference
Kafka
.