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 bd234eb58f59..73165cc0cb89 100644
--- a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
+++ b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
@@ -97,8 +97,6 @@ public final class CachedIntrospectionResults {
*/
public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";
- private static final PropertyDescriptor[] EMPTY_PROPERTY_DESCRIPTOR_ARRAY = {};
-
private static final boolean shouldIntrospectorIgnoreBeaninfoClasses =
SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME);
@@ -422,7 +420,7 @@ PropertyDescriptor getPropertyDescriptor(String name) {
}
PropertyDescriptor[] getPropertyDescriptors() {
- return this.propertyDescriptors.values().toArray(EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
+ return this.propertyDescriptors.values().toArray(PropertyDescriptorUtils.EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
}
private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor(Class> beanClass, PropertyDescriptor pd) {
diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyDescriptorUtils.java b/spring-beans/src/main/java/org/springframework/beans/PropertyDescriptorUtils.java
index aa9909822d18..6b7c49a2bf94 100644
--- a/spring-beans/src/main/java/org/springframework/beans/PropertyDescriptorUtils.java
+++ b/spring-beans/src/main/java/org/springframework/beans/PropertyDescriptorUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2022 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,9 +17,13 @@
package org.springframework.beans;
import java.beans.IntrospectionException;
+import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
+import java.util.Collection;
import java.util.Enumeration;
+import java.util.Map;
+import java.util.TreeMap;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
@@ -32,6 +36,91 @@
*/
abstract class PropertyDescriptorUtils {
+ public static final PropertyDescriptor[] EMPTY_PROPERTY_DESCRIPTOR_ARRAY = {};
+
+
+ /**
+ * Simple introspection algorithm for basic set/get/is accessor methods,
+ * building corresponding JavaBeans property descriptors for them.
+ *
This just supports the basic JavaBeans conventions, without indexed
+ * properties or any customizers, and without other BeanInfo metadata.
+ * For standard JavaBeans introspection, use the JavaBeans Introspector.
+ * @param beanClass the target class to introspect
+ * @return a collection of property descriptors
+ * @throws IntrospectionException from introspecting the given bean class
+ * @since 5.3.24
+ * @see SimpleBeanInfoFactory
+ * @see java.beans.Introspector#getBeanInfo(Class)
+ */
+ public static Collection determineBasicProperties(Class> beanClass) throws IntrospectionException {
+ Map pdMap = new TreeMap<>();
+
+ for (Method method : beanClass.getMethods()) {
+ String methodName = method.getName();
+
+ boolean setter;
+ int nameIndex;
+ if (methodName.startsWith("set") && method.getParameterCount() == 1) {
+ setter = true;
+ nameIndex = 3;
+ }
+ else if (methodName.startsWith("get") && method.getParameterCount() == 0 && method.getReturnType() != Void.TYPE) {
+ setter = false;
+ nameIndex = 3;
+ }
+ else if (methodName.startsWith("is") && method.getParameterCount() == 0 && method.getReturnType() == boolean.class) {
+ setter = false;
+ nameIndex = 2;
+ }
+ else {
+ continue;
+ }
+
+ String propertyName = Introspector.decapitalize(methodName.substring(nameIndex));
+ if (propertyName.isEmpty()) {
+ continue;
+ }
+
+ PropertyDescriptor pd = pdMap.get(propertyName);
+ if (pd != null) {
+ if (setter) {
+ if (pd.getWriteMethod() == null ||
+ pd.getWriteMethod().getParameterTypes()[0].isAssignableFrom(method.getParameterTypes()[0])) {
+ try {
+ pd.setWriteMethod(method);
+ }
+ catch (IntrospectionException ex) {
+ // typically a type mismatch -> ignore
+ }
+ }
+ }
+ else {
+ if (pd.getReadMethod() == null ||
+ (pd.getReadMethod().getReturnType() == method.getReturnType() && method.getName().startsWith("is"))) {
+ try {
+ pd.setReadMethod(method);
+ }
+ catch (IntrospectionException ex) {
+ // typically a type mismatch -> ignore
+ }
+ }
+ }
+ }
+ else {
+ pd = new BasicPropertyDescriptor(propertyName, beanClass);
+ if (setter) {
+ pd.setWriteMethod(method);
+ }
+ else {
+ pd.setReadMethod(method);
+ }
+ pdMap.put(propertyName, pd);
+ }
+ }
+
+ return pdMap.values();
+ }
+
/**
* See {@link java.beans.FeatureDescriptor}.
*/
@@ -173,4 +262,46 @@ public static boolean equals(PropertyDescriptor pd, PropertyDescriptor otherPd)
pd.isBound() == otherPd.isBound() && pd.isConstrained() == otherPd.isConstrained());
}
+
+ /**
+ * PropertyDescriptor for {@link #determineBasicProperties(Class)},
+ * not performing any early type determination for
+ * {@link #setReadMethod}/{@link #setWriteMethod}.
+ * @since 5.3.24
+ */
+ private static class BasicPropertyDescriptor extends PropertyDescriptor {
+
+ @Nullable
+ private Method readMethod;
+
+ @Nullable
+ private Method writeMethod;
+
+ public BasicPropertyDescriptor(String propertyName, Class> beanClass) throws IntrospectionException {
+ super(propertyName, beanClass, null, null);
+ }
+
+ @Override
+ public void setReadMethod(@Nullable Method readMethod) {
+ this.readMethod = readMethod;
+ }
+
+ @Override
+ @Nullable
+ public Method getReadMethod() {
+ return this.readMethod;
+ }
+
+ @Override
+ public void setWriteMethod(@Nullable Method writeMethod) {
+ this.writeMethod = writeMethod;
+ }
+
+ @Override
+ @Nullable
+ public Method getWriteMethod() {
+ return this.writeMethod;
+ }
+ }
+
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/SimpleBeanInfoFactory.java b/spring-beans/src/main/java/org/springframework/beans/SimpleBeanInfoFactory.java
new file mode 100644
index 000000000000..2e4705d1bdd9
--- /dev/null
+++ b/spring-beans/src/main/java/org/springframework/beans/SimpleBeanInfoFactory.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2022 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.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.beans;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
+import java.beans.SimpleBeanInfo;
+import java.util.Collection;
+
+import org.springframework.core.Ordered;
+import org.springframework.lang.NonNull;
+
+/**
+ * {@link BeanInfoFactory} implementation that bypasses the standard {@link java.beans.Introspector}
+ * for faster introspection, reduced to basic property determination (as commonly needed in Spring).
+ *
+ * To be configured via a {@code META-INF/spring.factories} file with the following content,
+ * overriding other custom {@code org.springframework.beans.BeanInfoFactory} declarations:
+ * {@code org.springframework.beans.BeanInfoFactory=org.springframework.beans.SimpleBeanInfoFactory}
+ *
+ *
Ordered at {@code Ordered.LOWEST_PRECEDENCE - 1} to override {@link ExtendedBeanInfoFactory}
+ * (registered by default in 5.3) if necessary while still allowing other user-defined
+ * {@link BeanInfoFactory} types to take precedence.
+ *
+ * @author Juergen Hoeller
+ * @since 5.3.24
+ * @see ExtendedBeanInfoFactory
+ * @see CachedIntrospectionResults
+ */
+public class SimpleBeanInfoFactory implements BeanInfoFactory, Ordered {
+
+ @Override
+ @NonNull
+ public BeanInfo getBeanInfo(Class> beanClass) throws IntrospectionException {
+ Collection pds = PropertyDescriptorUtils.determineBasicProperties(beanClass);
+ return new SimpleBeanInfo() {
+ @Override
+ public PropertyDescriptor[] getPropertyDescriptors() {
+ return pds.toArray(PropertyDescriptorUtils.EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
+ }
+ };
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.LOWEST_PRECEDENCE - 1;
+ }
+
+}