Skip to content

Commit 35de528

Browse files
authored
Cache JsonSerializer "per" ObjectMapper (#643)
- Maintain a separate cache of JsonProvider per ObjectMapper - do not create a new instance of SerializerProvider ourselves but instead use ObjectMapper#getSerializerProviderInstance introduced in Jackson 2.7 - do not create a JsonSerializer for the object class ourselves but instead delegate to Jackson SerializerProvider and therefore leverage its internal cache Addresses issue #642
1 parent 14872d7 commit 35de528

File tree

1 file changed

+65
-62
lines changed

1 file changed

+65
-62
lines changed

src/main/java/net/logstash/logback/marker/ObjectFieldsAppendingMarker.java

+65-62
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package net.logstash.logback.marker;
1717

1818
import java.io.IOException;
19+
import java.util.Map;
1920
import java.util.Objects;
2021
import java.util.concurrent.ConcurrentHashMap;
2122

@@ -28,10 +29,7 @@
2829
import com.fasterxml.jackson.databind.JsonMappingException;
2930
import com.fasterxml.jackson.databind.JsonSerializer;
3031
import com.fasterxml.jackson.databind.ObjectMapper;
31-
import com.fasterxml.jackson.databind.SerializationConfig;
3232
import com.fasterxml.jackson.databind.SerializerProvider;
33-
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
34-
import com.fasterxml.jackson.databind.ser.ResolvableSerializer;
3533
import com.fasterxml.jackson.databind.util.NameTransformer;
3634
import org.slf4j.Marker;
3735

@@ -91,8 +89,7 @@ public class ObjectFieldsAppendingMarker extends LogstashMarker implements Struc
9189
*
9290
* Since apps will typically serialize the same types of objects repeatedly, they shouldn't grow too much.
9391
*/
94-
private static final ConcurrentHashMap<Class<?>, JsonSerializer<Object>> beanSerializers = new ConcurrentHashMap<>();
95-
private static final ConcurrentHashMap<ObjectMapper, SerializerProvider> serializerProviders = new ConcurrentHashMap<>();
92+
private static final Map<ObjectMapper, SerializerHelper> serializerHelper = new ConcurrentHashMap<>();
9693

9794
public ObjectFieldsAppendingMarker(Object object) {
9895
super(MARKER_NAME);
@@ -101,70 +98,18 @@ public ObjectFieldsAppendingMarker(Object object) {
10198

10299
@Override
103100
public void writeTo(JsonGenerator generator) throws IOException {
104-
if (object != null) {
105-
ObjectMapper mapper = (ObjectMapper) generator.getCodec();
106-
JsonSerializer<Object> serializer = getBeanSerializer(mapper);
107-
if (serializer.isUnwrappingSerializer()) {
108-
serializer.serialize(object, generator, getSerializerProvider(mapper));
109-
}
101+
if (this.object != null) {
102+
SerializerHelper helper = getSerializerHelper(generator);
103+
helper.serialize(generator, this.object);
110104
}
111105
}
112-
106+
107+
113108
@Override
114109
public String toStringSelf() {
115110
return StructuredArguments.toString(object);
116111
}
117112

118-
/**
119-
* Gets a serializer that will write the {@link #object} unwrapped.
120-
*/
121-
private JsonSerializer<Object> getBeanSerializer(ObjectMapper mapper) throws JsonMappingException {
122-
123-
JsonSerializer<Object> jsonSerializer = beanSerializers.get(object.getClass());
124-
125-
if (jsonSerializer == null) {
126-
SerializerProvider serializerProvider = getSerializerProvider(mapper);
127-
JsonSerializer<Object> newSerializer = mapper.getSerializerFactory().createSerializer(
128-
serializerProvider,
129-
mapper.getSerializationConfig().constructType(object.getClass()))
130-
.unwrappingSerializer(NameTransformer.NOP);
131-
132-
if (newSerializer instanceof ResolvableSerializer) {
133-
((ResolvableSerializer) newSerializer).resolve(serializerProvider);
134-
}
135-
136-
JsonSerializer<Object> existingSerializer = beanSerializers.putIfAbsent(
137-
object.getClass(),
138-
newSerializer);
139-
140-
jsonSerializer = (existingSerializer == null) ? newSerializer : existingSerializer;
141-
}
142-
return jsonSerializer;
143-
144-
}
145-
146-
/**
147-
* Gets a {@link SerializerProvider} configured with the {@link ObjectMapper}'s {@link SerializationConfig}
148-
* ({@link ObjectMapper#getSerializationConfig()}) to be used for serialization.
149-
* <p>
150-
* Note that the {@link ObjectMapper}'s {@link SerializerProvider} ({@link ObjectMapper#getSerializerProvider()})
151-
* cannot be used directly, because the {@link SerializerProvider}'s {@link SerializationConfig} ({@link SerializerProvider#getConfig()}) is null,
152-
* which causes NullPointerExceptions when it is used.
153-
*/
154-
private SerializerProvider getSerializerProvider(ObjectMapper mapper) {
155-
156-
SerializerProvider provider = serializerProviders.get(mapper);
157-
if (provider == null) {
158-
159-
SerializerProvider newProvider = ((DefaultSerializerProvider) mapper.getSerializerProvider())
160-
.createInstance(mapper.getSerializationConfig(), mapper.getSerializerFactory());
161-
162-
SerializerProvider existingProvider = serializerProviders.putIfAbsent(mapper, newProvider);
163-
164-
provider = (existingProvider == null) ? newProvider : existingProvider;
165-
}
166-
return provider;
167-
}
168113

169114
@Override
170115
public boolean equals(Object obj) {
@@ -182,6 +127,7 @@ public boolean equals(Object obj) {
182127
return Objects.equals(this.object, other.object);
183128
}
184129

130+
185131
@Override
186132
public int hashCode() {
187133
final int prime = 31;
@@ -190,4 +136,61 @@ public int hashCode() {
190136
result = prime * result + (this.object == null ? 0 : this.object.hashCode());
191137
return result;
192138
}
139+
140+
141+
/**
142+
* Get a {@link SerializerHelper} suitable for use with the given {@link JsonGenerator}.
143+
*
144+
* @param gen the {@link JsonGenerator} for which an helper should be returned
145+
* @return a {@link SerializerHelper}
146+
*/
147+
private static SerializerHelper getSerializerHelper(JsonGenerator gen) {
148+
ObjectMapper mapper = (ObjectMapper) gen.getCodec();
149+
return serializerHelper.computeIfAbsent(mapper, SerializerHelper::new);
150+
}
151+
152+
private static class SerializerHelper {
153+
private final SerializerProvider serializers;
154+
private final ConcurrentHashMap<Class<?>, JsonSerializer<Object>> cache = new ConcurrentHashMap<>();
155+
156+
SerializerHelper(ObjectMapper mapper) {
157+
this.serializers = mapper.getSerializerProviderInstance();
158+
}
159+
160+
/**
161+
* Serialize the given value using the supplied generator
162+
*
163+
* @param gen the {@link JsonGenerator} to use to serialize the value
164+
* @param value the value to serialize
165+
* @throws IOException thrown when the underlying {@link JsonGenerator} could not be created
166+
* or when it has problems to serialize the given value
167+
*/
168+
public void serialize(JsonGenerator gen, Object value) throws IOException {
169+
if (value != null) {
170+
JsonSerializer<Object> unwrappingSerializer = getUnwrappingSerializer(value.getClass());
171+
172+
/*
173+
* Make sure the serializer accepts to serialize in an "unwrapped" fashion.
174+
* This may not be the case for serializer for Number types for instance.
175+
*/
176+
if (unwrappingSerializer.isUnwrappingSerializer()) {
177+
unwrappingSerializer.serialize(value, gen, serializers);
178+
}
179+
}
180+
}
181+
182+
private JsonSerializer<Object> getUnwrappingSerializer(Class<?> type) throws JsonMappingException {
183+
JsonSerializer<Object> serializer = cache.get(type);
184+
if (serializer == null) {
185+
serializer = createUnwrappingSerializer(type);
186+
cache.put(type, serializer);
187+
}
188+
return serializer;
189+
}
190+
191+
private JsonSerializer<Object> createUnwrappingSerializer(Class<?> type) throws JsonMappingException {
192+
JsonSerializer<Object> serializer = serializers.findValueSerializer(type);
193+
return serializer.unwrappingSerializer(NameTransformer.NOP);
194+
}
195+
}
193196
}

0 commit comments

Comments
 (0)