Polishing

This commit is contained in:
Juergen Hoeller
2020-08-28 19:58:56 +02:00
parent 6d9d4157ef
commit bb9e79daa7
6 changed files with 32 additions and 50 deletions

View File

@@ -227,7 +227,6 @@ public abstract class BeanUtils {
* @since 5.0 * @since 5.0
* @see <a href="https://kotlinlang.org/docs/reference/classes.html#constructors">Kotlin docs</a> * @see <a href="https://kotlinlang.org/docs/reference/classes.html#constructors">Kotlin docs</a>
*/ */
@SuppressWarnings("unchecked")
@Nullable @Nullable
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) { public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
Assert.notNull(clazz, "Class must not be null"); Assert.notNull(clazz, "Class must not be null");
@@ -442,8 +441,7 @@ public abstract class BeanUtils {
* @throws BeansException if PropertyDescriptor look fails * @throws BeansException if PropertyDescriptor look fails
*/ */
public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws BeansException { public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws BeansException {
CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz); return CachedIntrospectionResults.forClass(clazz).getPropertyDescriptors();
return cr.getPropertyDescriptors();
} }
/** /**
@@ -454,11 +452,8 @@ public abstract class BeanUtils {
* @throws BeansException if PropertyDescriptor lookup fails * @throws BeansException if PropertyDescriptor lookup fails
*/ */
@Nullable @Nullable
public static PropertyDescriptor getPropertyDescriptor(Class<?> clazz, String propertyName) public static PropertyDescriptor getPropertyDescriptor(Class<?> clazz, String propertyName) throws BeansException {
throws BeansException { return CachedIntrospectionResults.forClass(clazz).getPropertyDescriptor(propertyName);
CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
return cr.getPropertyDescriptor(propertyName);
} }
/** /**

View File

@@ -92,6 +92,8 @@ public final class CachedIntrospectionResults {
*/ */
public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore"; public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";
private static final PropertyDescriptor[] EMPTY_PROPERTY_DESCRIPTOR_ARRAY = {};
private static final boolean shouldIntrospectorIgnoreBeaninfoClasses = private static final boolean shouldIntrospectorIgnoreBeaninfoClasses =
SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME); SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME);
@@ -253,7 +255,7 @@ public final class CachedIntrospectionResults {
private final BeanInfo beanInfo; private final BeanInfo beanInfo;
/** PropertyDescriptor objects keyed by property name String. */ /** PropertyDescriptor objects keyed by property name String. */
private final Map<String, PropertyDescriptor> propertyDescriptorCache; private final Map<String, PropertyDescriptor> propertyDescriptors;
/** TypeDescriptor objects keyed by PropertyDescriptor. */ /** TypeDescriptor objects keyed by PropertyDescriptor. */
private final ConcurrentMap<PropertyDescriptor, TypeDescriptor> typeDescriptorCache; private final ConcurrentMap<PropertyDescriptor, TypeDescriptor> typeDescriptorCache;
@@ -274,7 +276,7 @@ public final class CachedIntrospectionResults {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]"); logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]");
} }
this.propertyDescriptorCache = new LinkedHashMap<>(); this.propertyDescriptors = new LinkedHashMap<>();
// This call is slow so we do it once. // This call is slow so we do it once.
PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors(); PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors();
@@ -291,7 +293,7 @@ public final class CachedIntrospectionResults {
"; editor [" + pd.getPropertyEditorClass().getName() + "]" : "")); "; editor [" + pd.getPropertyEditorClass().getName() + "]" : ""));
} }
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd); pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
this.propertyDescriptorCache.put(pd.getName(), pd); this.propertyDescriptors.put(pd.getName(), pd);
} }
// Explicitly check implemented interfaces for setter/getter methods as well, // Explicitly check implemented interfaces for setter/getter methods as well,
@@ -313,13 +315,13 @@ public final class CachedIntrospectionResults {
for (Class<?> ifc : currClass.getInterfaces()) { for (Class<?> ifc : currClass.getInterfaces()) {
if (!ClassUtils.isJavaLanguageInterface(ifc)) { if (!ClassUtils.isJavaLanguageInterface(ifc)) {
for (PropertyDescriptor pd : getBeanInfo(ifc).getPropertyDescriptors()) { for (PropertyDescriptor pd : getBeanInfo(ifc).getPropertyDescriptors()) {
PropertyDescriptor existingPd = this.propertyDescriptorCache.get(pd.getName()); PropertyDescriptor existingPd = this.propertyDescriptors.get(pd.getName());
if (existingPd == null || if (existingPd == null ||
(existingPd.getReadMethod() == null && pd.getReadMethod() != null)) { (existingPd.getReadMethod() == null && pd.getReadMethod() != null)) {
// GenericTypeAwarePropertyDescriptor leniently resolves a set* write method // GenericTypeAwarePropertyDescriptor leniently resolves a set* write method
// against a declared read method, so we prefer read method descriptors here. // against a declared read method, so we prefer read method descriptors here.
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd); pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
this.propertyDescriptorCache.put(pd.getName(), pd); this.propertyDescriptors.put(pd.getName(), pd);
} }
} }
introspectInterfaces(ifc, ifc); introspectInterfaces(ifc, ifc);
@@ -338,27 +340,19 @@ public final class CachedIntrospectionResults {
@Nullable @Nullable
PropertyDescriptor getPropertyDescriptor(String name) { PropertyDescriptor getPropertyDescriptor(String name) {
PropertyDescriptor pd = this.propertyDescriptorCache.get(name); PropertyDescriptor pd = this.propertyDescriptors.get(name);
if (pd == null && StringUtils.hasLength(name)) { if (pd == null && StringUtils.hasLength(name)) {
// Same lenient fallback checking as in Property... // Same lenient fallback checking as in Property...
pd = this.propertyDescriptorCache.get(StringUtils.uncapitalize(name)); pd = this.propertyDescriptors.get(StringUtils.uncapitalize(name));
if (pd == null) { if (pd == null) {
pd = this.propertyDescriptorCache.get(StringUtils.capitalize(name)); pd = this.propertyDescriptors.get(StringUtils.capitalize(name));
} }
} }
return (pd == null || pd instanceof GenericTypeAwarePropertyDescriptor ? pd : return pd;
buildGenericTypeAwarePropertyDescriptor(getBeanClass(), pd));
} }
PropertyDescriptor[] getPropertyDescriptors() { PropertyDescriptor[] getPropertyDescriptors() {
PropertyDescriptor[] pds = new PropertyDescriptor[this.propertyDescriptorCache.size()]; return this.propertyDescriptors.values().toArray(EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
int i = 0;
for (PropertyDescriptor pd : this.propertyDescriptorCache.values()) {
pds[i] = (pd instanceof GenericTypeAwarePropertyDescriptor ? pd :
buildGenericTypeAwarePropertyDescriptor(getBeanClass(), pd));
i++;
}
return pds;
} }
private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor(Class<?> beanClass, PropertyDescriptor pd) { private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor(Class<?> beanClass, PropertyDescriptor pd) {

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2017 the original author or authors. * Copyright 2002-2020 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.
@@ -60,12 +60,13 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
@Nullable @Nullable
private Class<?> propertyType; private Class<?> propertyType;
@Nullable
private final Class<?> propertyEditorClass; private final Class<?> propertyEditorClass;
public GenericTypeAwarePropertyDescriptor(Class<?> beanClass, String propertyName, public GenericTypeAwarePropertyDescriptor(Class<?> beanClass, String propertyName,
@Nullable Method readMethod, @Nullable Method writeMethod, Class<?> propertyEditorClass) @Nullable Method readMethod, @Nullable Method writeMethod,
throws IntrospectionException { @Nullable Class<?> propertyEditorClass) throws IntrospectionException {
super(propertyName, null, null); super(propertyName, null, null);
this.beanClass = beanClass; this.beanClass = beanClass;
@@ -156,6 +157,7 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
} }
@Override @Override
@Nullable
public Class<?> getPropertyEditorClass() { public Class<?> getPropertyEditorClass() {
return this.propertyEditorClass; return this.propertyEditorClass;
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2020 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.
@@ -114,8 +114,6 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
/** /**
* Create a new {@code BeanPropertyRowMapper}, accepting unpopulated * Create a new {@code BeanPropertyRowMapper}, accepting unpopulated
* properties in the target bean. * properties in the target bean.
* <p>Consider using the {@link #newInstance} factory method instead,
* which allows for specifying the mapped type once only.
* @param mappedClass the class that each row should be mapped to * @param mappedClass the class that each row should be mapped to
*/ */
public BeanPropertyRowMapper(Class<T> mappedClass) { public BeanPropertyRowMapper(Class<T> mappedClass) {
@@ -222,8 +220,8 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
this.mappedClass = mappedClass; this.mappedClass = mappedClass;
this.mappedFields = new HashMap<>(); this.mappedFields = new HashMap<>();
this.mappedProperties = new HashSet<>(); this.mappedProperties = new HashSet<>();
PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(mappedClass);
for (PropertyDescriptor pd : pds) { for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(mappedClass)) {
if (pd.getWriteMethod() != null) { if (pd.getWriteMethod() != null) {
this.mappedFields.put(lowerCaseName(pd.getName()), pd); this.mappedFields.put(lowerCaseName(pd.getName()), pd);
String underscoredName = underscoreName(pd.getName()); String underscoredName = underscoreName(pd.getName());
@@ -247,6 +245,7 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
if (!StringUtils.hasLength(name)) { if (!StringUtils.hasLength(name)) {
return ""; return "";
} }
StringBuilder result = new StringBuilder(); StringBuilder result = new StringBuilder();
result.append(lowerCaseName(name.substring(0, 1))); result.append(lowerCaseName(name.substring(0, 1)));
for (int i = 1; i < name.length(); i++) { for (int i = 1; i < name.length(); i++) {
@@ -337,8 +336,7 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
if (populatedProperties != null && !populatedProperties.equals(this.mappedProperties)) { if (populatedProperties != null && !populatedProperties.equals(this.mappedProperties)) {
throw new InvalidDataAccessApiUsageException("Given ResultSet does not contain all fields " + throw new InvalidDataAccessApiUsageException("Given ResultSet does not contain all fields " +
"necessary to populate object of class [" + this.mappedClass.getName() + "]: " + "necessary to populate object of " + this.mappedClass + ": " + this.mappedProperties);
this.mappedProperties);
} }
return mappedObject; return mappedObject;
@@ -380,8 +378,7 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
/** /**
* Static factory method to create a new {@code BeanPropertyRowMapper} * Static factory method to create a new {@code BeanPropertyRowMapper}.
* (with the mapped class specified only once).
* @param mappedClass the class that each row should be mapped to * @param mappedClass the class that each row should be mapped to
* @see #newInstance(Class, ConversionService) * @see #newInstance(Class, ConversionService)
*/ */
@@ -390,8 +387,7 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
} }
/** /**
* Static factory method to create a new {@code BeanPropertyRowMapper} * Static factory method to create a new {@code BeanPropertyRowMapper}.
* (with the required type specified only once).
* @param mappedClass the class that each row should be mapped to * @param mappedClass the class that each row should be mapped to
* @param conversionService the {@link ConversionService} for binding * @param conversionService the {@link ConversionService} for binding
* JDBC values to bean properties, or {@code null} for none * JDBC values to bean properties, or {@code null} for none

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2020 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.
@@ -62,8 +62,6 @@ public class SingleColumnRowMapper<T> implements RowMapper<T> {
/** /**
* Create a new {@code SingleColumnRowMapper}. * Create a new {@code SingleColumnRowMapper}.
* <p>Consider using the {@link #newInstance} factory method instead,
* which allows for specifying the required type once only.
* @param requiredType the type that each result object is expected to match * @param requiredType the type that each result object is expected to match
*/ */
public SingleColumnRowMapper(Class<T> requiredType) { public SingleColumnRowMapper(Class<T> requiredType) {
@@ -216,8 +214,7 @@ public class SingleColumnRowMapper<T> implements RowMapper<T> {
/** /**
* Static factory method to create a new {@code SingleColumnRowMapper} * Static factory method to create a new {@code SingleColumnRowMapper}.
* (with the required type specified only once).
* @param requiredType the type that each result object is expected to match * @param requiredType the type that each result object is expected to match
* @since 4.1 * @since 4.1
* @see #newInstance(Class, ConversionService) * @see #newInstance(Class, ConversionService)
@@ -227,8 +224,7 @@ public class SingleColumnRowMapper<T> implements RowMapper<T> {
} }
/** /**
* Static factory method to create a new {@code SingleColumnRowMapper} * Static factory method to create a new {@code SingleColumnRowMapper}.
* (with the required type specified only once).
* @param requiredType the type that each result object is expected to match * @param requiredType the type that each result object is expected to match
* @param conversionService the {@link ConversionService} for converting a * @param conversionService the {@link ConversionService} for converting a
* fetched value, or {@code null} for none * fetched value, or {@code null} for none

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2020 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.
@@ -37,9 +37,8 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*/ */
public class BeanPropertyRowMapperTests extends AbstractRowMapperTests { public class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
@Test @Test
@SuppressWarnings({ "unchecked", "rawtypes" }) @SuppressWarnings({"unchecked", "rawtypes"})
public void testOverridingDifferentClassDefinedForMapping() { public void testOverridingDifferentClassDefinedForMapping() {
BeanPropertyRowMapper mapper = new BeanPropertyRowMapper(Person.class); BeanPropertyRowMapper mapper = new BeanPropertyRowMapper(Person.class);
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() -> assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() ->