1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.beans;
18
19 import java.beans.BeanInfo;
20 import java.beans.IntrospectionException;
21 import java.beans.Introspector;
22 import java.beans.PropertyDescriptor;
23 import java.util.Collections;
24 import java.util.Iterator;
25 import java.util.LinkedHashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.concurrent.ConcurrentMap;
31
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34
35 import org.springframework.core.SpringProperties;
36 import org.springframework.core.convert.TypeDescriptor;
37 import org.springframework.core.io.support.SpringFactoriesLoader;
38 import org.springframework.util.ClassUtils;
39 import org.springframework.util.ConcurrentReferenceHashMap;
40 import org.springframework.util.StringUtils;
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74 public class CachedIntrospectionResults {
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93 public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";
94
95
96 private static final boolean shouldIntrospectorIgnoreBeaninfoClasses =
97 SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME);
98
99
100 private static List<BeanInfoFactory> beanInfoFactories = SpringFactoriesLoader.loadFactories(
101 BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader());
102
103 private static final Log logger = LogFactory.getLog(CachedIntrospectionResults.class);
104
105
106
107
108
109 static final Set<ClassLoader> acceptedClassLoaders =
110 Collections.newSetFromMap(new ConcurrentHashMap<ClassLoader, Boolean>(16));
111
112
113
114
115
116 static final ConcurrentMap<Class<?>, CachedIntrospectionResults> strongClassCache =
117 new ConcurrentHashMap<Class<?>, CachedIntrospectionResults>(64);
118
119
120
121
122
123 static final ConcurrentMap<Class<?>, CachedIntrospectionResults> softClassCache =
124 new ConcurrentReferenceHashMap<Class<?>, CachedIntrospectionResults>(64);
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139 public static void acceptClassLoader(ClassLoader classLoader) {
140 if (classLoader != null) {
141 acceptedClassLoaders.add(classLoader);
142 }
143 }
144
145
146
147
148
149
150
151 public static void clearClassLoader(ClassLoader classLoader) {
152 for (Iterator<ClassLoader> it = acceptedClassLoaders.iterator(); it.hasNext();) {
153 ClassLoader registeredLoader = it.next();
154 if (isUnderneathClassLoader(registeredLoader, classLoader)) {
155 it.remove();
156 }
157 }
158 for (Iterator<Class<?>> it = strongClassCache.keySet().iterator(); it.hasNext();) {
159 Class<?> beanClass = it.next();
160 if (isUnderneathClassLoader(beanClass.getClassLoader(), classLoader)) {
161 it.remove();
162 }
163 }
164 for (Iterator<Class<?>> it = softClassCache.keySet().iterator(); it.hasNext();) {
165 Class<?> beanClass = it.next();
166 if (isUnderneathClassLoader(beanClass.getClassLoader(), classLoader)) {
167 it.remove();
168 }
169 }
170 }
171
172
173
174
175
176
177
178 @SuppressWarnings("unchecked")
179 static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException {
180 CachedIntrospectionResults results = strongClassCache.get(beanClass);
181 if (results != null) {
182 return results;
183 }
184 results = softClassCache.get(beanClass);
185 if (results != null) {
186 return results;
187 }
188
189 results = new CachedIntrospectionResults(beanClass);
190 ConcurrentMap<Class<?>, CachedIntrospectionResults> classCacheToUse;
191
192 if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) ||
193 isClassLoaderAccepted(beanClass.getClassLoader())) {
194 classCacheToUse = strongClassCache;
195 }
196 else {
197 if (logger.isDebugEnabled()) {
198 logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe");
199 }
200 classCacheToUse = softClassCache;
201 }
202
203 CachedIntrospectionResults existing = classCacheToUse.putIfAbsent(beanClass, results);
204 return (existing != null ? existing : results);
205 }
206
207
208
209
210
211
212
213
214 private static boolean isClassLoaderAccepted(ClassLoader classLoader) {
215 for (ClassLoader acceptedLoader : acceptedClassLoaders) {
216 if (isUnderneathClassLoader(classLoader, acceptedLoader)) {
217 return true;
218 }
219 }
220 return false;
221 }
222
223
224
225
226
227
228
229 private static boolean isUnderneathClassLoader(ClassLoader candidate, ClassLoader parent) {
230 if (candidate == parent) {
231 return true;
232 }
233 if (candidate == null) {
234 return false;
235 }
236 ClassLoader classLoaderToCheck = candidate;
237 while (classLoaderToCheck != null) {
238 classLoaderToCheck = classLoaderToCheck.getParent();
239 if (classLoaderToCheck == parent) {
240 return true;
241 }
242 }
243 return false;
244 }
245
246
247
248 private final BeanInfo beanInfo;
249
250
251 private final Map<String, PropertyDescriptor> propertyDescriptorCache;
252
253
254 private final ConcurrentMap<PropertyDescriptor, TypeDescriptor> typeDescriptorCache;
255
256
257
258
259
260
261
262 private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
263 try {
264 if (logger.isTraceEnabled()) {
265 logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]");
266 }
267
268 BeanInfo beanInfo = null;
269 for (BeanInfoFactory beanInfoFactory : beanInfoFactories) {
270 beanInfo = beanInfoFactory.getBeanInfo(beanClass);
271 if (beanInfo != null) {
272 break;
273 }
274 }
275 if (beanInfo == null) {
276
277 beanInfo = (shouldIntrospectorIgnoreBeaninfoClasses ?
278 Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) :
279 Introspector.getBeanInfo(beanClass));
280 }
281 this.beanInfo = beanInfo;
282
283 if (logger.isTraceEnabled()) {
284 logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]");
285 }
286 this.propertyDescriptorCache = new LinkedHashMap<String, PropertyDescriptor>();
287
288
289 PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors();
290 for (PropertyDescriptor pd : pds) {
291 if (Class.class.equals(beanClass) &&
292 ("classLoader".equals(pd.getName()) || "protectionDomain".equals(pd.getName()))) {
293
294 continue;
295 }
296 if (logger.isTraceEnabled()) {
297 logger.trace("Found bean property '" + pd.getName() + "'" +
298 (pd.getPropertyType() != null ? " of type [" + pd.getPropertyType().getName() + "]" : "") +
299 (pd.getPropertyEditorClass() != null ?
300 "; editor [" + pd.getPropertyEditorClass().getName() + "]" : ""));
301 }
302 pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
303 this.propertyDescriptorCache.put(pd.getName(), pd);
304 }
305
306 this.typeDescriptorCache = new ConcurrentReferenceHashMap<PropertyDescriptor, TypeDescriptor>();
307 }
308 catch (IntrospectionException ex) {
309 throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass.getName() + "]", ex);
310 }
311 }
312
313 BeanInfo getBeanInfo() {
314 return this.beanInfo;
315 }
316
317 Class<?> getBeanClass() {
318 return this.beanInfo.getBeanDescriptor().getBeanClass();
319 }
320
321 PropertyDescriptor getPropertyDescriptor(String name) {
322 PropertyDescriptor pd = this.propertyDescriptorCache.get(name);
323 if (pd == null && StringUtils.hasLength(name)) {
324
325 pd = this.propertyDescriptorCache.get(name.substring(0, 1).toLowerCase() + name.substring(1));
326 if (pd == null) {
327 pd = this.propertyDescriptorCache.get(name.substring(0, 1).toUpperCase() + name.substring(1));
328 }
329 }
330 return (pd == null || pd instanceof GenericTypeAwarePropertyDescriptor ? pd :
331 buildGenericTypeAwarePropertyDescriptor(getBeanClass(), pd));
332 }
333
334 PropertyDescriptor[] getPropertyDescriptors() {
335 PropertyDescriptor[] pds = new PropertyDescriptor[this.propertyDescriptorCache.size()];
336 int i = 0;
337 for (PropertyDescriptor pd : this.propertyDescriptorCache.values()) {
338 pds[i] = (pd instanceof GenericTypeAwarePropertyDescriptor ? pd :
339 buildGenericTypeAwarePropertyDescriptor(getBeanClass(), pd));
340 i++;
341 }
342 return pds;
343 }
344
345 private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor(Class<?> beanClass, PropertyDescriptor pd) {
346 try {
347 return new GenericTypeAwarePropertyDescriptor(beanClass, pd.getName(), pd.getReadMethod(),
348 pd.getWriteMethod(), pd.getPropertyEditorClass());
349 }
350 catch (IntrospectionException ex) {
351 throw new FatalBeanException("Failed to re-introspect class [" + beanClass.getName() + "]", ex);
352 }
353 }
354
355 TypeDescriptor addTypeDescriptor(PropertyDescriptor pd, TypeDescriptor td) {
356 TypeDescriptor existing = this.typeDescriptorCache.putIfAbsent(pd, td);
357 return (existing != null ? existing : td);
358 }
359
360 TypeDescriptor getTypeDescriptor(PropertyDescriptor pd) {
361 return this.typeDescriptorCache.get(pd);
362 }
363
364 }