Skip to content

Commit 3eb74af

Browse files
committed
Implement #550
1 parent 015bdd7 commit 3eb74af

4 files changed

Lines changed: 171 additions & 44 deletions

File tree

release-notes/VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Version: 2.5.0 (xx-xxx-2014)
99
(reported by Fabien R, fabienrenaud@github)
1010
#543: Problem resolving self-referential recursive types
1111
(reported by ahgittin@github)
12+
#550: Minor optimization: prune introspection of "well-known" JDK types
1213
#552: Improved handling for ISO-8601 (date) format
1314
(contributed by Jerome G, geronimo-iia@github)
1415

src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -198,11 +198,6 @@ public boolean useForType(JavaType t)
198198
// Quick little shortcut, to avoid having to use global TypeFactory instance...
199199
private final static JavaType JSON_NODE_TYPE = SimpleType.constructUnsafe(JsonNode.class);
200200

201-
/* !!! 03-Apr-2009, tatu: Should try to avoid direct reference... but not
202-
* sure what'd be simple and elegant way. So until then:
203-
*/
204-
protected final static ClassIntrospector DEFAULT_INTROSPECTOR = BasicClassIntrospector.instance;
205-
206201
// 16-May-2009, tatu: Ditto ^^^
207202
protected final static AnnotationIntrospector DEFAULT_ANNOTATION_INTROSPECTOR = new JacksonAnnotationIntrospector();
208203

