Browse Source

Provide optional SimpleBeanInfoFactory for better introspection performance

Closes gh-29330
pull/28678/head
Juergen Hoeller 2 years ago
parent
commit
33023b240f
  1. 4
      spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
  2. 133
      spring-beans/src/main/java/org/springframework/beans/PropertyDescriptorUtils.java
  3. 64
      spring-beans/src/main/java/org/springframework/beans/SimpleBeanInfoFactory.java

4
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"; public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";
private static final PropertyDescriptor[] EMPTY_PROPERTY_DESCRIPTOR_ARRAY = {};
private static final boolean shouldIntrospectorIgnoreBeaninfoClasses = private static final boolean shouldIntrospectorIgnoreBeaninfoClasses =
SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME); SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME);
@ -422,7 +420,7 @@ public final class CachedIntrospectionResults {
} }
PropertyDescriptor[] getPropertyDescriptors() { 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) { private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor(Class<?> beanClass, PropertyDescriptor pd) {

133
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,9 +17,13 @@
package org.springframework.beans; package org.springframework.beans;
import java.beans.IntrospectionException; import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor; import java.beans.PropertyDescriptor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Map;
import java.util.TreeMap;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
@ -32,6 +36,91 @@ import org.springframework.util.ObjectUtils;
*/ */
abstract class PropertyDescriptorUtils { 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.
* <p>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<PropertyDescriptor> determineBasicProperties(Class<?> beanClass) throws IntrospectionException {
Map<String, PropertyDescriptor> 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}. * See {@link java.beans.FeatureDescriptor}.
*/ */
@ -173,4 +262,46 @@ abstract class PropertyDescriptorUtils {
pd.isBound() == otherPd.isBound() && pd.isConstrained() == otherPd.isConstrained()); 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;
}
}
} }

64
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).
*
* <p>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}
*
* <p>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<PropertyDescriptor> 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;
}
}
Loading…
Cancel
Save