1
1
/*
2
- * Copyright 2002-2020 the original author or authors.
2
+ * Copyright 2002-2022 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
20
20
import java .beans .IntrospectionException ;
21
21
import java .beans .Introspector ;
22
22
import java .beans .PropertyDescriptor ;
23
+ import java .net .URL ;
23
24
import java .security .ProtectionDomain ;
24
25
import java .util .Collections ;
25
26
import java .util .Iterator ;
30
31
import java .util .concurrent .ConcurrentHashMap ;
31
32
import java .util .concurrent .ConcurrentMap ;
32
33
34
+
33
35
import org .apache .commons .logging .Log ;
34
36
import org .apache .commons .logging .LogFactory ;
35
37
@@ -93,11 +95,13 @@ public class CachedIntrospectionResults {
93
95
*/
94
96
public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore" ;
95
97
98
+ private static final PropertyDescriptor [] EMPTY_PROPERTY_DESCRIPTOR_ARRAY = {};
99
+
96
100
97
101
private static final boolean shouldIntrospectorIgnoreBeaninfoClasses =
98
102
SpringProperties .getFlag (IGNORE_BEANINFO_PROPERTY_NAME );
99
103
100
- /** Stores the BeanInfoFactory instances */
104
+ /** Stores the BeanInfoFactory instances. */
101
105
private static final List <BeanInfoFactory > beanInfoFactories = SpringFactoriesLoader .loadFactories (
102
106
BeanInfoFactory .class , CachedIntrospectionResults .class .getClassLoader ());
103
107
@@ -176,7 +180,6 @@ public static void clearClassLoader(ClassLoader classLoader) {
176
180
* @return the corresponding CachedIntrospectionResults
177
181
* @throws BeansException in case of introspection failure
178
182
*/
179
- @ SuppressWarnings ("unchecked" )
180
183
static CachedIntrospectionResults forClass (Class <?> beanClass ) throws BeansException {
181
184
CachedIntrospectionResults results = strongClassCache .get (beanClass );
182
185
if (results != null ) {
@@ -244,14 +247,32 @@ private static boolean isUnderneathClassLoader(ClassLoader candidate, ClassLoade
244
247
return false ;
245
248
}
246
249
250
+ /**
251
+ * Retrieve a {@link BeanInfo} descriptor for the given target class.
252
+ * @param beanClass the target class to introspect
253
+ * @return the resulting {@code BeanInfo} descriptor (never {@code null})
254
+ * @throws IntrospectionException from the underlying {@link Introspector}
255
+ */
256
+ private static BeanInfo getBeanInfo (Class <?> beanClass ) throws IntrospectionException {
257
+ for (BeanInfoFactory beanInfoFactory : beanInfoFactories ) {
258
+ BeanInfo beanInfo = beanInfoFactory .getBeanInfo (beanClass );
259
+ if (beanInfo != null ) {
260
+ return beanInfo ;
261
+ }
262
+ }
263
+ return (shouldIntrospectorIgnoreBeaninfoClasses ?
264
+ Introspector .getBeanInfo (beanClass , Introspector .IGNORE_ALL_BEANINFO ) :
265
+ Introspector .getBeanInfo (beanClass ));
266
+ }
267
+
247
268
248
- /** The BeanInfo object for the introspected bean class */
269
+ /** The BeanInfo object for the introspected bean class. */
249
270
private final BeanInfo beanInfo ;
250
271
251
- /** PropertyDescriptor objects keyed by property name String */
252
- private final Map <String , PropertyDescriptor > propertyDescriptorCache ;
272
+ /** PropertyDescriptor objects keyed by property name String. */
273
+ private final Map <String , PropertyDescriptor > propertyDescriptors ;
253
274
254
- /** TypeDescriptor objects keyed by PropertyDescriptor */
275
+ /** TypeDescriptor objects keyed by PropertyDescriptor. */
255
276
private final ConcurrentMap <PropertyDescriptor , TypeDescriptor > typeDescriptorCache ;
256
277
257
278
@@ -265,37 +286,27 @@ private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
265
286
if (logger .isTraceEnabled ()) {
266
287
logger .trace ("Getting BeanInfo for class [" + beanClass .getName () + "]" );
267
288
}
268
-
269
- BeanInfo beanInfo = null ;
270
- for (BeanInfoFactory beanInfoFactory : beanInfoFactories ) {
271
- beanInfo = beanInfoFactory .getBeanInfo (beanClass );
272
- if (beanInfo != null ) {
273
- break ;
274
- }
275
- }
276
- if (beanInfo == null ) {
277
- // If none of the factories supported the class, fall back to the default
278
- beanInfo = (shouldIntrospectorIgnoreBeaninfoClasses ?
279
- Introspector .getBeanInfo (beanClass , Introspector .IGNORE_ALL_BEANINFO ) :
280
- Introspector .getBeanInfo (beanClass ));
281
- }
282
- this .beanInfo = beanInfo ;
289
+ this .beanInfo = getBeanInfo (beanClass );
283
290
284
291
if (logger .isTraceEnabled ()) {
285
292
logger .trace ("Caching PropertyDescriptors for class [" + beanClass .getName () + "]" );
286
293
}
287
- this .propertyDescriptorCache = new LinkedHashMap <String , PropertyDescriptor >();
294
+ this .propertyDescriptors = new LinkedHashMap <String , PropertyDescriptor >();
288
295
289
296
// This call is slow so we do it once.
290
297
PropertyDescriptor [] pds = this .beanInfo .getPropertyDescriptors ();
291
298
for (PropertyDescriptor pd : pds ) {
292
- if (Class .class == beanClass && (!"name" .equals (pd .getName ()) && !pd .getName ().endsWith ("Name" ))) {
299
+ if (Class .class == beanClass && !("name" .equals (pd .getName ()) ||
300
+ (pd .getName ().endsWith ("Name" ) && String .class == pd .getPropertyType ()))) {
293
301
// Only allow all name variants of Class properties
294
302
continue ;
295
303
}
296
- if (pd .getPropertyType () != null && (ClassLoader .class .isAssignableFrom (pd .getPropertyType ())
297
- || ProtectionDomain .class .isAssignableFrom (pd .getPropertyType ()))) {
298
- // Ignore ClassLoader and ProtectionDomain types - nobody needs to bind to those
304
+ if (URL .class == beanClass && "content" .equals (pd .getName ())) {
305
+ // Only allow URL attribute introspection, not content resolution
306
+ continue ;
307
+ }
308
+ if (pd .getWriteMethod () == null && isInvalidReadOnlyPropertyType (pd .getPropertyType ())) {
309
+ // Ignore read-only properties such as ClassLoader - no need to bind to those
299
310
continue ;
300
311
}
301
312
if (logger .isTraceEnabled ()) {
@@ -305,30 +316,15 @@ private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
305
316
"; editor [" + pd .getPropertyEditorClass ().getName () + "]" : "" ));
306
317
}
307
318
pd = buildGenericTypeAwarePropertyDescriptor (beanClass , pd );
308
- this .propertyDescriptorCache .put (pd .getName (), pd );
319
+ this .propertyDescriptors .put (pd .getName (), pd );
309
320
}
310
321
311
322
// Explicitly check implemented interfaces for setter/getter methods as well,
312
323
// in particular for Java 8 default methods...
313
- Class <?> clazz = beanClass ;
314
- while (clazz != null ) {
315
- Class <?>[] ifcs = clazz .getInterfaces ();
316
- for (Class <?> ifc : ifcs ) {
317
- BeanInfo ifcInfo = Introspector .getBeanInfo (ifc , Introspector .IGNORE_ALL_BEANINFO );
318
- PropertyDescriptor [] ifcPds = ifcInfo .getPropertyDescriptors ();
319
- for (PropertyDescriptor pd : ifcPds ) {
320
- if (!this .propertyDescriptorCache .containsKey (pd .getName ())) {
321
- pd = buildGenericTypeAwarePropertyDescriptor (beanClass , pd );
322
- if (pd .getPropertyType () != null && (ClassLoader .class .isAssignableFrom (pd .getPropertyType ())
323
- || ProtectionDomain .class .isAssignableFrom (pd .getPropertyType ()))) {
324
- // Ignore ClassLoader and ProtectionDomain types - nobody needs to bind to those
325
- continue ;
326
- }
327
- this .propertyDescriptorCache .put (pd .getName (), pd );
328
- }
329
- }
330
- }
331
- clazz = clazz .getSuperclass ();
324
+ Class <?> currClass = beanClass ;
325
+ while (currClass != null && currClass != Object .class ) {
326
+ introspectInterfaces (beanClass , currClass );
327
+ currClass = currClass .getSuperclass ();
332
328
}
333
329
334
330
this .typeDescriptorCache = new ConcurrentReferenceHashMap <PropertyDescriptor , TypeDescriptor >();
@@ -338,6 +334,35 @@ private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
338
334
}
339
335
}
340
336
337
+ private void introspectInterfaces (Class <?> beanClass , Class <?> currClass ) throws IntrospectionException {
338
+ for (Class <?> ifc : currClass .getInterfaces ()) {
339
+ if (!ClassUtils .isJavaLanguageInterface (ifc )) {
340
+ for (PropertyDescriptor pd : getBeanInfo (ifc ).getPropertyDescriptors ()) {
341
+ PropertyDescriptor existingPd = this .propertyDescriptors .get (pd .getName ());
342
+ if (existingPd == null ||
343
+ (existingPd .getReadMethod () == null && pd .getReadMethod () != null )) {
344
+ // GenericTypeAwarePropertyDescriptor leniently resolves a set* write method
345
+ // against a declared read method, so we prefer read method descriptors here.
346
+ pd = buildGenericTypeAwarePropertyDescriptor (beanClass , pd );
347
+ if (pd .getWriteMethod () == null && isInvalidReadOnlyPropertyType (pd .getPropertyType ())) {
348
+ // Ignore read-only properties such as ClassLoader - no need to bind to those
349
+ continue ;
350
+ }
351
+ this .propertyDescriptors .put (pd .getName (), pd );
352
+ }
353
+ }
354
+ introspectInterfaces (ifc , ifc );
355
+ }
356
+ }
357
+ }
358
+
359
+ private boolean isInvalidReadOnlyPropertyType (Class <?> returnType ) {
360
+ return (returnType != null && (AutoCloseable .class .isAssignableFrom (returnType ) ||
361
+ ClassLoader .class .isAssignableFrom (returnType ) ||
362
+ ProtectionDomain .class .isAssignableFrom (returnType )));
363
+ }
364
+
365
+
341
366
BeanInfo getBeanInfo () {
342
367
return this .beanInfo ;
343
368
}
@@ -346,28 +371,22 @@ Class<?> getBeanClass() {
346
371
return this .beanInfo .getBeanDescriptor ().getBeanClass ();
347
372
}
348
373
374
+
349
375
PropertyDescriptor getPropertyDescriptor (String name ) {
350
- PropertyDescriptor pd = this .propertyDescriptorCache .get (name );
376
+ PropertyDescriptor pd = this .propertyDescriptors .get (name );
351
377
if (pd == null && StringUtils .hasLength (name )) {
352
378
// Same lenient fallback checking as in Property...
353
- pd = this .propertyDescriptorCache .get (StringUtils .uncapitalize (name ));
379
+ pd = this .propertyDescriptors .get (StringUtils .uncapitalize (name ));
354
380
if (pd == null ) {
355
- pd = this .propertyDescriptorCache .get (StringUtils .capitalize (name ));
381
+ pd = this .propertyDescriptors .get (StringUtils .capitalize (name ));
356
382
}
357
383
}
358
384
return (pd == null || pd instanceof GenericTypeAwarePropertyDescriptor ? pd :
359
385
buildGenericTypeAwarePropertyDescriptor (getBeanClass (), pd ));
360
386
}
361
387
362
388
PropertyDescriptor [] getPropertyDescriptors () {
363
- PropertyDescriptor [] pds = new PropertyDescriptor [this .propertyDescriptorCache .size ()];
364
- int i = 0 ;
365
- for (PropertyDescriptor pd : this .propertyDescriptorCache .values ()) {
366
- pds [i ] = (pd instanceof GenericTypeAwarePropertyDescriptor ? pd :
367
- buildGenericTypeAwarePropertyDescriptor (getBeanClass (), pd ));
368
- i ++;
369
- }
370
- return pds ;
389
+ return this .propertyDescriptors .values ().toArray (EMPTY_PROPERTY_DESCRIPTOR_ARRAY );
371
390
}
372
391
373
392
private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor (Class <?> beanClass , PropertyDescriptor pd ) {
@@ -385,6 +404,7 @@ TypeDescriptor addTypeDescriptor(PropertyDescriptor pd, TypeDescriptor td) {
385
404
return (existing != null ? existing : td );
386
405
}
387
406
407
+
388
408
TypeDescriptor getTypeDescriptor (PropertyDescriptor pd ) {
389
409
return this .typeDescriptorCache .get (pd );
390
410
}
0 commit comments