Perform basic property determination without java.beans.Introspector
Closes gh-29320
This commit is contained in:
parent
113db2fb2f
commit
bba313c2f5
|
@ -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;
|
|||
* <p>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.
|
||||
*
|
||||
* <p>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.
|
||||
* <p>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).
|
||||
* <p>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.
|
||||
* <p>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<BeanInfoFactory> 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<PropertyDescriptor> pds = PropertyDescriptorUtils.determineBasicProperties(beanClass);
|
||||
return new SimpleBeanInfo() {
|
||||
@Override
|
||||
public PropertyDescriptor[] getPropertyDescriptors() {
|
||||
return pds.toArray(EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>To be configured via a {@code META-INF/spring.factories} file with the following content:
|
||||
* {@code org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory}
|
||||
*
|
||||
* <p>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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
* <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 6.0
|
||||
* @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 = 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>To be configured via a {@code META-INF/spring.factories} file with the following content:
|
||||
* {@code org.springframework.beans.BeanInfoFactory=org.springframework.beans.StandardBeanInfoFactory}
|
||||
*
|
||||
* <p>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).
|
||||
* <p>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.
|
||||
* <p>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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String> 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<String, Method> writeMethods = getWriteMethods(beanInfo);
|
||||
if (infrastructureType != Object.class) {
|
||||
Map<String, Method> 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<String, Method> getWriteMethods(BeanInfo beanInfo) {
|
||||
private Map<String, Method> getWriteMethods(Class<?> clazz) {
|
||||
Map<String, Method> 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);
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<Type> 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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue