Skip to content

Commit fab67b0

Browse files
committed
CachedIntrospectionResults and co consistently avoid synchronization (through use of ConcurrentReferenceHashMap)
As a side effect, through ConcurrentReferenceHashMap, we're using soft references for non-cache-safe bean classes. Issue: SPR-11867
1 parent cb40908 commit fab67b0

File tree

4 files changed

+100
-120
lines changed

4 files changed

+100
-120
lines changed

spring-beans/src/main/java/org/springframework/beans/BeanUtils.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,15 +29,15 @@
2929
import java.util.Date;
3030
import java.util.List;
3131
import java.util.Locale;
32-
import java.util.Map;
33-
import java.util.WeakHashMap;
32+
import java.util.Set;
3433

3534
import org.apache.commons.logging.Log;
3635
import org.apache.commons.logging.LogFactory;
3736

3837
import org.springframework.core.MethodParameter;
3938
import org.springframework.util.Assert;
4039
import org.springframework.util.ClassUtils;
40+
import org.springframework.util.ConcurrentReferenceHashMap;
4141
import org.springframework.util.ReflectionUtils;
4242
import org.springframework.util.StringUtils;
4343

@@ -57,9 +57,8 @@ public abstract class BeanUtils {
5757

5858
private static final Log logger = LogFactory.getLog(BeanUtils.class);
5959

60-
// Effectively using a WeakHashMap as a Set
61-
private static final Map<Class<?>, Boolean> unknownEditorTypes =
62-
Collections.synchronizedMap(new WeakHashMap<Class<?>, Boolean>());
60+
private static final Set<Class<?>> unknownEditorTypes =
61+
Collections.newSetFromMap(new ConcurrentReferenceHashMap<Class<?>, Boolean>(64));
6362

6463

6564
/**
@@ -404,7 +403,7 @@ public static PropertyDescriptor findPropertyForMethod(Method method) throws Bea
404403
* @return the corresponding editor, or {@code null} if none found
405404
*/
406405
public static PropertyEditor findEditorByConvention(Class<?> targetType) {
407-
if (targetType == null || targetType.isArray() || unknownEditorTypes.containsKey(targetType)) {
406+
if (targetType == null || targetType.isArray() || unknownEditorTypes.contains(targetType)) {
408407
return null;
409408
}
410409
ClassLoader cl = targetType.getClassLoader();
@@ -431,7 +430,7 @@ public static PropertyEditor findEditorByConvention(Class<?> targetType) {
431430
logger.warn("Editor class [" + editorName +
432431
"] does not implement [java.beans.PropertyEditor] interface");
433432
}
434-
unknownEditorTypes.put(targetType, Boolean.TRUE);
433+
unknownEditorTypes.add(targetType);
435434
return null;
436435
}
437436
return (PropertyEditor) instantiateClass(editorClass);
@@ -441,7 +440,7 @@ public static PropertyEditor findEditorByConvention(Class<?> targetType) {
441440
logger.debug("No property editor [" + editorName + "] found for type " +
442441
targetType.getName() + " according to 'Editor' suffix convention");
443442
}
444-
unknownEditorTypes.put(targetType, Boolean.TRUE);
443+
unknownEditorTypes.add(targetType);
445444
return null;
446445
}
447446
}

spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java

Lines changed: 46 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,12 @@
2020
import java.beans.IntrospectionException;
2121
import java.beans.Introspector;
2222
import java.beans.PropertyDescriptor;
23-
import java.lang.ref.Reference;
24-
import java.lang.ref.WeakReference;
25-
import java.util.HashSet;
23+
import java.util.Collections;
2624
import java.util.Iterator;
2725
import java.util.LinkedHashMap;
2826
import java.util.List;
2927
import java.util.Map;
3028
import java.util.Set;
31-
import java.util.WeakHashMap;
3229
import java.util.concurrent.ConcurrentHashMap;
3330

