Skip to content

Commit 33023b2

Browse files
committed
Provide optional SimpleBeanInfoFactory for better introspection performance
Closes gh-29330
1 parent c407dc3 commit 33023b2

File tree

3 files changed

+197
-4
lines changed

3 files changed

+197
-4
lines changed

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,6 @@ public final class CachedIntrospectionResults {
9797
*/
9898
public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";
9999

100-
private static final PropertyDescriptor[] EMPTY_PROPERTY_DESCRIPTOR_ARRAY = {};
101-
102100

103101
private static final boolean shouldIntrospectorIgnoreBeaninfoClasses =
104102
SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME);
@@ -422,7 +420,7 @@ PropertyDescriptor getPropertyDescriptor(String name) {
422420
}
423421

424422
PropertyDescriptor[] getPropertyDescriptors() {
425-
return this.propertyDescriptors.values().toArray(EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
423+
return this.propertyDescriptors.values().toArray(PropertyDescriptorUtils.EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
426424
}
427425

428426
private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor(Class<?> beanClass, PropertyDescriptor pd) {

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

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 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,9 +17,13 @@
1717
package org.springframework.beans;
1818

1919
import java.beans.IntrospectionException;
20+
import java.beans.Introspector;
2021
import java.beans.PropertyDescriptor;
2122
import java.lang.reflect.Method;
23+
import java.util.Collection;
2224
import java.util.Enumeration;
25+
import java.util.Map;
26+
import java.util.TreeMap;
2327

2428
import org.springframework.lang.Nullable;
2529
import org.springframework.util.ObjectUtils;
@@ -32,6 +36,91 @@
3236
*/
3337
abstract class PropertyDescriptorUtils {
3438

39+
public static final PropertyDescriptor[] EMPTY_PROPERTY_DESCRIPTOR_ARRAY = {};
40+
41+
42+
/**
43+
* Simple introspection algorithm for basic set/get/is accessor methods,
44+
* building corresponding JavaBeans property descriptors for them.
45+
* <p>This just supports the basic JavaBeans conventions, without indexed
46+
* properties or any customizers, and without other BeanInfo metadata.
47+
* For standard JavaBeans introspection, use the JavaBeans Introspector.
48+
* @param beanClass the target class to introspect
49+
* @return a collection of property descriptors
50+
* @throws IntrospectionException from introspecting the given bean class
51+
* @since 5.3.24
52+
* @see SimpleBeanInfoFactory
53+
* @see java.beans.Introspector#getBeanInfo(Class)
54+
*/
55+
public static Collection<PropertyDescriptor> determineBasicProperties(Class<?> beanClass) throws IntrospectionException {
56+
Map<String, PropertyDescriptor> pdMap = new TreeMap<>();
57+
58+
for (Method method : beanClass.getMethods()) {
59+
String methodName = method.getName();
60+
61+
boolean setter;
62+
int nameIndex;
63+
if (methodName.startsWith("set") && method.getParameterCount() == 1) {
64+
setter = true;
65+
nameIndex = 3;
66+
}
67+
else if (methodName.startsWith("get") && method.getParameterCount() == 0 && method.getReturnType() != Void.TYPE) {
68+
setter = false;
69+
nameIndex = 3;
70+
}
71+
else if (methodName.startsWith("is") && method.getParameterCount() == 0 && method.getReturnType() == boolean.class) {
72+
setter = false;
73+
nameIndex = 2;
74+
}
75+
else {
76+
continue;
77+
}
78+
79+
String propertyName = Introspector.decapitalize(methodName.substring(nameIndex));
80+
if (propertyName.isEmpty()) {
81+
continue;
82+
}
83+
84+
PropertyDescriptor pd = pdMap.get(propertyName);
85+
if (pd != null) {
86+
if (setter) {
87+
if (pd.getWriteMethod() == null ||
88+
pd.getWriteMethod().getParameterTypes()[0].isAssignableFrom(method.getParameterTypes()[0])) {
89+
try {
90+
pd.setWriteMethod(method);
91+
}
92+
catch (IntrospectionException ex) {
93+
// typically a type mismatch -> ignore
94+
}
95+
}
96+
}
97+
else {
98+
if (pd.getReadMethod() == null ||
99+
(pd.getReadMethod().getReturnType() == method.getReturnType() && method.getName().startsWith("is"))) {
100+
try {
101+
pd.setReadMethod(method);
102+
}
103+
catch (IntrospectionException ex) {
104+
// typically a type mismatch -> ignore
105+
}
106+
}
107+
}
108+
}
109+
else {
110+
pd = new BasicPropertyDescriptor(propertyName, beanClass);
111+
if (setter) {
112+
pd.setWriteMethod(method);
113+
}
114+
else {
115+
pd.setReadMethod(method);
116+
}
117+
pdMap.put(propertyName, pd);
118+
}
119+
}
120+
121+
return pdMap.values();
122+
}
123+
35124
/**
36125
* See {@link java.beans.FeatureDescriptor}.
37126
*/
@@ -173,4 +262,46 @@ public static boolean equals(PropertyDescriptor pd, PropertyDescriptor otherPd)
173262
pd.isBound() == otherPd.isBound() && pd.isConstrained() == otherPd.isConstrained());
174263
}
175264

265+
266+
/**
267+
* PropertyDescriptor for {@link #determineBasicProperties(Class)},
268+
* not performing any early type determination for
269+
* {@link #setReadMethod}/{@link #setWriteMethod}.
270+
* @since 5.3.24
271+
*/
272+
private static class BasicPropertyDescriptor extends PropertyDescriptor {
273+
274+
@Nullable
275+
private Method readMethod;
276+
277+
@Nullable
278+
private Method writeMethod;
279+
280+
public BasicPropertyDescriptor(String propertyName, Class<?> beanClass) throws IntrospectionException {
281+
super(propertyName, beanClass, null, null);
282+
}
283+
284+
@Override
285+
public void setReadMethod(@Nullable Method readMethod) {
286+
this.readMethod = readMethod;
287+
}
288+
289+
@Override
290+
@Nullable
291+
public Method getReadMethod() {
292+
return this.readMethod;
293+
}
294+
295+
@Override
296+
public void setWriteMethod(@Nullable Method writeMethod) {
297+
this.writeMethod = writeMethod;
298+
}
299+
300+
@Override
301+
@Nullable
302+
public Method getWriteMethod() {
303+
return this.writeMethod;
304+
}
305+
}
306+
176307
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.beans;
18+
19+
import java.beans.BeanInfo;
20+
import java.beans.IntrospectionException;
21+
import java.beans.PropertyDescriptor;
22+
import java.beans.SimpleBeanInfo;
23+
import java.util.Collection;
24+
25+
import org.springframework.core.Ordered;
26+
import org.springframework.lang.NonNull;
27+
28+
/**
29+
* {@link BeanInfoFactory} implementation that bypasses the standard {@link java.beans.Introspector}
30+
* for faster introspection, reduced to basic property determination (as commonly needed in Spring).
31+
*
32+
* <p>To be configured via a {@code META-INF/spring.factories} file with the following content,
33+
* overriding other custom {@code org.springframework.beans.BeanInfoFactory} declarations:
34+
* {@code org.springframework.beans.BeanInfoFactory=org.springframework.beans.SimpleBeanInfoFactory}
35+
*
36+
* <p>Ordered at {@code Ordered.LOWEST_PRECEDENCE - 1} to override {@link ExtendedBeanInfoFactory}
37+
* (registered by default in 5.3) if necessary while still allowing other user-defined
38+
* {@link BeanInfoFactory} types to take precedence.
39+
*
40+
* @author Juergen Hoeller
41+
* @since 5.3.24
42+
* @see ExtendedBeanInfoFactory
43+
* @see CachedIntrospectionResults
44+
*/
45+
public class SimpleBeanInfoFactory implements BeanInfoFactory, Ordered {
46+
47+
@Override
48+
@NonNull
49+
public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
50+
Collection<PropertyDescriptor> pds = PropertyDescriptorUtils.determineBasicProperties(beanClass);
51+
return new SimpleBeanInfo() {
52+
@Override
53+
public PropertyDescriptor[] getPropertyDescriptors() {
54+
return pds.toArray(PropertyDescriptorUtils.EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
55+
}
56+
};
57+
}
58+
59+
@Override
60+
public int getOrder() {
61+
return Ordered.LOWEST_PRECEDENCE - 1;
62+
}
63+
64+
}

0 commit comments

Comments
 (0)