From 33023b240fab6858958d6637febc8578348d2b99 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 17 Oct 2022 12:25:53 +0200 Subject: [PATCH] Provide optional SimpleBeanInfoFactory for better introspection performance Closes gh-29330 --- .../beans/CachedIntrospectionResults.java | 4 +- .../beans/PropertyDescriptorUtils.java | 133 +++++++++++++++++- .../beans/SimpleBeanInfoFactory.java | 64 +++++++++ 3 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 spring-beans/src/main/java/org/springframework/beans/SimpleBeanInfoFactory.java 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 bd234eb58f..73165cc0cb 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 @@ public final class CachedIntrospectionResults { } 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 aa9909822d..6b7c49a2bf 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 @@ import org.springframework.util.ObjectUtils; */ 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 @@ abstract class PropertyDescriptorUtils { 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 0000000000..2e4705d1bd --- /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; + } + +}