3431
import org.apache.commons.logging.Log;
@@ -38,6 +35,7 @@
3835
import org.springframework.core.convert.TypeDescriptor;
3936
import org.springframework.core.io.support.SpringFactoriesLoader;
4037
import org.springframework.util.ClassUtils;
38+
import org.springframework.util.ConcurrentReferenceHashMap;
4139
import org.springframework.util.StringUtils;
4240

4341
/**
@@ -107,14 +105,22 @@ public class CachedIntrospectionResults {
107105
* Set of ClassLoaders that this CachedIntrospectionResults class will always
108106
* accept classes from, even if the classes do not qualify as cache-safe.
109107
*/
110-
static final Set<ClassLoader> acceptedClassLoaders = new HashSet<ClassLoader>();
108+
static final Set<ClassLoader> acceptedClassLoaders =
109+
Collections.newSetFromMap(new ConcurrentHashMap<ClassLoader, Boolean>(16));
111110

112111
/**
113-
* Map keyed by class containing CachedIntrospectionResults.
114-
* Needs to be a WeakHashMap with WeakReferences as values to allow
115-
* for proper garbage collection in case of multiple class loaders.
112+
* Map keyed by Class containing CachedIntrospectionResults, strongly held.
113+
* This variant is being used for cache-safe bean classes.
116114
*/
117-
static final Map<Class<?>, Object> classCache = new WeakHashMap<Class<?>, Object>();
115+
static final Map<Class<?>, CachedIntrospectionResults> strongClassCache =
116+
new ConcurrentHashMap<Class<?>, CachedIntrospectionResults>(64);
117+
118+
/**
119+
* Map keyed by Class containing CachedIntrospectionResults, softly held.
120+
* This variant is being used for non-cache-safe bean classes.
121+
*/
122+
static final Map<Class<?>, CachedIntrospectionResults> softClassCache =
123+
new ConcurrentReferenceHashMap<Class<?>, CachedIntrospectionResults>(64);
118124

119125

120126
/**
@@ -131,9 +137,7 @@ public class CachedIntrospectionResults {
131137
*/
132138
public static void acceptClassLoader(ClassLoader classLoader) {
133139
if (classLoader != null) {
134-
synchronized (acceptedClassLoaders) {
135-
acceptedClassLoaders.add(classLoader);
136-
}
140+
acceptedClassLoaders.add(classLoader);
137141
}
138142
}
139143

