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.BeanInfo;
|
||||||
import java.beans.IntrospectionException;
|
import java.beans.IntrospectionException;
|
||||||
import java.beans.Introspector;
|
|
||||||
import java.beans.PropertyDescriptor;
|
import java.beans.PropertyDescriptor;
|
||||||
|
import java.beans.SimpleBeanInfo;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.security.ProtectionDomain;
|
import java.security.ProtectionDomain;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
|
@ -36,7 +37,6 @@ import java.util.concurrent.ConcurrentMap;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import org.springframework.core.SpringProperties;
|
|
||||||
import org.springframework.core.convert.TypeDescriptor;
|
import org.springframework.core.convert.TypeDescriptor;
|
||||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||||
import org.springframework.lang.Nullable;
|
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:
|
* <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
|
* 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
|
* application classes, which allows for clean caching along with the application's
|
||||||
* lifecycle in any case. For a web application, consider declaring a local
|
* lifecycle in any case.
|
||||||
* {@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.
|
|
||||||
*
|
*
|
||||||
* <p>In case of a non-clean ClassLoader arrangement without a cleanup listener having
|
* <p>As of 6.0, Spring's default introspection discovers basic JavaBeans properties
|
||||||
* been set up, this class will fall back to a weak-reference-based caching model that
|
* through an efficient method reflection pass. For full JavaBeans introspection
|
||||||
* recreates much-requested entries every time the garbage collector removed them. In
|
* including indexed properties and all JDK-supported customizers, configure a
|
||||||
* such a scenario, consider the {@link #IGNORE_BEANINFO_PROPERTY_NAME} system property.
|
* {@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 Rod Johnson
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
|
@ -78,32 +79,9 @@ import org.springframework.util.StringUtils;
|
||||||
*/
|
*/
|
||||||
public final class CachedIntrospectionResults {
|
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 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(
|
private static final List<BeanInfoFactory> beanInfoFactories = SpringFactoriesLoader.loadFactories(
|
||||||
BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader());
|
BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader());
|
||||||
|
|
||||||
|
|
@ -241,7 +219,7 @@ public final class CachedIntrospectionResults {
|
||||||
* Retrieve a {@link BeanInfo} descriptor for the given target class.
|
* Retrieve a {@link BeanInfo} descriptor for the given target class.
|
||||||
* @param beanClass the target class to introspect
|
* @param beanClass the target class to introspect
|
||||||
* @return the resulting {@code BeanInfo} descriptor (never {@code null})
|
* @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 {
|
private static BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
|
||||||
for (BeanInfoFactory beanInfoFactory : beanInfoFactories) {
|
for (BeanInfoFactory beanInfoFactory : beanInfoFactories) {
|
||||||
|
|
@ -250,9 +228,14 @@ public final class CachedIntrospectionResults {
|
||||||
return beanInfo;
|
return beanInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (shouldIntrospectorIgnoreBeaninfoClasses ?
|
|
||||||
Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) :
|
Collection<PropertyDescriptor> pds = PropertyDescriptorUtils.determineBasicProperties(beanClass);
|
||||||
Introspector.getBeanInfo(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");
|
* 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.
|
||||||
|
|
@ -18,34 +18,35 @@ package org.springframework.beans;
|
||||||
|
|
||||||
import java.beans.BeanInfo;
|
import java.beans.BeanInfo;
|
||||||
import java.beans.IntrospectionException;
|
import java.beans.IntrospectionException;
|
||||||
import java.beans.Introspector;
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.NonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link BeanInfoFactory} implementation that evaluates whether bean classes have
|
* Extension of {@link StandardBeanInfoFactory} that supports "non-standard"
|
||||||
* "non-standard" JavaBeans setter methods and are thus candidates for introspection
|
* JavaBeans setter methods through introspection by Spring's
|
||||||
* by Spring's (package-visible) {@code ExtendedBeanInfo} implementation.
|
* (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
|
* <p>Ordered at {@link Ordered#LOWEST_PRECEDENCE} to allow other user-defined
|
||||||
* {@link BeanInfoFactory} types to take precedence.
|
* {@link BeanInfoFactory} types to take precedence.
|
||||||
*
|
*
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
|
* @author Juergen Hoeller
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
* @see BeanInfoFactory
|
* @see StandardBeanInfoFactory
|
||||||
* @see CachedIntrospectionResults
|
* @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
|
@Override
|
||||||
@Nullable
|
@NonNull
|
||||||
public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
|
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;
|
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");
|
* 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.
|
||||||
|
|
@ -19,10 +19,14 @@ package org.springframework.beans;
|
||||||
import java.beans.IntrospectionException;
|
import java.beans.IntrospectionException;
|
||||||
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;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common delegate methods for Spring's internal {@link PropertyDescriptor} implementations.
|
* Common delegate methods for Spring's internal {@link PropertyDescriptor} implementations.
|
||||||
|
|
@ -32,6 +36,82 @@ import org.springframework.util.ObjectUtils;
|
||||||
*/
|
*/
|
||||||
abstract class PropertyDescriptorUtils {
|
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}.
|
* See {@link java.beans.FeatureDescriptor}.
|
||||||
*/
|
*/
|
||||||
|
|
@ -173,4 +253,45 @@ 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}.
|
||||||
|
*/
|
||||||
|
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;
|
package org.springframework.beans.factory.aot;
|
||||||
|
|
||||||
import java.beans.BeanInfo;
|
|
||||||
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.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
@ -34,8 +31,7 @@ import java.util.function.Predicate;
|
||||||
import org.springframework.aot.generate.GeneratedMethods;
|
import org.springframework.aot.generate.GeneratedMethods;
|
||||||
import org.springframework.aot.hint.ExecutableMode;
|
import org.springframework.aot.hint.ExecutableMode;
|
||||||
import org.springframework.aot.hint.RuntimeHints;
|
import org.springframework.aot.hint.RuntimeHints;
|
||||||
import org.springframework.beans.BeanInfoFactory;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.beans.ExtendedBeanInfoFactory;
|
|
||||||
import org.springframework.beans.MutablePropertyValues;
|
import org.springframework.beans.MutablePropertyValues;
|
||||||
import org.springframework.beans.PropertyValue;
|
import org.springframework.beans.PropertyValue;
|
||||||
import org.springframework.beans.factory.FactoryBean;
|
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 String BEAN_DEFINITION_VARIABLE = BeanRegistrationCodeFragments.BEAN_DEFINITION_VARIABLE;
|
||||||
|
|
||||||
private static final BeanInfoFactory beanInfoFactory = new ExtendedBeanInfoFactory();
|
|
||||||
|
|
||||||
private final RuntimeHints hints;
|
private final RuntimeHints hints;
|
||||||
|
|
||||||
private final Predicate<String> attributeFilter;
|
private final Predicate<String> attributeFilter;
|
||||||
|
|
@ -185,9 +179,8 @@ class BeanDefinitionPropertiesCodeGenerator {
|
||||||
BEAN_DEFINITION_VARIABLE, propertyValue.getName(), valueCode);
|
BEAN_DEFINITION_VARIABLE, propertyValue.getName(), valueCode);
|
||||||
}
|
}
|
||||||
Class<?> infrastructureType = getInfrastructureType(beanDefinition);
|
Class<?> infrastructureType = getInfrastructureType(beanDefinition);
|
||||||
BeanInfo beanInfo = (infrastructureType != Object.class) ? getBeanInfo(infrastructureType) : null;
|
if (infrastructureType != Object.class) {
|
||||||
if (beanInfo != null) {
|
Map<String, Method> writeMethods = getWriteMethods(infrastructureType);
|
||||||
Map<String, Method> writeMethods = getWriteMethods(beanInfo);
|
|
||||||
for (PropertyValue propertyValue : propertyValues) {
|
for (PropertyValue propertyValue : propertyValues) {
|
||||||
Method writeMethod = writeMethods.get(propertyValue.getName());
|
Method writeMethod = writeMethods.get(propertyValue.getName());
|
||||||
if (writeMethod != null) {
|
if (writeMethod != null) {
|
||||||
|
|
@ -208,25 +201,10 @@ class BeanDefinitionPropertiesCodeGenerator {
|
||||||
return ClassUtils.getUserClass(beanDefinition.getResolvableType().toClass());
|
return ClassUtils.getUserClass(beanDefinition.getResolvableType().toClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
private Map<String, Method> getWriteMethods(Class<?> clazz) {
|
||||||
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) {
|
|
||||||
Map<String, Method> writeMethods = new HashMap<>();
|
Map<String, Method> writeMethods = new HashMap<>();
|
||||||
for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
|
for (PropertyDescriptor propertyDescriptor : BeanUtils.getPropertyDescriptors(clazz)) {
|
||||||
writeMethods.put(propertyDescriptor.getName(),
|
writeMethods.put(propertyDescriptor.getName(), propertyDescriptor.getWriteMethod());
|
||||||
propertyDescriptor.getWriteMethod());
|
|
||||||
}
|
}
|
||||||
return Collections.unmodifiableMap(writeMethods);
|
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");
|
* 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.
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package org.springframework.context.annotation;
|
package org.springframework.context.annotation;
|
||||||
|
|
||||||
import java.beans.Introspector;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
@ -167,7 +166,7 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator {
|
||||||
String beanClassName = definition.getBeanClassName();
|
String beanClassName = definition.getBeanClassName();
|
||||||
Assert.state(beanClassName != null, "No bean class name set");
|
Assert.state(beanClassName != null, "No bean class name set");
|
||||||
String shortClassName = ClassUtils.getShortName(beanClassName);
|
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");
|
* 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.
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package org.springframework.context.annotation;
|
package org.springframework.context.annotation;
|
||||||
|
|
||||||
import java.beans.Introspector;
|
|
||||||
import java.beans.PropertyDescriptor;
|
import java.beans.PropertyDescriptor;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
|
|
@ -592,7 +591,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
|
||||||
if (this.isDefaultName) {
|
if (this.isDefaultName) {
|
||||||
resourceName = this.member.getName();
|
resourceName = this.member.getName();
|
||||||
if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
|
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) {
|
else if (embeddedValueResolver != null) {
|
||||||
|
|
@ -638,7 +637,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
|
||||||
if (this.isDefaultName) {
|
if (this.isDefaultName) {
|
||||||
resourceName = this.member.getName();
|
resourceName = this.member.getName();
|
||||||
if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
|
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();
|
Class<?> resourceType = resource.beanInterface();
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,6 @@
|
||||||
|
|
||||||
package org.springframework.aot.hint;
|
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.Method;
|
||||||
import java.lang.reflect.RecordComponent;
|
import java.lang.reflect.RecordComponent;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
|
@ -92,17 +88,14 @@ public class BindingReflectionHintsRegistrar {
|
||||||
typeHint.withMembers(
|
typeHint.withMembers(
|
||||||
MemberCategory.DECLARED_FIELDS,
|
MemberCategory.DECLARED_FIELDS,
|
||||||
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
|
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
|
||||||
try {
|
for (Method method : clazz.getMethods()) {
|
||||||
BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
|
String methodName = method.getName();
|
||||||
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
|
if (methodName.startsWith("set") && method.getParameterCount() == 1) {
|
||||||
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
|
registerPropertyHints(hints, seen, method, 0);
|
||||||
registerPropertyHints(hints, seen, propertyDescriptor.getWriteMethod(), 0);
|
|
||||||
registerPropertyHints(hints, seen, propertyDescriptor.getReadMethod(), -1);
|
|
||||||
}
|
}
|
||||||
}
|
else if ((methodName.startsWith("get") && method.getParameterCount() == 0 && method.getReturnType() != Void.TYPE) ||
|
||||||
catch (IntrospectionException ex) {
|
(methodName.startsWith("is") && method.getParameterCount() == 0 && method.getReturnType() == boolean.class)) {
|
||||||
if (logger.isDebugEnabled()) {
|
registerPropertyHints(hints, seen, method, -1);
|
||||||
logger.debug("Ignoring referenced type [" + clazz.getName() + "]: " + ex.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -125,8 +118,8 @@ public class BindingReflectionHintsRegistrar {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerPropertyHints(ReflectionHints hints, Set<Type> seen, @Nullable Method method, int parameterIndex) {
|
private void registerPropertyHints(ReflectionHints hints, Set<Type> seen, @Nullable Method method, int parameterIndex) {
|
||||||
if (method != null && method.getDeclaringClass() != Object.class
|
if (method != null && method.getDeclaringClass() != Object.class &&
|
||||||
&& method.getDeclaringClass() != Enum.class) {
|
method.getDeclaringClass() != Enum.class) {
|
||||||
hints.registerMethod(method, ExecutableMode.INVOKE);
|
hints.registerMethod(method, ExecutableMode.INVOKE);
|
||||||
MethodParameter methodParameter = MethodParameter.forExecutable(method, parameterIndex);
|
MethodParameter methodParameter = MethodParameter.forExecutable(method, parameterIndex);
|
||||||
Type methodParameterType = methodParameter.getGenericParameterType();
|
Type methodParameterType = methodParameter.getGenericParameterType();
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package org.springframework.util;
|
package org.springframework.util;
|
||||||
|
|
||||||
import java.beans.Introspector;
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.Externalizable;
|
import java.io.Externalizable;
|
||||||
import java.io.Serializable;
|
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.
|
* property format. Strips the outer class name in case of a nested class.
|
||||||
* @param clazz the class
|
* @param clazz the class
|
||||||
* @return the short name rendered in a standard JavaBeans property format
|
* @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) {
|
public static String getShortNameAsProperty(Class<?> clazz) {
|
||||||
String shortName = getShortName(clazz);
|
String shortName = getShortName(clazz);
|
||||||
int dotIndex = shortName.lastIndexOf(PACKAGE_SEPARATOR);
|
int dotIndex = shortName.lastIndexOf(PACKAGE_SEPARATOR);
|
||||||
shortName = (dotIndex != -1 ? shortName.substring(dotIndex + 1) : shortName);
|
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);
|
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) {
|
private static String changeFirstCharacterCase(String str, boolean capitalize) {
|
||||||
if (!hasLength(str)) {
|
if (!hasLength(str)) {
|
||||||
return str;
|
return str;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue