-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Add a way to configure TypeFactory
Jackson uses
#4115
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 5 commits
47164ab
fe64525
7bf1c4b
ae11701
eed6b6f
ce900a8
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 |
---|---|---|
|
@@ -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; | ||
|
@@ -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]; | ||
|
||
/** | ||
|
@@ -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)); | ||
} | ||
|
||
/** | ||
|
@@ -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: | ||
|
@@ -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) { | ||
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 think I'll change this so we won't need a new method -- caller can just construct 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. Sure! Ill check your commit later👍🏼👍🏼 |
||
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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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); | ||
|
@@ -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; | ||
} | ||
|
@@ -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); | ||
} | ||
} | ||
|
||
/* | ||
/********************************************************************** | ||
|
@@ -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); | ||
|
@@ -205,10 +249,12 @@ public void testBuilderValueValidation() throws Exception | |
DefaultCacheProvider.builder() | ||
.maxDeserializerCacheSize(0) | ||
.maxSerializerCacheSize(0) | ||
.maxTypeFactoryCacheSize(0) | ||
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. One thing I wonder: I think that most cache implementations impose a minimum size, so Then again, it is now possibly to add custom cache instances too which can be "no-op" (do nothing) so maybe this doesn't matter. I also haven't tested out if backing 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.
Caffeine, on the other hand, batches evictions, so the zero maximum size constraint can be temporarily violated. In addition, Cache2k, Android LruCache, and Ehcache all throw IllegalArgumentException when trying to create a cache with a maximum size of zero. 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. Thank you @iProdigy . Sounds like we are good then. So 0 size for 2.14+ Jackson implementation will not retain anything in cache, and while there is write overhead that should be negligible. It'd be nice to test this invariant, but no further work should be necessary it sounds. 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. Hmmm, I am trying to come up with JavaDoc like... * Note that setting value of {@code ...} to zero will not .... unlike some Cache providers. ....but idk what to exactly say 🥲 any help? 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. Added JavaDoc and tagged you there. |
||
.build(); | ||
DefaultCacheProvider.builder() | ||
.maxDeserializerCacheSize(Integer.MAX_VALUE) | ||
.maxSerializerCacheSize(Integer.MAX_VALUE) | ||
.maxTypeFactoryCacheSize(Integer.MAX_VALUE) | ||
.build(); | ||
|
||
// fail cases | ||
|
@@ -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")); | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -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); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.
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.
Added JavaDoc as per comment #4115 (comment). WDYT?
/cc @iProdigy @cowtowncoder
Uh oh!
There was an error while loading. Please reload this page.
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.
I would rephrase... For DefaultCacheProvider, zero max size effectively does disable caching since entries are not retained in the underlying LRUMap. Yes, it's technically true that a write & removal occurs under the hood, but users don't care about this detail (other than for optimizing performance, as a special no-op implementation could've been used instead)
Perhaps:
Specifying a maximum size of zero prevents values from being retained in the cache
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.
True, they don't need to. Thank you for the suggestion! 🙏🏼🙏🏼 Changed accordingly