@@ -214,8 +209,10 @@ public boolean useForType(JavaType t)
214209
* Base settings contain defaults used for all {@link ObjectMapper}
215210
* instances.
216211
*/
217-
protected final static BaseSettings DEFAULT_BASE = new BaseSettings(DEFAULT_INTROSPECTOR,
218-
DEFAULT_ANNOTATION_INTROSPECTOR, STD_VISIBILITY_CHECKER, null, TypeFactory.defaultInstance(),
212+
protected final static BaseSettings DEFAULT_BASE = new BaseSettings(
213+
null, // can not share global ClassIntrospector any more (2.5+)
214+
DEFAULT_ANNOTATION_INTROSPECTOR,
215+
STD_VISIBILITY_CHECKER, null, TypeFactory.defaultInstance(),
219216
null, StdDateFormat.instance, null,
220217
Locale.getDefault(),
221218
// TimeZone.getDefault()
@@ -449,9 +446,11 @@ public ObjectMapper(JsonFactory jf,
449446

450447
HashMap<ClassKey,Class<?>> mixins = new HashMap<ClassKey,Class<?>>();
451448
_mixInAnnotations = mixins;
452-
_serializationConfig = new SerializationConfig(DEFAULT_BASE,
449+
450+
BaseSettings base = DEFAULT_BASE.withClassIntrospector(defaultClassIntrospector());
451+
_serializationConfig = new SerializationConfig(base,
453452
_subtypeResolver, mixins);
454-
_deserializationConfig = new DeserializationConfig(DEFAULT_BASE,
453+
_deserializationConfig = new DeserializationConfig(base,
455454
_subtypeResolver, mixins);
456455

457456
// Some overrides we may need
@@ -467,7 +466,7 @@ public ObjectMapper(JsonFactory jf,
467466
// Default serializer factory is stateless, can just assign
468467
_serializerFactory = BeanSerializerFactory.instance;
469468
}
470-
469+
471470
/**
472471
* Method for creating a new {@link ObjectMapper} instance that
473472
* has same initial configuration as this instance. Note that this
@@ -491,7 +490,6 @@ public ObjectMapper copy()
491490

492491
/**
493492
* @since 2.1
494-
* @param exp
495493
*/
496494
protected void _checkInvalidCopy(Class<?> exp)
497495
{
@@ -500,6 +498,16 @@ protected void _checkInvalidCopy(Class<?> exp)
500498
+" (version: "+version()+") does not override copy(); it has to");
501499
}
502500
}
501+
502+
/**
503+
* Overridable helper method used to construct default {@link ClassIntrospector}
504+
* to use.
505+
*
506+
* @since 2.5
507+
*/
508+
protected ClassIntrospector defaultClassIntrospector() {
509+
return new BasicClassIntrospector();
510+
}
503511

504512
/*
505513
/**********************************************************

src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java

Lines changed: 129 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package com.fasterxml.jackson.databind.introspect;
22

3+
import java.util.Collection;
4+
import java.util.Map;
5+
36
import com.fasterxml.jackson.databind.AnnotationIntrospector;
47
import com.fasterxml.jackson.databind.DeserializationConfig;
58
import com.fasterxml.jackson.databind.JavaType;
69
import com.fasterxml.jackson.databind.SerializationConfig;
710
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
811
import com.fasterxml.jackson.databind.cfg.MapperConfig;
912
import com.fasterxml.jackson.databind.type.SimpleType;
13+
import com.fasterxml.jackson.databind.util.LRUMap;
1014

1115
public class BasicClassIntrospector
1216
extends ClassIntrospector
@@ -19,6 +23,8 @@ public class BasicClassIntrospector
1923
* This is strictly performance optimization to reduce what is
2024
* usually one-time cost, but seems useful for some cases considering
2125
* simplicity.
26+
*
27+
* @since 2.4
2228
*/
2329

2430
protected final static BasicBeanDescription STRING_DESC;
@@ -48,9 +54,21 @@ public class BasicClassIntrospector
4854
/**********************************************************
4955
*/
5056

57+
@Deprecated // since 2.5: construct instance directly
5158
public final static BasicClassIntrospector instance = new BasicClassIntrospector();
5259

53-
public BasicClassIntrospector() { }
60+
/**
61+
* Looks like 'forClassAnnotations()' gets called so frequently that we
62+
* should consider caching to avoid some of the lookups.
63+
*
64+
* @since 2.5
65+
*/
66+
protected final LRUMap<JavaType,BasicBeanDescription> _cachedFCA;
67+
68+
public BasicClassIntrospector() {
69+
// a small cache should go a long way here
70+
_cachedFCA = new LRUMap<JavaType,BasicBeanDescription>(16, 64);
71+
}
5472

5573
/*
5674
/**********************************************************
@@ -62,11 +80,18 @@ public BasicClassIntrospector() { }
6280
public BasicBeanDescription forSerialization(SerializationConfig cfg,
6381
JavaType type, MixInResolver r)
6482
{
65-
// minor optimization: for JDK types do minimal introspection
66-
BasicBeanDescription desc = _findCachedDesc(type);
83+
// minor optimization: for some JDK types do minimal introspection
84+
BasicBeanDescription desc = _findStdTypeDesc(type);
6785
if (desc == null) {
68-
desc = BasicBeanDescription.forSerialization(collectProperties(cfg,
69-
type, r, true, "set"));
86+
// As per [Databind#550], skip full introspection for some of standard
87+
// structured types as well
88+
desc = _findStdJdkCollectionDesc(cfg, type, r);
89+
if (desc == null) {
90+
desc = BasicBeanDescription.forSerialization(collectProperties(cfg,
91+
type, r, true, "set"));
92+
}
93+
// Also: this is a superset of "forClassAnnotations", so may optimize by optional add:
94+
_cachedFCA.putIfAbsent(type, desc);
7095
}
7196
return desc;
7297
}
@@ -75,11 +100,18 @@ public BasicBeanDescription forSerialization(SerializationConfig cfg,
75100
public BasicBeanDescription forDeserialization(DeserializationConfig cfg,
76101
JavaType type, MixInResolver r)
77102
{
78-
// minor optimization: for JDK types do minimal introspection
79-
BasicBeanDescription desc = _findCachedDesc(type);
103+
// minor optimization: for some JDK types do minimal introspection
104+
BasicBeanDescription desc = _findStdTypeDesc(type);
80105
if (desc == null) {
81-
desc = BasicBeanDescription.forDeserialization(collectProperties(cfg,
82-
type, r, false, "set"));
106+
// As per [Databind#550], skip full introspection for some of standard
107+
// structured types as well
108+
desc = _findStdJdkCollectionDesc(cfg, type, r);
109+
if (desc == null) {
110+
desc = BasicBeanDescription.forDeserialization(collectProperties(cfg,
111+
type, r, false, "set"));
112+
}
113+
// Also: this is a superset of "forClassAnnotations", so may optimize by optional add:
114+
_cachedFCA.putIfAbsent(type, desc);
83115
}
84116
return desc;
85117
}
@@ -88,42 +120,65 @@ public BasicBeanDescription forDeserialization(DeserializationConfig cfg,
88120
public BasicBeanDescription forDeserializationWithBuilder(DeserializationConfig cfg,
89121
JavaType type, MixInResolver r)
90122
{
91-
// no caching for Builders (no standard JDK builder types):
92-
return BasicBeanDescription.forDeserialization(collectPropertiesWithBuilder(cfg,
93-
type, r, false));
123+
// no std JDK types with Builders, so:
124+
125+
BasicBeanDescription desc = BasicBeanDescription.forDeserialization(collectPropertiesWithBuilder(cfg,
126+
type, r, false));
127+
// this is still a superset of "forClassAnnotations", so may optimize by optional add:
128+
_cachedFCA.putIfAbsent(type, desc);
129+
return desc;
94130
}
95131

96132
@Override
97133
public BasicBeanDescription forCreation(DeserializationConfig cfg,
98134
JavaType type, MixInResolver r)
99135
{
100-
BasicBeanDescription desc = _findCachedDesc(type);
136+
BasicBeanDescription desc = _findStdTypeDesc(type);
101137
if (desc == null) {
102-
desc = BasicBeanDescription.forDeserialization(
138+
139+
// As per [Databind#550], skip full introspection for some of standard
140+
// structured types as well
141+
desc = _findStdJdkCollectionDesc(cfg, type, r);
142+
if (desc == null) {
143+
desc = BasicBeanDescription.forDeserialization(
103144
collectProperties(cfg, type, r, false, "set"));
145+
}
104146
}
147+
// should this be cached for FCA?
105148
return desc;
106149
}
107150

108151
@Override
109152
public BasicBeanDescription forClassAnnotations(MapperConfig<?> cfg,
110153
JavaType type, MixInResolver r)
111154
{
112-
boolean useAnnotations = cfg.isAnnotationProcessingEnabled();
113-
AnnotatedClass ac = AnnotatedClass.construct(type.getRawClass(),
114-
(useAnnotations ? cfg.getAnnotationIntrospector() : null), r);
115-
return BasicBeanDescription.forOtherUse(cfg, type, ac);
155+
BasicBeanDescription desc = _findStdTypeDesc(type);
156+
if (desc == null) {
157+
desc = _cachedFCA.get(type);
158+
if (desc == null) {
159+
boolean useAnnotations = cfg.isAnnotationProcessingEnabled();
160+
AnnotatedClass ac = AnnotatedClass.construct(type.getRawClass(),
161+
(useAnnotations ? cfg.getAnnotationIntrospector() : null), r);
162+
desc = BasicBeanDescription.forOtherUse(cfg, type, ac);
163+
_cachedFCA.put(type, desc);
164+
}
165+
}
166+
return desc;
116167
}
117168

118169
@Override
119170
public BasicBeanDescription forDirectClassAnnotations(MapperConfig<?> cfg,
120171
JavaType type, MixInResolver r)
121172
{
122-
boolean useAnnotations = cfg.isAnnotationProcessingEnabled();
123-
AnnotationIntrospector ai = cfg.getAnnotationIntrospector();
124-
AnnotatedClass ac = AnnotatedClass.constructWithoutSuperTypes(type.getRawClass(),
125-
(useAnnotations ? ai : null), r);
126-
return BasicBeanDescription.forOtherUse(cfg, type, ac);
173+
BasicBeanDescription desc = _findStdTypeDesc(type);
174+
if (desc == null) {
175+
boolean useAnnotations = cfg.isAnnotationProcessingEnabled();
176+
AnnotationIntrospector ai = cfg.getAnnotationIntrospector();
177+
AnnotatedClass ac = AnnotatedClass.constructWithoutSuperTypes(type.getRawClass(),
178+
(useAnnotations ? ai : null), r);
179+
desc = BasicBeanDescription.forOtherUse(cfg, type, ac);
180+
}
181+
return desc;
127182
}
128183

129184
/*
@@ -167,20 +222,62 @@ protected POJOPropertiesCollector constructPropertyCollector(MapperConfig<?> con
167222
* Method called to see if type is one of core JDK types
168223
* that we have cached for efficiency.
169224
*/
170-
protected BasicBeanDescription _findCachedDesc(JavaType type)
225+
protected BasicBeanDescription _findStdTypeDesc(JavaType type)
171226
{
172227
Class<?> cls = type.getRawClass();
173-
if (cls == String.class) {
174-
return STRING_DESC;
228+
if (cls.isPrimitive()) {
229+
if (cls == Boolean.TYPE) {
230+
return BOOLEAN_DESC;
231+
}
232+
if (cls == Integer.TYPE) {
233+
return INT_DESC;
234+
}
235+
if (cls == Long.TYPE) {
236+
return LONG_DESC;
237+
}
238+
} else {
239+
if (cls == String.class) {
240+
return STRING_DESC;
241+
}
175242
}
176-
if (cls == Boolean.TYPE) {
177-
return BOOLEAN_DESC;
243+
return null;
244+
}
245+
246+
/**
247+
* Helper method used to decide whether we can omit introspection
248+
* for members (methods, fields, constructors); we may do so for
249+
* a limited number of container types JDK provides.
250+
*/
251+
protected boolean _isStdJDKCollection(JavaType type)
252+
{
253+
if (!type.isContainerType() || type.isArrayType()) {
254+
return false;
178255
}
179-
if (cls == Integer.TYPE) {
180-
return INT_DESC;
256+
Class<?> raw = type.getRawClass();
257+
Package pkg = raw.getPackage();
258+
if (pkg != null) {
259+
String pkgName = pkg.getName();
260+
if (pkgName.startsWith("java.lang")
261+
|| pkgName.startsWith("java.util")) {
262+
/* 23-Sep-2014, tatu: Should we be conservative here (minimal number
263+
* of matches), or ambitious? Let's do latter for now.
264+
*/
265+
if (Collection.class.isAssignableFrom(raw)
266+
|| Map.class.isAssignableFrom(raw)) {
267+
return true;
268+
}
269+
}
181270
}
182-
if (cls == Long.TYPE) {
183-
return LONG_DESC;
271+
return false;
272+
}
273+
274+
protected BasicBeanDescription _findStdJdkCollectionDesc(MapperConfig<?> cfg,
275+
JavaType type, MixInResolver r)
276+
{
277+
if (_isStdJDKCollection(type)) {
278+
AnnotatedClass ac = AnnotatedClass.construct(type.getRawClass(),
279+
(cfg.isAnnotationProcessingEnabled() ? cfg.getAnnotationIntrospector() : null), r);
280+
return BasicBeanDescription.forOtherUse(cfg, type, ac);
184281
}
185282
return null;
186283
}

src/main/java/com/fasterxml/jackson/databind/util/LRUMap.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@
1313
* NOTE: since version 2.4.2, this is <b>NOT</b> an LRU-based at all; reason
1414
* being that it is not possible to use JDK components that do LRU _AND_ perform
1515
* well wrt synchronization on multi-core systems. So we choose efficient synchronization
16-
* over potentially more effecient handling of entries.
16+
* over potentially more efficient handling of entries.
17+
*<p>
18+
* And yes, there are efficient LRU implementations such as
19+
* <a href="https://code.google.com/p/concurrentlinkedhashmap/">concurrentlinkedhashmap</a>;
20+
* but at this point we really try to keep external deps to minimum. But perhaps
21+
* a shaded variant may be used one day.
1722
*/
1823
public class LRUMap<K,V>
1924
implements java.io.Serializable
@@ -43,6 +48,22 @@ public V put(K key, V value) {
4348
return _map.put(key, value);
4449
}
4550

51+
/**
52+
* @since 2.5
53+
*/
54+
public V putIfAbsent(K key, V value) {
55+
// not 100% optimal semantically, but better from correctness (never exceeds
56+
// defined maximum) and close enough all in all:
57+
if (_map.size() >= _maxEntries) {
58+
synchronized (this) {
59+
if (_map.size() >= _maxEntries) {
60+
clear();
61+
}
62+
}
63+
}
64+
return _map.putIfAbsent(key, value);
65+
}
66+
4667
// NOTE: key is of type Object only to retain binary backwards-compatibility
4768
public V get(Object key) { return _map.get(key); }
4869

0 commit comments

Comments
 (0)