Skip to content

Commit 45828cb

Browse files
committed
Check BeanInfoFactory for interface introspection as well
Issue: SPR-16322
1 parent 446e7ed commit 45828cb

File tree

3 files changed

+59
-50
lines changed

3 files changed

+59
-50
lines changed

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

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -244,6 +244,27 @@ private static boolean isUnderneathClassLoader(@Nullable ClassLoader candidate,
244244
return false;
245245
}
246246

247+
/**
248+
* Retrieve a {@link BeanInfo} descriptor for the given target class.
249+
* @param beanClass the target class to introspect
250+
* @param ignoreBeaninfoClasses whether to apply {@link Introspector#IGNORE_ALL_BEANINFO} mode
251+
* @return the resulting {@code BeanInfo} descriptor (never {@code null})
252+
* @throws IntrospectionException from the underlying {@link Introspector}
253+
*/
254+
private static BeanInfo getBeanInfo(Class<?> beanClass, boolean ignoreBeaninfoClasses)
255+
throws IntrospectionException {
256+
257+
for (BeanInfoFactory beanInfoFactory : beanInfoFactories) {
258+
BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanClass);
259+
if (beanInfo != null) {
260+
return beanInfo;
261+
}
262+
}
263+
return (ignoreBeaninfoClasses ?
264+
Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) :
265+
Introspector.getBeanInfo(beanClass));
266+
}
267+
247268

248269
/** The BeanInfo object for the introspected bean class */
249270
private final BeanInfo beanInfo;
@@ -265,21 +286,7 @@ private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
265286
if (logger.isTraceEnabled()) {
266287
logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]");
267288
}
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, shouldIntrospectorIgnoreBeaninfoClasses);
283290

284291
if (logger.isTraceEnabled()) {
285292
logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]");
@@ -307,15 +314,17 @@ private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
307314
// Explicitly check implemented interfaces for setter/getter methods as well,
308315
// in particular for Java 8 default methods...
309316
Class<?> clazz = beanClass;
310-
while (clazz != null) {
317+
while (clazz != null && clazz != Object.class) {
311318
Class<?>[] ifcs = clazz.getInterfaces();
312319
for (Class<?> ifc : ifcs) {
313-
BeanInfo ifcInfo = Introspector.getBeanInfo(ifc, Introspector.IGNORE_ALL_BEANINFO);
314-
PropertyDescriptor[] ifcPds = ifcInfo.getPropertyDescriptors();
315-
for (PropertyDescriptor pd : ifcPds) {
316-
if (!this.propertyDescriptorCache.containsKey(pd.getName())) {
317-
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
318-
this.propertyDescriptorCache.put(pd.getName(), pd);
320+
if (!ClassUtils.isJavaLanguageInterface(ifc)) {
321+
BeanInfo ifcInfo = getBeanInfo(ifc, true);
322+
PropertyDescriptor[] ifcPds = ifcInfo.getPropertyDescriptors();
323+
for (PropertyDescriptor pd : ifcPds) {
324+
if (!this.propertyDescriptorCache.containsKey(pd.getName())) {
325+
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
326+
this.propertyDescriptorCache.put(pd.getName(), pd);
327+
}
319328
}
320329
}
321330
}
@@ -329,6 +338,7 @@ private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
329338
}
330339
}
331340

341+
332342
BeanInfo getBeanInfo() {
333343
return this.beanInfo;
334344
}

spring-core/src/main/java/org/springframework/core/Conventions.java

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -16,16 +16,10 @@
1616

1717
package org.springframework.core;
1818

19-
import java.io.Externalizable;
20-
import java.io.Serializable;
2119
import java.lang.reflect.Method;
2220
import java.lang.reflect.Proxy;
23-
import java.util.Arrays;
2421
import java.util.Collection;
25-
import java.util.Collections;
26-
import java.util.HashSet;
2722
import java.util.Iterator;
28-
import java.util.Set;
2923

3024
import org.springframework.lang.Nullable;
3125
import org.springframework.util.Assert;
@@ -47,36 +41,21 @@ public abstract class Conventions {
4741
*/
4842
private static final String PLURAL_SUFFIX = "List";
4943

50-
/**
51-
* Set of interfaces that are supposed to be ignored
52-
* when searching for the 'primary' interface of a proxy.
53-
*/
54-
private static final Set<Class<?>> IGNORED_INTERFACES;
55-
56-
static {
57-
IGNORED_INTERFACES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
58-
Serializable.class, Externalizable.class, Cloneable.class, Comparable.class)));
59-
}
60-
61-
private static final ReactiveAdapterRegistry reactiveAdapterRegistry =
62-
ReactiveAdapterRegistry.getSharedInstance();
44+
private static final ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
6345