@@ -144,20 +148,22 @@ public static void acceptClassLoader(ClassLoader classLoader) {
144148
* @param classLoader the ClassLoader to clear the cache for
145149
*/
146150
public static void clearClassLoader(ClassLoader classLoader) {
147-
synchronized (classCache) {
148-
for (Iterator<Class<?>> it = classCache.keySet().iterator(); it.hasNext();) {
149-
Class<?> beanClass = it.next();
150-
if (isUnderneathClassLoader(beanClass.getClassLoader(), classLoader)) {
151-
it.remove();
152-
}
151+
for (Iterator<ClassLoader> it = acceptedClassLoaders.iterator(); it.hasNext();) {
152+
ClassLoader registeredLoader = it.next();
153+
if (isUnderneathClassLoader(registeredLoader, classLoader)) {
154+
it.remove();
153155
}
154156
}
155-
synchronized (acceptedClassLoaders) {
156-
for (Iterator<ClassLoader> it = acceptedClassLoaders.iterator(); it.hasNext();) {
157-
ClassLoader registeredLoader = it.next();
158-
if (isUnderneathClassLoader(registeredLoader, classLoader)) {
159-
it.remove();
160-
}
157+
for (Iterator<Class<?>> it = strongClassCache.keySet().iterator(); it.hasNext();) {
158+
Class<?> beanClass = it.next();
159+
if (isUnderneathClassLoader(beanClass.getClassLoader(), classLoader)) {
160+
it.remove();
161+
}
162+
}
163+
for (Iterator<Class<?>> it = softClassCache.keySet().iterator(); it.hasNext();) {
164+
Class<?> beanClass = it.next();
165+
if (isUnderneathClassLoader(beanClass.getClassLoader(), classLoader)) {
166+
it.remove();
161167
}
162168
}
163169
}
@@ -170,35 +176,25 @@ public static void clearClassLoader(ClassLoader classLoader) {
170176
*/
171177
@SuppressWarnings("unchecked")
172178
static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException {
173-
CachedIntrospectionResults results;
174-
Object value;
175-
synchronized (classCache) {
176-
value = classCache.get(beanClass);
179+
CachedIntrospectionResults results = strongClassCache.get(beanClass);
180+
if (results != null) {
181+
return results;
177182
}
178-
if (value instanceof Reference) {
179-
Reference<CachedIntrospectionResults> ref = (Reference<CachedIntrospectionResults>) value;
180-
results = ref.get();
183+
results = softClassCache.get(beanClass);
184+
if (results != null) {
185+
return results;
181186
}
182-
else {
183-
results = (CachedIntrospectionResults) value;
187+
188+
results = new CachedIntrospectionResults(beanClass);
189+
if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) ||
190+
isClassLoaderAccepted(beanClass.getClassLoader())) {
191+
strongClassCache.put(beanClass, results);
184192
}
185-
if (results == null) {
186-
if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) ||
187-
isClassLoaderAccepted(beanClass.getClassLoader())) {
188-
results = new CachedIntrospectionResults(beanClass);
189-
synchronized (classCache) {
190-
classCache.put(beanClass, results);
191-
}
192-
}
193-
else {
194-
if (logger.isDebugEnabled()) {
195-
logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe");
196-
}
197-
results = new CachedIntrospectionResults(beanClass);
198-
synchronized (classCache) {
199-
classCache.put(beanClass, new WeakReference<CachedIntrospectionResults>(results));
200-
}
193+
else {
194+
if (logger.isDebugEnabled()) {
195+
logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe");
201196
}
197+
softClassCache.put(beanClass, results);
202198
}
203199
return results;
204200
}
@@ -211,13 +207,7 @@ static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansExcep
211207
* @see #acceptClassLoader
212208
*/
213209
private static boolean isClassLoaderAccepted(ClassLoader classLoader) {
214-
// Iterate over array copy in order to avoid synchronization for the entire
215-
// ClassLoader check (avoiding a synchronized acceptedClassLoaders Iterator).
216-
ClassLoader[] acceptedLoaderArray;
217-
synchronized (acceptedClassLoaders) {
218-
acceptedLoaderArray = acceptedClassLoaders.toArray(new ClassLoader[acceptedClassLoaders.size()]);
219-
}
220-
for (ClassLoader acceptedLoader : acceptedLoaderArray) {
210+
for (ClassLoader acceptedLoader : acceptedClassLoaders) {
221211
if (isUnderneathClassLoader(classLoader, acceptedLoader)) {
222212
return true;
223213
}

spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -42,18 +42,18 @@ class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
4242

4343
private final Class<?> beanClass;
4444

45+
private final Class<?> propertyEditorClass;
46+
4547
private final Method readMethod;
4648

4749
private final Method writeMethod;
4850

49-
private final Class<?> propertyEditorClass;
50-
5151
private volatile Set<Method> ambiguousWriteMethods;
5252

53-
private Class<?> propertyType;
54-
5553
private MethodParameter writeMethodParameter;
5654

55+
private Class<?> propertyType;
56+
5757

5858
public GenericTypeAwarePropertyDescriptor(Class<?> beanClass, String propertyName,
5959
Method readMethod, Method writeMethod, Class<?> propertyEditorClass)
@@ -78,27 +78,44 @@ public GenericTypeAwarePropertyDescriptor(Class<?> beanClass, String propertyNam
7878
this.readMethod = readMethodToUse;
7979
this.writeMethod = writeMethodToUse;
8080

81-
if (this.writeMethod != null && this.readMethod == null) {
82-
// Write method not matched against read method: potentially ambiguous through
83-
// several overloaded variants, in which case an arbitrary winner has been chosen
84-
// by the JDK's JavaBeans Introspector...
85-
Set<Method> ambiguousCandidates = new HashSet<Method>();
86-
for (Method method : beanClass.getMethods()) {
87-
if (method.getName().equals(writeMethodToUse.getName()) &&
88-
!method.equals(writeMethodToUse) && !method.isBridge()) {
89-
ambiguousCandidates.add(method);
81+
if (this.writeMethod != null) {
82+
if (this.readMethod == null) {
83+
// Write method not matched against read method: potentially ambiguous through
84+
// several overloaded variants, in which case an arbitrary winner has been chosen
85+
// by the JDK's JavaBeans Introspector...
86+
Set<Method> ambiguousCandidates = new HashSet<Method>();
87+
for (Method method : beanClass.getMethods()) {
88+
if (method.getName().equals(writeMethodToUse.getName()) &&
89+
!method.equals(writeMethodToUse) && !method.isBridge()) {
90+
ambiguousCandidates.add(method);
91+
}
92+
}
93+
if (!ambiguousCandidates.isEmpty()) {
94+
this.ambiguousWriteMethods = ambiguousCandidates;
9095
}
9196
}
92-
if (!ambiguousCandidates.isEmpty()) {
93-
this.ambiguousWriteMethods = ambiguousCandidates;
94-
}
97+
this.writeMethodParameter = new MethodParameter(this.writeMethod, 0);
98+
GenericTypeResolver.resolveParameterType(this.writeMethodParameter, this.beanClass);
99+
}
100+
101+
if (this.readMethod != null) {
102+
this.propertyType = GenericTypeResolver.resolveReturnType(this.readMethod, this.beanClass);
103+
}
104+
else if (this.writeMethodParameter != null) {
105+
this.propertyType = this.writeMethodParameter.getParameterType();
95106
}
96107
}
97108

109+
98110
public Class<?> getBeanClass() {
99111
return this.beanClass;
100112
}
101113

114+
@Override
115+
public Class<?> getPropertyEditorClass() {
116+
return this.propertyEditorClass;
117+
}
118+
102119
@Override
103120
public Method getReadMethod() {
104121
return this.readMethod;
@@ -120,39 +137,13 @@ public Method getWriteMethodForActualAccess() {
120137
return this.writeMethod;
121138
}
122139

123-
@Override
124-
public Class<?> getPropertyEditorClass() {
125-
return this.propertyEditorClass;
140+
public MethodParameter getWriteMethodParameter() {
141+
return this.writeMethodParameter;
126142
}
127143

128144
@Override
129-
public synchronized Class<?> getPropertyType() {
130-
if (this.propertyType == null) {
131-
if (this.readMethod != null) {
132-
this.propertyType = GenericTypeResolver.resolveReturnType(this.readMethod, this.beanClass);
133-
}
134-
else {
135-
MethodParameter writeMethodParam = getWriteMethodParameter();
136-
if (writeMethodParam != null) {
137-
this.propertyType = writeMethodParam.getParameterType();
138-
}
139-
else {
140-
this.propertyType = super.getPropertyType();
141-
}
142-
}
143-
}
145+
public Class<?> getPropertyType() {
144146
return this.propertyType;
145147
}
146148

147-
public synchronized MethodParameter getWriteMethodParameter() {
148-
if (this.writeMethod == null) {
149-
return null;
150-
}
151-
if (this.writeMethodParameter == null) {
152-
this.writeMethodParameter = new MethodParameter(this.writeMethod, 0);
153-
GenericTypeResolver.resolveParameterType(this.writeMethodParameter, this.beanClass);
154-
}
155-
return this.writeMethodParameter;
156-
}
157-
158149
}

0 commit comments

Comments
 (0)