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..02f4ec7200 100644
--- a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
+++ b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
@@ -18,12 +18,13 @@ package org.springframework.beans;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
-import java.beans.Introspector;
import java.beans.PropertyDescriptor;
+import java.beans.SimpleBeanInfo;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.security.ProtectionDomain;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -36,7 +37,6 @@ import java.util.concurrent.ConcurrentMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.core.SpringProperties;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.lang.Nullable;
@@ -60,14 +60,15 @@ import org.springframework.util.StringUtils;
*
Note that for caching to work effectively, some preconditions need to be met:
* Prefer an arrangement where the Spring jars live in the same ClassLoader as the
* application classes, which allows for clean caching along with the application's
- * lifecycle in any case. For a web application, consider declaring a local
- * {@link org.springframework.web.util.IntrospectorCleanupListener} in {@code web.xml}
- * in case of a multi-ClassLoader layout, which will allow for effective caching as well.
+ * lifecycle in any case.
*
- *
In case of a non-clean ClassLoader arrangement without a cleanup listener having
- * been set up, this class will fall back to a weak-reference-based caching model that
- * recreates much-requested entries every time the garbage collector removed them. In
- * such a scenario, consider the {@link #IGNORE_BEANINFO_PROPERTY_NAME} system property.
+ *
As of 6.0, Spring's default introspection discovers basic JavaBeans properties
+ * through an efficient method reflection pass. For full JavaBeans introspection
+ * including indexed properties and all JDK-supported customizers, configure a
+ * {@code META-INF/spring.factories} file with the following content:
+ * {@code org.springframework.beans.BeanInfoFactory=org.springframework.beans.StandardBeanInfoFactory}
+ * For Spring 5.3 compatible extended introspection including non-void setter methods:
+ * {@code org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory}
*
* @author Rod Johnson
* @author Juergen Hoeller
@@ -78,32 +79,9 @@ import org.springframework.util.StringUtils;
*/
public final class CachedIntrospectionResults {
- /**
- * System property that instructs Spring to use the {@link Introspector#IGNORE_ALL_BEANINFO}
- * mode when calling the JavaBeans {@link Introspector}: "spring.beaninfo.ignore", with a
- * value of "true" skipping the search for {@code BeanInfo} classes (typically for scenarios
- * where no such classes are being defined for beans in the application in the first place).
- *
The default is "false", considering all {@code BeanInfo} metadata classes, like for
- * standard {@link Introspector#getBeanInfo(Class)} calls. Consider switching this flag to
- * "true" if you experience repeated ClassLoader access for non-existing {@code BeanInfo}
- * classes, in case such access is expensive on startup or on lazy loading.
- *
Note that such an effect may also indicate a scenario where caching doesn't work
- * effectively: Prefer an arrangement where the Spring jars live in the same ClassLoader
- * as the application classes, which allows for clean caching along with the application's
- * lifecycle in any case. For a web application, consider declaring a local
- * {@link org.springframework.web.util.IntrospectorCleanupListener} in {@code web.xml}
- * in case of a multi-ClassLoader layout, which will allow for effective caching as well.
- * @see Introspector#getBeanInfo(Class, int)
- */
- 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);
-
- /** Stores the BeanInfoFactory instances. */
private static final List beanInfoFactories = SpringFactoriesLoader.loadFactories(
BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader());
@@ -241,7 +219,7 @@ public final class CachedIntrospectionResults {
* Retrieve a {@link BeanInfo} descriptor for the given target class.
* @param beanClass the target class to introspect
* @return the resulting {@code BeanInfo} descriptor (never {@code null})
- * @throws IntrospectionException from the underlying {@link Introspector}
+ * @throws IntrospectionException from introspecting the given bean class
*/
private static BeanInfo getBeanInfo(Class> beanClass) throws IntrospectionException {
for (BeanInfoFactory beanInfoFactory : beanInfoFactories) {
@@ -250,9 +228,14 @@ public final class CachedIntrospectionResults {
return beanInfo;
}
}
- return (shouldIntrospectorIgnoreBeaninfoClasses ?
- Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) :
- Introspector.getBeanInfo(beanClass));
+
+ Collection pds = PropertyDescriptorUtils.determineBasicProperties(beanClass);
+ return new SimpleBeanInfo() {
+ @Override
+ public PropertyDescriptor[] getPropertyDescriptors() {
+ return pds.toArray(EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
+ }
+ };
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfoFactory.java b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfoFactory.java
index d7d2b2ecc9..5f41742632 100644
--- a/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfoFactory.java
+++ b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfoFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 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.
@@ -18,34 +18,35 @@ package org.springframework.beans;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
-import java.beans.Introspector;
import java.lang.reflect.Method;
import org.springframework.core.Ordered;
-import org.springframework.lang.Nullable;
+import org.springframework.lang.NonNull;
/**
- * {@link BeanInfoFactory} implementation that evaluates whether bean classes have
- * "non-standard" JavaBeans setter methods and are thus candidates for introspection
- * by Spring's (package-visible) {@code ExtendedBeanInfo} implementation.
+ * Extension of {@link StandardBeanInfoFactory} that supports "non-standard"
+ * JavaBeans setter methods through introspection by Spring's
+ * (package-visible) {@code ExtendedBeanInfo} implementation.
+ *
+ * To be configured via a {@code META-INF/spring.factories} file with the following content:
+ * {@code org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory}
*
*
Ordered at {@link Ordered#LOWEST_PRECEDENCE} to allow other user-defined
* {@link BeanInfoFactory} types to take precedence.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.2
- * @see BeanInfoFactory
+ * @see StandardBeanInfoFactory
* @see CachedIntrospectionResults
*/
-public class ExtendedBeanInfoFactory implements BeanInfoFactory, Ordered {
+public class ExtendedBeanInfoFactory extends StandardBeanInfoFactory {
- /**
- * Return an {@link ExtendedBeanInfo} for the given bean class, if applicable.
- */
@Override
- @Nullable
+ @NonNull
public BeanInfo getBeanInfo(Class> beanClass) throws IntrospectionException {
- return (supports(beanClass) ? new ExtendedBeanInfo(Introspector.getBeanInfo(beanClass)) : null);
+ BeanInfo beanInfo = super.getBeanInfo(beanClass);
+ return (supports(beanClass) ? new ExtendedBeanInfo(beanInfo) : beanInfo);
}
/**
@@ -61,9 +62,4 @@ public class ExtendedBeanInfoFactory implements BeanInfoFactory, Ordered {
return false;
}
- @Override
- public int getOrder() {
- return Ordered.LOWEST_PRECEDENCE;
- }
-
}
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..a5d760380b 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.
@@ -19,10 +19,14 @@ package org.springframework.beans;
import java.beans.IntrospectionException;
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;
+import org.springframework.util.StringUtils;
/**
* Common delegate methods for Spring's internal {@link PropertyDescriptor} implementations.
@@ -32,6 +36,82 @@ import org.springframework.util.ObjectUtils;
*/
abstract class PropertyDescriptorUtils {
+ /**
+ * 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 6.0
+ * @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 = StringUtils.uncapitalizeAsProperty(methodName.substring(nameIndex));
+ 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 +253,45 @@ 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}.
+ */
+ 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/StandardBeanInfoFactory.java b/spring-beans/src/main/java/org/springframework/beans/StandardBeanInfoFactory.java
new file mode 100644
index 0000000000..7abdade7c4
--- /dev/null
+++ b/spring-beans/src/main/java/org/springframework/beans/StandardBeanInfoFactory.java
@@ -0,0 +1,81 @@
+/*
+ * 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.Introspector;
+
+import org.springframework.core.Ordered;
+import org.springframework.core.SpringProperties;
+import org.springframework.lang.NonNull;
+
+/**
+ * {@link BeanInfoFactory} implementation that performs standard
+ * {@link java.beans.Introspector} inspection.
+ *
+ * To be configured via a {@code META-INF/spring.factories} file with the following content:
+ * {@code org.springframework.beans.BeanInfoFactory=org.springframework.beans.StandardBeanInfoFactory}
+ *
+ *
Ordered at {@link Ordered#LOWEST_PRECEDENCE} to allow other user-defined
+ * {@link BeanInfoFactory} types to take precedence.
+ *
+ * @author Juergen Hoeller
+ * @since 6.0
+ * @see ExtendedBeanInfoFactory
+ * @see CachedIntrospectionResults
+ * @see Introspector#getBeanInfo(Class)
+ */
+public class StandardBeanInfoFactory implements BeanInfoFactory, Ordered {
+
+ /**
+ * System property that instructs Spring to use the {@link Introspector#IGNORE_ALL_BEANINFO}
+ * mode when calling the JavaBeans {@link Introspector}: "spring.beaninfo.ignore", with a
+ * value of "true" skipping the search for {@code BeanInfo} classes (typically for scenarios
+ * where no such classes are being defined for beans in the application in the first place).
+ *
The default is "false", considering all {@code BeanInfo} metadata classes, like for
+ * standard {@link Introspector#getBeanInfo(Class)} calls. Consider switching this flag to
+ * "true" if you experience repeated ClassLoader access for non-existing {@code BeanInfo}
+ * classes, in case such access is expensive on startup or on lazy loading.
+ *
Note that such an effect may also indicate a scenario where caching doesn't work
+ * effectively: Prefer an arrangement where the Spring jars live in the same ClassLoader
+ * as the application classes, which allows for clean caching along with the application's
+ * lifecycle in any case. For a web application, consider declaring a local
+ * {@link org.springframework.web.util.IntrospectorCleanupListener} in {@code web.xml}
+ * in case of a multi-ClassLoader layout, which will allow for effective caching as well.
+ * @see Introspector#getBeanInfo(Class, int)
+ */
+ public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";
+
+ private static final boolean shouldIntrospectorIgnoreBeaninfoClasses =
+ SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME);
+
+
+ @Override
+ @NonNull
+ public BeanInfo getBeanInfo(Class> beanClass) throws IntrospectionException {
+ return (shouldIntrospectorIgnoreBeaninfoClasses ?
+ Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) :
+ Introspector.getBeanInfo(beanClass));
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.LOWEST_PRECEDENCE;
+ }
+
+}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java
index cef034888c..ec13bb3a53 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java
@@ -16,9 +16,6 @@
package org.springframework.beans.factory.aot;
-import java.beans.BeanInfo;
-import java.beans.IntrospectionException;
-import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Arrays;
@@ -34,8 +31,7 @@ import java.util.function.Predicate;
import org.springframework.aot.generate.GeneratedMethods;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
-import org.springframework.beans.BeanInfoFactory;
-import org.springframework.beans.ExtendedBeanInfoFactory;
+import org.springframework.beans.BeanUtils;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.FactoryBean;
@@ -81,8 +77,6 @@ class BeanDefinitionPropertiesCodeGenerator {
private static final String BEAN_DEFINITION_VARIABLE = BeanRegistrationCodeFragments.BEAN_DEFINITION_VARIABLE;
- private static final BeanInfoFactory beanInfoFactory = new ExtendedBeanInfoFactory();
-
private final RuntimeHints hints;
private final Predicate attributeFilter;
@@ -185,9 +179,8 @@ class BeanDefinitionPropertiesCodeGenerator {
BEAN_DEFINITION_VARIABLE, propertyValue.getName(), valueCode);
}
Class> infrastructureType = getInfrastructureType(beanDefinition);
- BeanInfo beanInfo = (infrastructureType != Object.class) ? getBeanInfo(infrastructureType) : null;
- if (beanInfo != null) {
- Map writeMethods = getWriteMethods(beanInfo);
+ if (infrastructureType != Object.class) {
+ Map writeMethods = getWriteMethods(infrastructureType);
for (PropertyValue propertyValue : propertyValues) {
Method writeMethod = writeMethods.get(propertyValue.getName());
if (writeMethod != null) {
@@ -208,25 +201,10 @@ class BeanDefinitionPropertiesCodeGenerator {
return ClassUtils.getUserClass(beanDefinition.getResolvableType().toClass());
}
- @Nullable
- private BeanInfo getBeanInfo(Class> beanType) {
- try {
- BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanType);
- if (beanInfo != null) {
- return beanInfo;
- }
- return Introspector.getBeanInfo(beanType, Introspector.IGNORE_ALL_BEANINFO);
- }
- catch (IntrospectionException ex) {
- return null;
- }
- }
-
- private Map getWriteMethods(BeanInfo beanInfo) {
+ private Map getWriteMethods(Class> clazz) {
Map writeMethods = new HashMap<>();
- for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
- writeMethods.put(propertyDescriptor.getName(),
- propertyDescriptor.getWriteMethod());
+ for (PropertyDescriptor propertyDescriptor : BeanUtils.getPropertyDescriptors(clazz)) {
+ writeMethods.put(propertyDescriptor.getName(), propertyDescriptor.getWriteMethod());
}
return Collections.unmodifiableMap(writeMethods);
}
diff --git a/spring-beans/src/main/resources/META-INF/spring.factories b/spring-beans/src/main/resources/META-INF/spring.factories
deleted file mode 100644
index b623047ec2..0000000000
--- a/spring-beans/src/main/resources/META-INF/spring.factories
+++ /dev/null
@@ -1 +0,0 @@
-org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
\ No newline at end of file
diff --git a/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoFactoryTests.java
deleted file mode 100644
index c4449bcb5b..0000000000
--- a/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoFactoryTests.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2002-2019 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.IntrospectionException;
-
-import org.junit.jupiter.api.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-
-/**
- * Unit tests for {@link ExtendedBeanInfoTests}.
- *
- * @author Chris Beams
- */
-public class ExtendedBeanInfoFactoryTests {
-
- private ExtendedBeanInfoFactory factory = new ExtendedBeanInfoFactory();
-
- @Test
- public void shouldNotSupportClassHavingOnlyVoidReturningSetter() throws IntrospectionException {
- @SuppressWarnings("unused")
- class C {
- public void setFoo(String s) { }
- }
- assertThat(factory.getBeanInfo(C.class)).isNull();
- }
-
- @Test
- public void shouldSupportClassHavingNonVoidReturningSetter() throws IntrospectionException {
- @SuppressWarnings("unused")
- class C {
- public C setFoo(String s) { return this; }
- }
- assertThat(factory.getBeanInfo(C.class)).isNotNull();
- }
-
- @Test
- public void shouldSupportClassHavingNonVoidReturningIndexedSetter() throws IntrospectionException {
- @SuppressWarnings("unused")
- class C {
- public C setFoo(int i, String s) { return this; }
- }
- assertThat(factory.getBeanInfo(C.class)).isNotNull();
- }
-
- @Test
- public void shouldNotSupportClassHavingNonPublicNonVoidReturningIndexedSetter() throws IntrospectionException {
- @SuppressWarnings("unused")
- class C {
- void setBar(String s) { }
- }
- assertThat(factory.getBeanInfo(C.class)).isNull();
- }
-
- @Test
- public void shouldNotSupportClassHavingNonVoidReturningParameterlessSetter() throws IntrospectionException {
- @SuppressWarnings("unused")
- class C {
- C setBar() { return this; }
- }
- assertThat(factory.getBeanInfo(C.class)).isNull();
- }
-
- @Test
- public void shouldNotSupportClassHavingNonVoidReturningMethodNamedSet() throws IntrospectionException {
- @SuppressWarnings("unused")
- class C {
- C set(String s) { return this; }
- }
- assertThat(factory.getBeanInfo(C.class)).isNull();
- }
-
-}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java
index 4f2dc199fd..be73ec25d0 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 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.
@@ -16,7 +16,6 @@
package org.springframework.context.annotation;
-import java.beans.Introspector;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
@@ -167,7 +166,7 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator {
String beanClassName = definition.getBeanClassName();
Assert.state(beanClassName != null, "No bean class name set");
String shortClassName = ClassUtils.getShortName(beanClassName);
- return Introspector.decapitalize(shortClassName);
+ return StringUtils.uncapitalizeAsProperty(shortClassName);
}
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java
index 18a6245b64..b7b31fba94 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 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.
@@ -16,7 +16,6 @@
package org.springframework.context.annotation;
-import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.annotation.Annotation;
@@ -592,7 +591,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
if (this.isDefaultName) {
resourceName = this.member.getName();
if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
- resourceName = Introspector.decapitalize(resourceName.substring(3));
+ resourceName = StringUtils.uncapitalizeAsProperty(resourceName.substring(3));
}
}
else if (embeddedValueResolver != null) {
@@ -638,7 +637,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
if (this.isDefaultName) {
resourceName = this.member.getName();
if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
- resourceName = Introspector.decapitalize(resourceName.substring(3));
+ resourceName = StringUtils.uncapitalizeAsProperty(resourceName.substring(3));
}
}
Class> resourceType = resource.beanInterface();
diff --git a/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java b/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java
index 134711407d..d56189935a 100644
--- a/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java
+++ b/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java
@@ -16,10 +16,6 @@
package org.springframework.aot.hint;
-import java.beans.BeanInfo;
-import java.beans.IntrospectionException;
-import java.beans.Introspector;
-import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.RecordComponent;
import java.lang.reflect.Type;
@@ -92,17 +88,14 @@ public class BindingReflectionHintsRegistrar {
typeHint.withMembers(
MemberCategory.DECLARED_FIELDS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
- try {
- BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
- PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
- for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
- registerPropertyHints(hints, seen, propertyDescriptor.getWriteMethod(), 0);
- registerPropertyHints(hints, seen, propertyDescriptor.getReadMethod(), -1);
+ for (Method method : clazz.getMethods()) {
+ String methodName = method.getName();
+ if (methodName.startsWith("set") && method.getParameterCount() == 1) {
+ registerPropertyHints(hints, seen, method, 0);
}
- }
- catch (IntrospectionException ex) {
- if (logger.isDebugEnabled()) {
- logger.debug("Ignoring referenced type [" + clazz.getName() + "]: " + ex.getMessage());
+ else if ((methodName.startsWith("get") && method.getParameterCount() == 0 && method.getReturnType() != Void.TYPE) ||
+ (methodName.startsWith("is") && method.getParameterCount() == 0 && method.getReturnType() == boolean.class)) {
+ registerPropertyHints(hints, seen, method, -1);
}
}
}
@@ -125,8 +118,8 @@ public class BindingReflectionHintsRegistrar {
}
private void registerPropertyHints(ReflectionHints hints, Set seen, @Nullable Method method, int parameterIndex) {
- if (method != null && method.getDeclaringClass() != Object.class
- && method.getDeclaringClass() != Enum.class) {
+ if (method != null && method.getDeclaringClass() != Object.class &&
+ method.getDeclaringClass() != Enum.class) {
hints.registerMethod(method, ExecutableMode.INVOKE);
MethodParameter methodParameter = MethodParameter.forExecutable(method, parameterIndex);
Type methodParameterType = methodParameter.getGenericParameterType();
diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java
index 5c4fb6ed22..ea4618d03d 100644
--- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java
@@ -16,7 +16,6 @@
package org.springframework.util;
-import java.beans.Introspector;
import java.io.Closeable;
import java.io.Externalizable;
import java.io.Serializable;
@@ -997,13 +996,13 @@ public abstract class ClassUtils {
* property format. Strips the outer class name in case of a nested class.
* @param clazz the class
* @return the short name rendered in a standard JavaBeans property format
- * @see java.beans.Introspector#decapitalize(String)
+ * @see StringUtils#uncapitalizeAsProperty(String)
*/
public static String getShortNameAsProperty(Class> clazz) {
String shortName = getShortName(clazz);
int dotIndex = shortName.lastIndexOf(PACKAGE_SEPARATOR);
shortName = (dotIndex != -1 ? shortName.substring(dotIndex + 1) : shortName);
- return Introspector.decapitalize(shortName);
+ return StringUtils.uncapitalizeAsProperty(shortName);
}
/**
diff --git a/spring-core/src/main/java/org/springframework/util/StringUtils.java b/spring-core/src/main/java/org/springframework/util/StringUtils.java
index 711fc170b7..3c2716be5a 100644
--- a/spring-core/src/main/java/org/springframework/util/StringUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/StringUtils.java
@@ -554,6 +554,24 @@ public abstract class StringUtils {
return changeFirstCharacterCase(str, false);
}
+ /**
+ * Uncapitalize a {@code String} in JavaBeans property format,
+ * changing the first letter to lower case as per
+ * {@link Character#toLowerCase(char)}, unless the initial two
+ * letters are upper case in direct succession.
+ * @param str the {@code String} to uncapitalize
+ * @return the uncapitalized {@code String}
+ * @since 6.0
+ * @see java.beans.Introspector#decapitalize(String)
+ */
+ public static String uncapitalizeAsProperty(String str) {
+ if (!hasLength(str) || (str.length() > 1 && Character.isUpperCase(str.charAt(0)) &&
+ Character.isUpperCase(str.charAt(1)))) {
+ return str;
+ }
+ return changeFirstCharacterCase(str, false);
+ }
+
private static String changeFirstCharacterCase(String str, boolean capitalize) {
if (!hasLength(str)) {
return str;