From 2e57603310c2d016ace1ed26ba6120572bfa9f2c Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 26 Feb 2024 13:40:05 +0100 Subject: [PATCH 1/2] Try type conversion for unique fallback write method as well Closes gh-32329 See gh-32159 --- .../beans/BeanWrapperImpl.java | 26 +++++++++++++------ .../GenericTypeAwarePropertyDescriptor.java | 8 ++++++ .../DefaultListableBeanFactoryTests.java | 13 +++++++--- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java b/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java index 0b004fa495..93a9724d44 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -21,6 +21,7 @@ import java.lang.reflect.Method; import org.apache.commons.logging.LogFactory; +import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.core.convert.TypeDescriptor; import org.springframework.lang.Nullable; @@ -283,16 +284,25 @@ public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements @Override public boolean setValueFallbackIfPossible(@Nullable Object value) { - Method writeMethod = this.pd.getWriteMethodFallback(value != null ? value.getClass() : null); - if (writeMethod != null) { - ReflectionUtils.makeAccessible(writeMethod); - try { + try { + Method writeMethod = this.pd.getWriteMethodFallback(value != null ? value.getClass() : null); + if (writeMethod == null) { + writeMethod = this.pd.getUniqueWriteMethodFallback(); + if (writeMethod != null) { + // Conversion necessary as we would otherwise have received the method + // from the type-matching getWriteMethodFallback call above already + value = convertForProperty(this.pd.getName(), null, value, + new TypeDescriptor(new MethodParameter(writeMethod, 0))); + } + } + if (writeMethod != null) { + ReflectionUtils.makeAccessible(writeMethod); writeMethod.invoke(getWrappedInstance(), value); return true; } - catch (Exception ex) { - LogFactory.getLog(BeanPropertyHandler.class).debug("Write method fallback failed", ex); - } + } + catch (Exception ex) { + LogFactory.getLog(BeanPropertyHandler.class).debug("Write method fallback failed", ex); } return false; } 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 eb3a156869..bc2faca996 100644 --- a/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java @@ -171,6 +171,14 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor { return null; } + @Nullable + public Method getUniqueWriteMethodFallback() { + if (this.ambiguousWriteMethods != null && this.ambiguousWriteMethods.size() == 1) { + return this.ambiguousWriteMethods.iterator().next(); + } + return null; + } + public boolean hasUniqueWriteMethod() { return (this.writeMethod != null && this.ambiguousWriteMethods == null); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java index 501311bd21..ee7dfcd45a 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java @@ -1342,6 +1342,11 @@ class DefaultListableBeanFactoryTests { rbd.getPropertyValues().add("value", Duration.ofSeconds(1000)); lbf.registerBeanDefinition("overloaded", rbd); assertThat(lbf.getBean(SetterOverload.class).getObject()).isEqualTo("1000s"); + + rbd = new RootBeanDefinition(SetterOverload.class); + rbd.getPropertyValues().add("value", "1000"); + lbf.registerBeanDefinition("overloaded", rbd); + assertThat(lbf.getBean(SetterOverload.class).getObject()).isEqualTo("1000i"); } @Test @@ -3369,13 +3374,13 @@ class DefaultListableBeanFactoryTests { return this.value; } - public void setValue(int length) { - this.value = length + "i"; - } - public void setValue(Duration duration) { this.value = duration.getSeconds() + "s"; } + + public void setValue(int length) { + this.value = length + "i"; + } } From 479879c53a9cd6beb3c713c7b37771220d070efb Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 26 Feb 2024 13:40:21 +0100 Subject: [PATCH 2/2] Polishing --- .../AutowiredAnnotationBeanPostProcessorTests.java | 10 ++++------ .../java/org/springframework/core/ResolvableType.java | 5 +++-- .../jms/listener/DefaultMessageListenerContainer.java | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index 2aa4029336..98aa91c203 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -2542,23 +2542,21 @@ class AutowiredAnnotationBeanPostProcessorTests { assertThat(mixedOptionalInjectionBean.nullableBean).isNull(); } - private Consumer methodParameterDeclaredOn( - Class expected) { + + private Consumer methodParameterDeclaredOn(Class expected) { return declaredOn( injectionPoint -> injectionPoint.getMethodParameter().getDeclaringClass(), expected); } - private Consumer fieldDeclaredOn( - Class expected) { + private Consumer fieldDeclaredOn(Class expected) { return declaredOn( injectionPoint -> injectionPoint.getField().getDeclaringClass(), expected); } private Consumer declaredOn( - Function> declaringClassExtractor, - Class expected) { + Function> declaringClassExtractor, Class expected) { return ex -> { InjectionPoint injectionPoint = ex.getInjectionPoint(); Class declaringClass = declaringClassExtractor.apply(injectionPoint); diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index 510b191c9a..4baca27524 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -1122,7 +1122,8 @@ public class ResolvableType implements Serializable { Assert.notNull(clazz, "Class must not be null"); Assert.notNull(generics, "Generics array must not be null"); TypeVariable[] variables = clazz.getTypeParameters(); - Assert.isTrue(variables.length == generics.length, () -> "Mismatched number of generics specified for " + clazz.toGenericString()); + Assert.isTrue(variables.length == generics.length, + () -> "Mismatched number of generics specified for " + clazz.toGenericString()); Type[] arguments = new Type[generics.length]; for (int i = 0; i < generics.length; i++) { diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java b/spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java index bcf93b242b..c424775124 100644 --- a/spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java +++ b/spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java @@ -856,7 +856,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe } /** - * Determine whether this listener container currently has more + * Called to determine whether this listener container currently has more * than one idle instance among its scheduled invokers. */ private int getIdleInvokerCount() {