From 45828cb934174479a230a10bb5d5b64d0d56867b Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 8 Jan 2018 18:05:02 +0100 Subject: [PATCH] Check BeanInfoFactory for interface introspection as well Issue: SPR-16322 --- .../beans/CachedIntrospectionResults.java | 56 +++++++++++-------- .../org/springframework/core/Conventions.java | 29 +--------- .../org/springframework/util/ClassUtils.java | 24 +++++++- 3 files changed, 59 insertions(+), 50 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java index 37ade58ce8..eaf54e862a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java +++ b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -244,6 +244,27 @@ public class CachedIntrospectionResults { return false; } + /** + * Retrieve a {@link BeanInfo} descriptor for the given target class. + * @param beanClass the target class to introspect + * @param ignoreBeaninfoClasses whether to apply {@link Introspector#IGNORE_ALL_BEANINFO} mode + * @return the resulting {@code BeanInfo} descriptor (never {@code null}) + * @throws IntrospectionException from the underlying {@link Introspector} + */ + private static BeanInfo getBeanInfo(Class beanClass, boolean ignoreBeaninfoClasses) + throws IntrospectionException { + + for (BeanInfoFactory beanInfoFactory : beanInfoFactories) { + BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanClass); + if (beanInfo != null) { + return beanInfo; + } + } + return (ignoreBeaninfoClasses ? + Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) : + Introspector.getBeanInfo(beanClass)); + } + /** The BeanInfo object for the introspected bean class */ private final BeanInfo beanInfo; @@ -265,21 +286,7 @@ public class CachedIntrospectionResults { if (logger.isTraceEnabled()) { logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]"); } - - BeanInfo beanInfo = null; - for (BeanInfoFactory beanInfoFactory : beanInfoFactories) { - beanInfo = beanInfoFactory.getBeanInfo(beanClass); - if (beanInfo != null) { - break; - } - } - if (beanInfo == null) { - // If none of the factories supported the class, fall back to the default - beanInfo = (shouldIntrospectorIgnoreBeaninfoClasses ? - Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) : - Introspector.getBeanInfo(beanClass)); - } - this.beanInfo = beanInfo; + this.beanInfo = getBeanInfo(beanClass, shouldIntrospectorIgnoreBeaninfoClasses); if (logger.isTraceEnabled()) { logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]"); @@ -307,15 +314,17 @@ public class CachedIntrospectionResults { // Explicitly check implemented interfaces for setter/getter methods as well, // in particular for Java 8 default methods... Class clazz = beanClass; - while (clazz != null) { + while (clazz != null && clazz != Object.class) { Class[] ifcs = clazz.getInterfaces(); for (Class ifc : ifcs) { - BeanInfo ifcInfo = Introspector.getBeanInfo(ifc, Introspector.IGNORE_ALL_BEANINFO); - PropertyDescriptor[] ifcPds = ifcInfo.getPropertyDescriptors(); - for (PropertyDescriptor pd : ifcPds) { - if (!this.propertyDescriptorCache.containsKey(pd.getName())) { - pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd); - this.propertyDescriptorCache.put(pd.getName(), pd); + if (!ClassUtils.isJavaLanguageInterface(ifc)) { + BeanInfo ifcInfo = getBeanInfo(ifc, true); + PropertyDescriptor[] ifcPds = ifcInfo.getPropertyDescriptors(); + for (PropertyDescriptor pd : ifcPds) { + if (!this.propertyDescriptorCache.containsKey(pd.getName())) { + pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd); + this.propertyDescriptorCache.put(pd.getName(), pd); + } } } } @@ -329,6 +338,7 @@ public class CachedIntrospectionResults { } } + BeanInfo getBeanInfo() { return this.beanInfo; } diff --git a/spring-core/src/main/java/org/springframework/core/Conventions.java b/spring-core/src/main/java/org/springframework/core/Conventions.java index 3b0de6ea98..16efaa851d 100644 --- a/spring-core/src/main/java/org/springframework/core/Conventions.java +++ b/spring-core/src/main/java/org/springframework/core/Conventions.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,10 @@ package org.springframework.core; -import java.io.Externalizable; -import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; import java.util.Iterator; -import java.util.Set; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -47,19 +41,7 @@ public abstract class Conventions { */ private static final String PLURAL_SUFFIX = "List"; - /** - * Set of interfaces that are supposed to be ignored - * when searching for the 'primary' interface of a proxy. - */ - private static final Set> IGNORED_INTERFACES; - - static { - IGNORED_INTERFACES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( - Serializable.class, Externalizable.class, Cloneable.class, Comparable.class))); - } - - private static final ReactiveAdapterRegistry reactiveAdapterRegistry = - ReactiveAdapterRegistry.getSharedInstance(); + private static final ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance(); /** @@ -67,16 +49,13 @@ public abstract class Conventions { * based on its concrete type. The convention used is to return the * un-capitalized short name of the {@code Class}, according to JavaBeans * property naming rules. - * *

For example:
* {@code com.myapp.Product} becomes {@code "product"}
* {@code com.myapp.MyProduct} becomes {@code "myProduct"}
* {@code com.myapp.UKProduct} becomes {@code "UKProduct"}
- * *

For arrays the pluralized version of the array component type is used. * For {@code Collection}s an attempt is made to 'peek ahead' to determine * the component type and return its pluralized version. - * * @param value the value to generate a variable name for * @return the generated variable name */ @@ -110,12 +89,10 @@ public abstract class Conventions { /** * Determine the conventional variable name for the given parameter taking * the generic collection type, if any, into account. - * *

As of 5.0 this method supports reactive types:
* {@code Mono} becomes {@code "productMono"}
* {@code Flux} becomes {@code "myProductFlux"}
* {@code Observable} becomes {@code "myProductObservable"}
- * * @param parameter the method or constructor parameter * @return the generated variable name */ @@ -295,7 +272,7 @@ public abstract class Conventions { if (Proxy.isProxyClass(valueClass)) { Class[] ifcs = valueClass.getInterfaces(); for (Class ifc : ifcs) { - if (!IGNORED_INTERFACES.contains(ifc)) { + if (!ClassUtils.isJavaLanguageInterface(ifc)) { return ifc; } } diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java index b646f53ea9..14dbb802eb 100644 --- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,8 @@ package org.springframework.util; import java.beans.Introspector; +import java.io.Externalizable; +import java.io.Serializable; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Method; @@ -74,6 +76,13 @@ public abstract class ClassUtils { public static final String CLASS_FILE_SUFFIX = ".class"; + /** + * Common Java language interfaces which are supposed to be ignored + * when searching for 'primary' user-level interfaces. + */ + private static final Set> javaLanguageInterfaces = new HashSet<>( + Arrays.asList(Serializable.class, Externalizable.class, Cloneable.class, Comparable.class)); + /** * Map with primitive wrapper type as key and corresponding primitive * type as value, for example: Integer.class -> int.class. @@ -1218,6 +1227,19 @@ public abstract class ClassUtils { } } + /** + * Determine whether the given interface is a common Java language interface: + * {@link Serializable}, {@link Externalizable}, {@link Cloneable}, {@link Comparable} + * - all of which can be ignored when looking for 'primary' user-level interfaces. + * Common characteristics: no service-level operations, no bean property methods, + * no default methods. + * @param ifc the interface to check + * @since 5.0.3 + */ + public static boolean isJavaLanguageInterface(Class ifc) { + return javaLanguageInterfaces.contains(ifc); + } + /** * Check whether the given object is a CGLIB proxy. * @param object the object to check