6446

6547
/**
6648
* Determine the conventional variable name for the supplied {@code Object}
6749
* based on its concrete type. The convention used is to return the
6850
* un-capitalized short name of the {@code Class}, according to JavaBeans
6951
* property naming rules.
70-
*
7152
* <p>For example:<br>
7253
* {@code com.myapp.Product} becomes {@code "product"}<br>
7354
* {@code com.myapp.MyProduct} becomes {@code "myProduct"}<br>
7455
* {@code com.myapp.UKProduct} becomes {@code "UKProduct"}<br>
75-
*
7656
* <p>For arrays the pluralized version of the array component type is used.
7757
* For {@code Collection}s an attempt is made to 'peek ahead' to determine
7858
* the component type and return its pluralized version.
79-
*
8059
* @param value the value to generate a variable name for
8160
* @return the generated variable name
8261
*/
@@ -110,12 +89,10 @@ else if (value instanceof Collection) {
11089
/**
11190
* Determine the conventional variable name for the given parameter taking
11291
* the generic collection type, if any, into account.
113-
*
11492
* <p>As of 5.0 this method supports reactive types:<br>
11593
* {@code Mono<com.myapp.Product>} becomes {@code "productMono"}<br>
11694
* {@code Flux<com.myapp.MyProduct>} becomes {@code "myProductFlux"}<br>
11795
* {@code Observable<com.myapp.MyProduct>} becomes {@code "myProductObservable"}<br>
118-
*
11996
* @param parameter the method or constructor parameter
12097
* @return the generated variable name
12198
*/
@@ -295,7 +272,7 @@ private static Class<?> getClassForValue(Object value) {
295272
if (Proxy.isProxyClass(valueClass)) {
296273
Class<?>[] ifcs = valueClass.getInterfaces();
297274
for (Class<?> ifc : ifcs) {
298-
if (!IGNORED_INTERFACES.contains(ifc)) {
275+
if (!ClassUtils.isJavaLanguageInterface(ifc)) {
299276
return ifc;
300277
}
301278
}

spring-core/src/main/java/org/springframework/util/ClassUtils.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -17,6 +17,8 @@
1717
package org.springframework.util;
1818

1919
import java.beans.Introspector;
20+
import java.io.Externalizable;
21+
import java.io.Serializable;
2022
import java.lang.reflect.Array;
2123
import java.lang.reflect.Constructor;
2224
import java.lang.reflect.Method;
@@ -74,6 +76,13 @@ public abstract class ClassUtils {
7476
public static final String CLASS_FILE_SUFFIX = ".class";
7577

7678

79+
/**
80+
* Common Java language interfaces which are supposed to be ignored
81+
* when searching for 'primary' user-level interfaces.
82+
*/
83+
private static final Set<Class<?>> javaLanguageInterfaces = new HashSet<>(
84+
Arrays.asList(Serializable.class, Externalizable.class, Cloneable.class, Comparable.class));
85+
7786
/**
7887
* Map with primitive wrapper type as key and corresponding primitive
7988
* type as value, for example: Integer.class -> int.class.
@@ -1218,6 +1227,19 @@ public static boolean isVisible(Class<?> clazz, @Nullable ClassLoader classLoade
12181227
}
12191228
}
12201229

1230+
/**
1231+
* Determine whether the given interface is a common Java language interface:
1232+
* {@link Serializable}, {@link Externalizable}, {@link Cloneable}, {@link Comparable}
1233+
* - all of which can be ignored when looking for 'primary' user-level interfaces.
1234+
* Common characteristics: no service-level operations, no bean property methods,
1235+
* no default methods.
1236+
* @param ifc the interface to check
1237+
* @since 5.0.3
1238+
*/
1239+
public static boolean isJavaLanguageInterface(Class<?> ifc) {
1240+
return javaLanguageInterfaces.contains(ifc);
1241+
}
1242+
12211243
/**
12221244
* Check whether the given object is a CGLIB proxy.
12231245
* @param object the object to check

0 commit comments

Comments
 (0)