Skip to content

Commit bba313c

Browse files
committed
Perform basic property determination without java.beans.Introspector
Closes gh-29320
1 parent 113db2f commit bba313c

File tree

12 files changed

+276
-199
lines changed

12 files changed

+276
-199
lines changed

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

Lines changed: 19 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@
1818

1919
import java.beans.BeanInfo;
2020
import java.beans.IntrospectionException;
21-
import java.beans.Introspector;
2221
import java.beans.PropertyDescriptor;
22+
import java.beans.SimpleBeanInfo;
2323
import java.lang.reflect.Method;
2424
import java.lang.reflect.Modifier;
2525
import java.net.URL;
2626
import java.security.ProtectionDomain;
27+
import java.util.Collection;
2728
import java.util.Collections;
2829
import java.util.HashSet;
2930
import java.util.LinkedHashMap;
@@ -36,7 +37,6 @@
3637
import org.apache.commons.logging.Log;
3738
import org.apache.commons.logging.LogFactory;
3839

39-
import org.springframework.core.SpringProperties;
4040
import org.springframework.core.convert.TypeDescriptor;
4141
import org.springframework.core.io.support.SpringFactoriesLoader;
4242
import org.springframework.lang.Nullable;
@@ -60,14 +60,15 @@
6060
* <p>Note that for caching to work effectively, some preconditions need to be met:
6161
* Prefer an arrangement where the Spring jars live in the same ClassLoader as the
6262
* application classes, which allows for clean caching along with the application's
63-
* lifecycle in any case. For a web application, consider declaring a local
64-
* {@link org.springframework.web.util.IntrospectorCleanupListener} in {@code web.xml}
65-
* in case of a multi-ClassLoader layout, which will allow for effective caching as well.
63+
* lifecycle in any case.
6664
*
67-
* <p>In case of a non-clean ClassLoader arrangement without a cleanup listener having
68-
* been set up, this class will fall back to a weak-reference-based caching model that
69-
* recreates much-requested entries every time the garbage collector removed them. In
70-
* such a scenario, consider the {@link #IGNORE_BEANINFO_PROPERTY_NAME} system property.
65+
* <p>As of 6.0, Spring's default introspection discovers basic JavaBeans properties
66+
* through an efficient method reflection pass. For full JavaBeans introspection
67+
* including indexed properties and all JDK-supported customizers, configure a
68+
* {@code META-INF/spring.factories} file with the following content:
69+
* {@code org.springframework.beans.BeanInfoFactory=org.springframework.beans.StandardBeanInfoFactory}
70+
* For Spring 5.3 compatible extended introspection including non-void setter methods:
71+
* {@code org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory}
7172
*
7273
* @author Rod Johnson
7374
* @author Juergen Hoeller
@@ -78,32 +79,9 @@
7879
*/
7980
public final class CachedIntrospectionResults {
8081

81-
/**
82-
* System property that instructs Spring to use the {@link Introspector#IGNORE_ALL_BEANINFO}
83-
* mode when calling the JavaBeans {@link Introspector}: "spring.beaninfo.ignore", with a
84-
* value of "true" skipping the search for {@code BeanInfo} classes (typically for scenarios
85-
* where no such classes are being defined for beans in the application in the first place).
86-
* <p>The default is "false", considering all {@code BeanInfo} metadata classes, like for
87-
* standard {@link Introspector#getBeanInfo(Class)} calls. Consider switching this flag to
88-
* "true" if you experience repeated ClassLoader access for non-existing {@code BeanInfo}
89-
* classes, in case such access is expensive on startup or on lazy loading.
90-
* <p>Note that such an effect may also indicate a scenario where caching doesn't work
91-
* effectively: Prefer an arrangement where the Spring jars live in the same ClassLoader
92-
* as the application classes, which allows for clean caching along with the application's
93-
* lifecycle in any case. For a web application, consider declaring a local
94-
* {@link org.springframework.web.util.IntrospectorCleanupListener} in {@code web.xml}
95-
* in case of a multi-ClassLoader layout, which will allow for effective caching as well.
96-
* @see Introspector#getBeanInfo(Class, int)
97-
*/
98-
public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";
99-
10082
private static final PropertyDescriptor[] EMPTY_PROPERTY_DESCRIPTOR_ARRAY = {};
10183

10284

103-
private static final boolean shouldIntrospectorIgnoreBeaninfoClasses =
104-
SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME);
105-
106-
/** Stores the BeanInfoFactory instances. */
10785
private static final List<BeanInfoFactory> beanInfoFactories = SpringFactoriesLoader.loadFactories(
10886
BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader());
10987

@@ -241,7 +219,7 @@ private static boolean isUnderneathClassLoader(@Nullable ClassLoader candidate,
241219
* Retrieve a {@link BeanInfo} descriptor for the given target class.
242220
* @param beanClass the target class to introspect
243221
* @return the resulting {@code BeanInfo} descriptor (never {@code null})
244-
* @throws IntrospectionException from the underlying {@link Introspector}
222+
* @throws IntrospectionException from introspecting the given bean class
245223
*/
246224
private static BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
247225
for (BeanInfoFactory beanInfoFactory : beanInfoFactories) {
@@ -250,9 +228,14 @@ private static BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionExce
250228
return beanInfo;
251229
}
252230
}
253-
return (shouldIntrospectorIgnoreBeaninfoClasses ?
254-
Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) :
255-
Introspector.getBeanInfo(beanClass));
231+
232+
Collection<PropertyDescriptor> pds = PropertyDescriptorUtils.determineBasicProperties(beanClass);
233+
return new SimpleBeanInfo() {
234+
@Override
235+
public PropertyDescriptor[] getPropertyDescriptors() {
236+
return pds.toArray(EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
237+
}
238+
};
256239
}
257240

258241

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

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 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.
@@ -18,34 +18,35 @@
1818

1919
import java.beans.BeanInfo;
2020
import java.beans.IntrospectionException;
21-
import java.beans.Introspector;
2221
import java.lang.reflect.Method;
2322

2423
import org.springframework.core.Ordered;
25-
import org.springframework.lang.Nullable;
24+
import org.springframework.lang.NonNull;
2625

2726
/**
28-
* {@link BeanInfoFactory} implementation that evaluates whether bean classes have
29-
* "non-standard" JavaBeans setter methods and are thus candidates for introspection
30-
* by Spring's (package-visible) {@code ExtendedBeanInfo} implementation.
27+
* Extension of {@link StandardBeanInfoFactory} that supports "non-standard"
28+
* JavaBeans setter methods through introspection by Spring's
29+
* (package-visible) {@code ExtendedBeanInfo} implementation.
30+
*
31+
* <p>To be configured via a {@code META-INF/spring.factories} file with the following content:
32+
* {@code org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory}
3133
*
3234
* <p>Ordered at {@link Ordered#LOWEST_PRECEDENCE} to allow other user-defined
3335
* {@link BeanInfoFactory} types to take precedence.
3436
*
3537
* @author Chris Beams
38+
* @author Juergen Hoeller
3639
* @since 3.2
37-
* @see BeanInfoFactory
40+
* @see StandardBeanInfoFactory
3841
* @see CachedIntrospectionResults
3942
*/
40-
public class ExtendedBeanInfoFactory implements BeanInfoFactory, Ordered {
43+
public class ExtendedBeanInfoFactory extends StandardBeanInfoFactory {
4144

42-
/**
43-
* Return an {@link ExtendedBeanInfo} for the given bean class, if applicable.
44-
*/
4545
@Override
46-
@Nullable
46+
@NonNull
4747
public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
48-
return (supports(beanClass) ? new ExtendedBeanInfo(Introspector.getBeanInfo(beanClass)) : null);
48+
BeanInfo beanInfo = super.getBeanInfo(beanClass);
49+
return (supports(beanClass) ? new ExtendedBeanInfo(beanInfo) : beanInfo);
4950
}
5051

5152
/**
@@ -61,9 +62,4 @@ private boolean supports(Class<?> beanClass) {
6162
return false;
6263
}
6364

64-
@Override
65-
public int getOrder() {
66-
return Ordered.LOWEST_PRECEDENCE;
67-
}
68-
6965
}

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

Lines changed: 122 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.
@@ -19,10 +19,14 @@
1919
import java.beans.IntrospectionException;
2020
import java.beans.PropertyDescriptor;
2121
import java.lang.reflect.Method;
22+
import java.util.Collection;
2223
import java.util.Enumeration;
24+
import java.util.Map;
25+
import java.util.TreeMap;
2326

2427
import org.springframework.lang.Nullable;
2528
import org.springframework.util.ObjectUtils;
29+
import org.springframework.util.StringUtils;
2630

2731
/**
2832
* Common delegate methods for Spring's internal {@link PropertyDescriptor} implementations.
@@ -32,6 +36,82 @@
3236
*/
3337
abstract class PropertyDescriptorUtils {
3438

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

256+
257+
/**
258+
* PropertyDescriptor for {@link #determineBasicProperties(Class)},
259+
* not performing any early type determination for
260+
* {@link #setReadMethod}/{@link #setWriteMethod}.
261+
*/
262+
private static class BasicPropertyDescriptor extends PropertyDescriptor {
263+
264+
@Nullable
265+
private Method readMethod;
266+
267+
@Nullable
268+
private Method writeMethod;
269+
270+
public BasicPropertyDescriptor(String propertyName, Class<?> beanClass) throws IntrospectionException {
271+
super(propertyName, beanClass, null, null);
272+
}
273+
274+
@Override
275+
public void setReadMethod(@Nullable Method readMethod) {
276+
this.readMethod = readMethod;
277+
}
278+
279+
@Override
280+
@Nullable
281+
public Method getReadMethod() {
282+
return this.readMethod;
283+
}
284+
285+
@Override
286+
public void setWriteMethod(@Nullable Method writeMethod) {
287+
this.writeMethod = writeMethod;
288+
}
289+
290+
@Override
291+
@Nullable
292+
public Method getWriteMethod() {
293+
return this.writeMethod;
294+
}
295+
}
296+
176297
}

0 commit comments

Comments
 (0)