Skip to content

Commit

Permalink
Add a way to configure TypeFactory Jackson uses (#4115)
Browse files Browse the repository at this point in the history
  • Loading branch information
JooHyukKim authored Sep 26, 2023
1 parent d5e0b43 commit 245fe1c
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2297,6 +2297,7 @@ public ObjectMapper setCacheProvider(CacheProvider cacheProvider) {
_serializationConfig = _serializationConfig.with(cacheProvider);
_deserializationContext = _deserializationContext.withCaches(cacheProvider);
_serializerProvider = _serializerProvider.withCaches(cacheProvider);
_typeFactory = _typeFactory.withCaches(cacheProvider);
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,19 @@ public interface CacheProvider
* @return {@link LookupCache} instance for constructing {@link DeserializerCache}.
*/
LookupCache<JavaType, JsonDeserializer<Object>> forDeserializerCache(DeserializationConfig config);

/**
* Method to provide a {@link LookupCache} instance for constructing {@link com.fasterxml.jackson.databind.ser.SerializerCache}.
*
* @return {@link LookupCache} instance for constructing {@link com.fasterxml.jackson.databind.ser.SerializerCache}.
*/
LookupCache<TypeKey, JsonSerializer<Object>> forSerializerCache(SerializationConfig config);

/**
* Method to provide a {@link LookupCache} instance for constructing {@link com.fasterxml.jackson.databind.type.TypeFactory}.
*
* @return {@link LookupCache} instance for constructing {@link com.fasterxml.jackson.databind.type.TypeFactory}.
*/
LookupCache<Object, JavaType> forTypeFactory();

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.DeserializerCache;
import com.fasterxml.jackson.databind.ser.SerializerCache;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.LRUMap;
import com.fasterxml.jackson.databind.util.LookupCache;
import com.fasterxml.jackson.databind.util.TypeKey;
Expand All @@ -21,7 +22,7 @@ public class DefaultCacheProvider
private static final long serialVersionUID = 1L;

private final static DefaultCacheProvider DEFAULT
= new DefaultCacheProvider(DeserializerCache.DEFAULT_MAX_CACHE_SIZE, SerializerCache.DEFAULT_MAX_CACHE_SIZE);
= new DefaultCacheProvider(DeserializerCache.DEFAULT_MAX_CACHE_SIZE, SerializerCache.DEFAULT_MAX_CACHE_SIZE, TypeFactory.DEFAULT_MAX_CACHE_SIZE);

/**
* Maximum size of the {@link LookupCache} instance constructed by {@link #forDeserializerCache(DeserializationConfig)}.
Expand All @@ -36,17 +37,25 @@ public class DefaultCacheProvider
* @see Builder#maxSerializerCacheSize(int)
*/
protected final int _maxSerializerCacheSize;


/**
* Maximum size of the {@link LookupCache} instance constructed by {@link #forTypeFactory()}.
*
* @see Builder#maxTypeFactoryCacheSize(int)
*/
protected final int _maxTypeFactoryCacheSize;

/*
/**********************************************************************
/* Life cycle
/**********************************************************************
*/

protected DefaultCacheProvider(int maxDeserializerCacheSize, int maxSerializerCacheSize)
protected DefaultCacheProvider(int maxDeserializerCacheSize, int maxSerializerCacheSize, int maxTypeFactoryCacheSize)
{
_maxDeserializerCacheSize = maxDeserializerCacheSize;
_maxSerializerCacheSize = maxSerializerCacheSize;
_maxTypeFactoryCacheSize = maxTypeFactoryCacheSize;
}

/*
Expand Down Expand Up @@ -84,6 +93,11 @@ public LookupCache<TypeKey, JsonSerializer<Object>> forSerializerCache(Serializa
return _buildCache(_maxSerializerCacheSize);
}

@Override
public LookupCache<Object, JavaType> forTypeFactory() {
return _buildCache(_maxTypeFactoryCacheSize);
}

/*
/**********************************************************
/* Overridable factory methods
Expand Down Expand Up @@ -127,11 +141,19 @@ public static class Builder {
*/
private int _maxSerializerCacheSize;

/**
* Maximum Size of the {@link LookupCache} instance created by {@link #forTypeFactory()}.
* Corresponds to {@link DefaultCacheProvider#_maxTypeFactoryCacheSize}.
*/
private int _maxTypeFactoryCacheSize;

Builder() { }

/**
* Define the maximum size of the {@link LookupCache} instance constructed by {@link #forDeserializerCache(DeserializationConfig)}
* and {@link #_buildCache(int)}.
* <p>
* Note that specifying a maximum size of zero prevents values from being retained in the cache.
*
* @param maxDeserializerCacheSize Size for the {@link LookupCache} to use within {@link DeserializerCache}
* @return this builder
Expand All @@ -149,6 +171,8 @@ public Builder maxDeserializerCacheSize(int maxDeserializerCacheSize) {
/**
* Define the maximum size of the {@link LookupCache} instance constructed by {@link #forSerializerCache(SerializationConfig)}
* and {@link #_buildCache(int)}
* <p>
* Note that specifying a maximum size of zero prevents values from being retained in the cache.
*
* @param maxSerializerCacheSize Size for the {@link LookupCache} to use within {@link SerializerCache}
* @return this builder
Expand All @@ -163,13 +187,31 @@ public Builder maxSerializerCacheSize(int maxSerializerCacheSize) {
return this;
}

/**
* Define the maximum size of the {@link LookupCache} instance constructed by {@link #forTypeFactory()}
* and {@link #_buildCache(int)}
* <p>
* Note that specifying a maximum size of zero prevents values from being retained in the cache.
*
* @param maxTypeFactoryCacheSize Size for the {@link LookupCache} to use within {@link com.fasterxml.jackson.databind.type.TypeFactory}
* @return this builder
* @throws IllegalArgumentException if {@code maxTypeFactoryCacheSize} is negative
*/
public Builder maxTypeFactoryCacheSize(int maxTypeFactoryCacheSize) {
if (maxTypeFactoryCacheSize < 0) {
throw new IllegalArgumentException("Cannot set maxTypeFactoryCacheSize to a negative value");
}
_maxTypeFactoryCacheSize = maxTypeFactoryCacheSize;
return this;
}

/**
* Constructs a {@link DefaultCacheProvider} with the provided configuration values, using defaults where not specified.
*
* @return A {@link DefaultCacheProvider} instance with the specified configuration
*/
public DefaultCacheProvider build() {
return new DefaultCacheProvider(_maxDeserializerCacheSize, _maxSerializerCacheSize);
return new DefaultCacheProvider(_maxDeserializerCacheSize, _maxSerializerCacheSize, _maxTypeFactoryCacheSize);
}
}
}
26 changes: 24 additions & 2 deletions src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.cfg.CacheProvider;
import com.fasterxml.jackson.databind.util.ArrayBuilders;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.LRUMap;
Expand Down Expand Up @@ -70,6 +71,15 @@ public class TypeFactory // note: was final in 2.9, removed from 2.10
{
private static final long serialVersionUID = 1L;

/**
* Default size used to construct {@link #_typeCache}.
*
* Used to be passed inline.
*
* @since 2.16
*/
public static final int DEFAULT_MAX_CACHE_SIZE = 200;

private final static JavaType[] NO_TYPES = new JavaType[0];

/**
Expand Down Expand Up @@ -178,7 +188,7 @@ public class TypeFactory // note: was final in 2.9, removed from 2.10
*/

private TypeFactory() {
this(new LRUMap<>(16, 200));
this(new LRUMap<>(16, DEFAULT_MAX_CACHE_SIZE));
}

/**
Expand All @@ -198,7 +208,7 @@ protected TypeFactory(LookupCache<Object,JavaType> typeCache, TypeParser p,
TypeModifier[] mods, ClassLoader classLoader)
{
if (typeCache == null) {
typeCache = new LRUMap<>(16, 200);
typeCache = new LRUMap<>(16, DEFAULT_MAX_CACHE_SIZE);
}
_typeCache = typeCache;
// As per [databind#894] must ensure we have back-linkage from TypeFactory:
Expand Down Expand Up @@ -260,11 +270,23 @@ public TypeFactory withCache(LRUMap<Object,JavaType> cache) {
* bigger maximum size.
*
* @since 2.12
* @deprecated Since 2.16. Use {@link #withCaches(CacheProvider)} instead.
*/
public TypeFactory withCache(LookupCache<Object,JavaType> cache) {
return new TypeFactory(cache, _parser, _modifiers, _classLoader);
}

/**
* Mutant factory method that will construct a new {@link TypeFactory}
* with cache instances provided by {@link CacheProvider}.
*
* @since 2.16
*/
public TypeFactory withCaches(CacheProvider cacheProvider) {
return new TypeFactory(cacheProvider.forTypeFactory(),
_parser, _modifiers, _classLoader);
}

/**
* Method used to access the globally shared instance, which has
* no custom configuration. Used by {@code ObjectMapper} to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.fasterxml.jackson.databind.util.LRUMap;
import com.fasterxml.jackson.databind.util.LookupCache;
import com.fasterxml.jackson.databind.util.TypeKey;
import java.util.List;
import org.junit.Test;

import java.util.HashMap;
Expand Down Expand Up @@ -90,6 +91,11 @@ public LookupCache<JavaType, JsonDeserializer<Object>> forDeserializerCache(Dese
return _cache;
}

@Override
public LookupCache<Object, JavaType> forTypeFactory() {
return new LRUMap<>(16, 64);
}

@Override
public LookupCache<TypeKey, JsonSerializer<Object>> forSerializerCache(SerializationConfig config) {
return new LRUMap<>(8, 64);
Expand All @@ -110,6 +116,10 @@ public LookupCache<JavaType, JsonDeserializer<Object>> forDeserializerCache(Dese
}

@Override
public LookupCache<Object, JavaType> forTypeFactory() {
return new LRUMap<>(16, 64);
}

public LookupCache<TypeKey, JsonSerializer<Object>> forSerializerCache(SerializationConfig config) {
return _cache;
}
Expand All @@ -128,6 +138,40 @@ public JsonSerializer<Object> put(TypeKey key, JsonSerializer<Object> value) {
return super.put(key, value);
}
}

static class CustomTypeFactoryCacheProvider implements CacheProvider {

final CustomTestTypeFactoryCache _cache = new CustomTestTypeFactoryCache();

@Override
public LookupCache<JavaType, JsonDeserializer<Object>> forDeserializerCache(DeserializationConfig config) {
return new LRUMap<>(16, 64);
}

@Override
public LookupCache<Object, JavaType> forTypeFactory() {
return _cache;
}

public LookupCache<TypeKey, JsonSerializer<Object>> forSerializerCache(SerializationConfig config) {
return new LRUMap<>(16, 64);
}
}

static class CustomTestTypeFactoryCache extends LRUMap<Object,JavaType> {

public boolean _isInvoked = false;

public CustomTestTypeFactoryCache() {
super(8, 16);
}

@Override
public JavaType putIfAbsent(Object key, JavaType value) {
_isInvoked = true;
return super.putIfAbsent(key, value);
}
}

/*
/**********************************************************************
Expand Down Expand Up @@ -186,7 +230,7 @@ public void testDefaultCacheProviderSharesCache() throws Exception
.cacheProvider(cacheProvider)
.build();

// Act
// Act
// 3. Add two different types to each mapper cache
mapper1.readValue("{\"point\":24}", RandomBean.class);
mapper2.readValue("{\"height\":24}", AnotherBean.class);
Expand All @@ -205,10 +249,12 @@ public void testBuilderValueValidation() throws Exception
DefaultCacheProvider.builder()
.maxDeserializerCacheSize(0)
.maxSerializerCacheSize(0)
.maxTypeFactoryCacheSize(0)
.build();
DefaultCacheProvider.builder()
.maxDeserializerCacheSize(Integer.MAX_VALUE)
.maxSerializerCacheSize(Integer.MAX_VALUE)
.maxTypeFactoryCacheSize(Integer.MAX_VALUE)
.build();

// fail cases
Expand All @@ -224,6 +270,12 @@ public void testBuilderValueValidation() throws Exception
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("Cannot set maxSerializerCacheSize to a negative value"));
}
try {
DefaultCacheProvider.builder().maxTypeFactoryCacheSize(-1);
fail("Should not reach here");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("Cannot set maxTypeFactoryCacheSize to a negative value"));
}
}

/**
Expand Down Expand Up @@ -258,4 +310,19 @@ private void _verifySerializeSuccess(CacheProvider cacheProvider) throws Excepti
assertEquals("{\"slide\":123}",
mapper.writeValueAsString(new SerBean()));
}

/**
* Sanity test for serialization with {@link CacheProvider#forTypeFactory()}
*/
@Test
public void sanityCheckTypeFactoryCacheSize() throws Exception
{
// custom
CustomTypeFactoryCacheProvider customProvider = new CustomTypeFactoryCacheProvider();
ObjectMapper mapper = JsonMapper.builder()
.cacheProvider(customProvider)
.build();
mapper.getTypeFactory().constructParametricType(List.class, HashMap.class);
assertTrue(customProvider._cache._isInvoked);
}
}

0 comments on commit 245fe1c

Please sign in to comment.