From 6d9d4157efe6b5adabe217b15ce021eb62474d94 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 28 Aug 2020 19:57:51 +0200 Subject: [PATCH 1/2] Specifically detect Joda-Time 2.x Closes gh-25655 --- .../format/support/DefaultFormattingConversionService.java | 6 +++--- .../http/converter/json/Jackson2ObjectMapperBuilder.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/format/support/DefaultFormattingConversionService.java b/spring-context/src/main/java/org/springframework/format/support/DefaultFormattingConversionService.java index 454fdc4ffe..77316eaf4b 100644 --- a/spring-context/src/main/java/org/springframework/format/support/DefaultFormattingConversionService.java +++ b/spring-context/src/main/java/org/springframework/format/support/DefaultFormattingConversionService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -39,7 +39,7 @@ import org.springframework.util.StringValueResolver; * {@link DefaultConversionService#addDefaultConverters addDefaultConverters} method. * *

Automatically registers formatters for JSR-354 Money & Currency, JSR-310 Date-Time - * and/or Joda-Time, depending on the presence of the corresponding API on the classpath. + * and/or Joda-Time 2.x, depending on the presence of the corresponding API on the classpath. * * @author Chris Beams * @author Juergen Hoeller @@ -54,7 +54,7 @@ public class DefaultFormattingConversionService extends FormattingConversionServ static { ClassLoader classLoader = DefaultFormattingConversionService.class.getClassLoader(); jsr354Present = ClassUtils.isPresent("javax.money.MonetaryAmount", classLoader); - jodaTimePresent = ClassUtils.isPresent("org.joda.time.LocalDate", classLoader); + jodaTimePresent = ClassUtils.isPresent("org.joda.time.YearMonth", classLoader); } diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java index e0862b3342..1748d6ed69 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java @@ -809,8 +809,8 @@ public class Jackson2ObjectMapperBuilder { // jackson-datatype-jsr310 not available } - // Joda-Time present? - if (ClassUtils.isPresent("org.joda.time.LocalDate", this.moduleClassLoader)) { + // Joda-Time 2.x present? + if (ClassUtils.isPresent("org.joda.time.YearMonth", this.moduleClassLoader)) { try { Class jodaModuleClass = (Class) ClassUtils.forName("com.fasterxml.jackson.datatype.joda.JodaModule", this.moduleClassLoader); From bb9e79daa7353a4356f3bfa9d7ffd2b58057a319 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 28 Aug 2020 19:58:56 +0200 Subject: [PATCH 2/2] Polishing --- .../org/springframework/beans/BeanUtils.java | 11 ++----- .../beans/CachedIntrospectionResults.java | 30 ++++++++----------- .../GenericTypeAwarePropertyDescriptor.java | 8 +++-- .../jdbc/core/BeanPropertyRowMapper.java | 18 +++++------ .../jdbc/core/SingleColumnRowMapper.java | 10 ++----- .../jdbc/core/BeanPropertyRowMapperTests.java | 5 ++-- 6 files changed, 32 insertions(+), 50 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java index c0ab530a40..69feba2527 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java @@ -227,7 +227,6 @@ public abstract class BeanUtils { * @since 5.0 * @see Kotlin docs */ - @SuppressWarnings("unchecked") @Nullable public static Constructor findPrimaryConstructor(Class clazz) { Assert.notNull(clazz, "Class must not be null"); @@ -442,8 +441,7 @@ public abstract class BeanUtils { * @throws BeansException if PropertyDescriptor look fails */ public static PropertyDescriptor[] getPropertyDescriptors(Class clazz) throws BeansException { - CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz); - return cr.getPropertyDescriptors(); + return CachedIntrospectionResults.forClass(clazz).getPropertyDescriptors(); } /** @@ -454,11 +452,8 @@ public abstract class BeanUtils { * @throws BeansException if PropertyDescriptor lookup fails */ @Nullable - public static PropertyDescriptor getPropertyDescriptor(Class clazz, String propertyName) - throws BeansException { - - CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz); - return cr.getPropertyDescriptor(propertyName); + public static PropertyDescriptor getPropertyDescriptor(Class clazz, String propertyName) throws BeansException { + return CachedIntrospectionResults.forClass(clazz).getPropertyDescriptor(propertyName); } /** 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 7d94fe28bd..63ab95a11f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java +++ b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java @@ -92,6 +92,8 @@ public final class CachedIntrospectionResults { */ 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); @@ -253,7 +255,7 @@ public final class CachedIntrospectionResults { private final BeanInfo beanInfo; /** PropertyDescriptor objects keyed by property name String. */ - private final Map propertyDescriptorCache; + private final Map propertyDescriptors; /** TypeDescriptor objects keyed by PropertyDescriptor. */ private final ConcurrentMap typeDescriptorCache; @@ -274,7 +276,7 @@ public final class CachedIntrospectionResults { if (logger.isTraceEnabled()) { 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. PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors(); @@ -291,7 +293,7 @@ public final class CachedIntrospectionResults { "; editor [" + pd.getPropertyEditorClass().getName() + "]" : "")); } 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, @@ -313,13 +315,13 @@ public final class CachedIntrospectionResults { for (Class ifc : currClass.getInterfaces()) { if (!ClassUtils.isJavaLanguageInterface(ifc)) { for (PropertyDescriptor pd : getBeanInfo(ifc).getPropertyDescriptors()) { - PropertyDescriptor existingPd = this.propertyDescriptorCache.get(pd.getName()); + PropertyDescriptor existingPd = this.propertyDescriptors.get(pd.getName()); if (existingPd == null || (existingPd.getReadMethod() == null && pd.getReadMethod() != null)) { // GenericTypeAwarePropertyDescriptor leniently resolves a set* write method // against a declared read method, so we prefer read method descriptors here. pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd); - this.propertyDescriptorCache.put(pd.getName(), pd); + this.propertyDescriptors.put(pd.getName(), pd); } } introspectInterfaces(ifc, ifc); @@ -338,27 +340,19 @@ public final class CachedIntrospectionResults { @Nullable PropertyDescriptor getPropertyDescriptor(String name) { - PropertyDescriptor pd = this.propertyDescriptorCache.get(name); + PropertyDescriptor pd = this.propertyDescriptors.get(name); if (pd == null && StringUtils.hasLength(name)) { // Same lenient fallback checking as in Property... - pd = this.propertyDescriptorCache.get(StringUtils.uncapitalize(name)); + pd = this.propertyDescriptors.get(StringUtils.uncapitalize(name)); if (pd == null) { - pd = this.propertyDescriptorCache.get(StringUtils.capitalize(name)); + pd = this.propertyDescriptors.get(StringUtils.capitalize(name)); } } - return (pd == null || pd instanceof GenericTypeAwarePropertyDescriptor ? pd : - buildGenericTypeAwarePropertyDescriptor(getBeanClass(), pd)); + return pd; } PropertyDescriptor[] getPropertyDescriptors() { - PropertyDescriptor[] pds = new PropertyDescriptor[this.propertyDescriptorCache.size()]; - int i = 0; - for (PropertyDescriptor pd : this.propertyDescriptorCache.values()) { - pds[i] = (pd instanceof GenericTypeAwarePropertyDescriptor ? pd : - buildGenericTypeAwarePropertyDescriptor(getBeanClass(), pd)); - i++; - } - return pds; + return this.propertyDescriptors.values().toArray(EMPTY_PROPERTY_DESCRIPTOR_ARRAY); } private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor(Class beanClass, PropertyDescriptor pd) { diff --git a/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java index edcb0cd794..603f5aae15 100644 --- a/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java @@ -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"); * you may not use this file except in compliance with the License. @@ -60,12 +60,13 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor { @Nullable private Class propertyType; + @Nullable private final Class propertyEditorClass; public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName, - @Nullable Method readMethod, @Nullable Method writeMethod, Class propertyEditorClass) - throws IntrospectionException { + @Nullable Method readMethod, @Nullable Method writeMethod, + @Nullable Class propertyEditorClass) throws IntrospectionException { super(propertyName, null, null); this.beanClass = beanClass; @@ -156,6 +157,7 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor { } @Override + @Nullable public Class getPropertyEditorClass() { return this.propertyEditorClass; } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java index 5a5ece73d2..b5860e82fe 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java @@ -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"); * you may not use this file except in compliance with the License. @@ -114,8 +114,6 @@ public class BeanPropertyRowMapper implements RowMapper { /** * Create a new {@code BeanPropertyRowMapper}, accepting unpopulated * properties in the target bean. - *

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 */ public BeanPropertyRowMapper(Class mappedClass) { @@ -222,8 +220,8 @@ public class BeanPropertyRowMapper implements RowMapper { this.mappedClass = mappedClass; this.mappedFields = new HashMap<>(); this.mappedProperties = new HashSet<>(); - PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(mappedClass); - for (PropertyDescriptor pd : pds) { + + for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(mappedClass)) { if (pd.getWriteMethod() != null) { this.mappedFields.put(lowerCaseName(pd.getName()), pd); String underscoredName = underscoreName(pd.getName()); @@ -247,6 +245,7 @@ public class BeanPropertyRowMapper implements RowMapper { if (!StringUtils.hasLength(name)) { return ""; } + StringBuilder result = new StringBuilder(); result.append(lowerCaseName(name.substring(0, 1))); for (int i = 1; i < name.length(); i++) { @@ -337,8 +336,7 @@ public class BeanPropertyRowMapper implements RowMapper { if (populatedProperties != null && !populatedProperties.equals(this.mappedProperties)) { throw new InvalidDataAccessApiUsageException("Given ResultSet does not contain all fields " + - "necessary to populate object of class [" + this.mappedClass.getName() + "]: " + - this.mappedProperties); + "necessary to populate object of " + this.mappedClass + ": " + this.mappedProperties); } return mappedObject; @@ -380,8 +378,7 @@ public class BeanPropertyRowMapper implements RowMapper { /** - * Static factory method to create a new {@code BeanPropertyRowMapper} - * (with the mapped class specified only once). + * Static factory method to create a new {@code BeanPropertyRowMapper}. * @param mappedClass the class that each row should be mapped to * @see #newInstance(Class, ConversionService) */ @@ -390,8 +387,7 @@ public class BeanPropertyRowMapper implements RowMapper { } /** - * Static factory method to create a new {@code BeanPropertyRowMapper} - * (with the required type specified only once). + * Static factory method to create a new {@code BeanPropertyRowMapper}. * @param mappedClass the class that each row should be mapped to * @param conversionService the {@link ConversionService} for binding * JDBC values to bean properties, or {@code null} for none diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/SingleColumnRowMapper.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/SingleColumnRowMapper.java index 0a2e9cb491..3b762906d8 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/SingleColumnRowMapper.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/SingleColumnRowMapper.java @@ -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"); * you may not use this file except in compliance with the License. @@ -62,8 +62,6 @@ public class SingleColumnRowMapper implements RowMapper { /** * Create a new {@code SingleColumnRowMapper}. - *

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 */ public SingleColumnRowMapper(Class requiredType) { @@ -216,8 +214,7 @@ public class SingleColumnRowMapper implements RowMapper { /** - * Static factory method to create a new {@code SingleColumnRowMapper} - * (with the required type specified only once). + * Static factory method to create a new {@code SingleColumnRowMapper}. * @param requiredType the type that each result object is expected to match * @since 4.1 * @see #newInstance(Class, ConversionService) @@ -227,8 +224,7 @@ public class SingleColumnRowMapper implements RowMapper { } /** - * Static factory method to create a new {@code SingleColumnRowMapper} - * (with the required type specified only once). + * Static factory method to create a new {@code SingleColumnRowMapper}. * @param requiredType the type that each result object is expected to match * @param conversionService the {@link ConversionService} for converting a * fetched value, or {@code null} for none diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java index 5672c88ca5..6e1f84a632 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java @@ -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"); * 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 { - @Test - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({"unchecked", "rawtypes"}) public void testOverridingDifferentClassDefinedForMapping() { BeanPropertyRowMapper mapper = new BeanPropertyRowMapper(Person.class); assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() ->