Avoid pre-conversion attempt in case of overloaded write methods
Closes gh-32159 See gh-31872
This commit is contained in:
parent
067638ae6e
commit
af5acb6d34
|
@ -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.
|
||||
|
@ -607,6 +607,22 @@ public abstract class BeanUtils {
|
|||
return Object.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the specified property has a unique write method,
|
||||
* i.e. is writable but does not declare overloaded setter methods.
|
||||
* @param pd the PropertyDescriptor for the property
|
||||
* @return {@code true} if writable and unique, {@code false} otherwise
|
||||
* @since 6.1.4
|
||||
*/
|
||||
public static boolean hasUniqueWriteMethod(PropertyDescriptor pd) {
|
||||
if (pd instanceof GenericTypeAwarePropertyDescriptor gpd) {
|
||||
return gpd.hasUniqueWriteMethod();
|
||||
}
|
||||
else {
|
||||
return (pd.getWriteMethod() != null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a new MethodParameter object for the write method of the
|
||||
* specified property.
|
||||
|
|
|
@ -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.
|
||||
|
@ -171,6 +171,10 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
|
|||
return null;
|
||||
}
|
||||
|
||||
public boolean hasUniqueWriteMethod() {
|
||||
return (this.writeMethod != null && this.ambiguousWriteMethods == null);
|
||||
}
|
||||
|
||||
public MethodParameter getWriteMethodParameter() {
|
||||
Assert.state(this.writeMethodParameter != null, "No write method available");
|
||||
return this.writeMethodParameter;
|
||||
|
|
|
@ -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.
|
||||
|
@ -39,6 +39,7 @@ import org.springframework.beans.BeanUtils;
|
|||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.BeanWrapperImpl;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.InvalidPropertyException;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.PropertyAccessorUtils;
|
||||
import org.springframework.beans.PropertyValue;
|
||||
|
@ -1683,8 +1684,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
|||
}
|
||||
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
|
||||
Object convertedValue = resolvedValue;
|
||||
boolean convertible = bw.isWritableProperty(propertyName) &&
|
||||
!PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
|
||||
boolean convertible = isConvertibleProperty(propertyName, bw);
|
||||
if (convertible) {
|
||||
convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
|
||||
}
|
||||
|
@ -1721,6 +1721,19 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the factory should cache a converted value for the given property.
|
||||
*/
|
||||
private boolean isConvertibleProperty(String propertyName, BeanWrapper bw) {
|
||||
try {
|
||||
return !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName) &&
|
||||
BeanUtils.hasUniqueWriteMethod(bw.getPropertyDescriptor(propertyName));
|
||||
}
|
||||
catch (InvalidPropertyException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given value for the specified target property.
|
||||
*/
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.lang.reflect.Method;
|
|||
import java.net.MalformedURLException;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
@ -666,13 +667,13 @@ class DefaultListableBeanFactoryTests {
|
|||
bd.setPropertyValues(pvs);
|
||||
lbf.registerBeanDefinition("tb", bd);
|
||||
|
||||
assertThatExceptionOfType(BeanCreationException.class).as("invalid property").isThrownBy(() ->
|
||||
lbf.getBean("tb"))
|
||||
.withCauseInstanceOf(NotWritablePropertyException.class)
|
||||
.satisfies(ex -> {
|
||||
NotWritablePropertyException cause = (NotWritablePropertyException) ex.getCause();
|
||||
assertThat(cause.getPossibleMatches()).containsExactly("age");
|
||||
});
|
||||
assertThatExceptionOfType(BeanCreationException.class).as("invalid property")
|
||||
.isThrownBy(() -> lbf.getBean("tb"))
|
||||
.withCauseInstanceOf(NotWritablePropertyException.class)
|
||||
.satisfies(ex -> {
|
||||
NotWritablePropertyException cause = (NotWritablePropertyException) ex.getCause();
|
||||
assertThat(cause.getPossibleMatches()).containsExactly("age");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -720,9 +721,9 @@ class DefaultListableBeanFactoryTests {
|
|||
p.setProperty("rod.spouse", "*kerry");
|
||||
registerBeanDefinitions(p);
|
||||
|
||||
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
|
||||
lbf.getBean("kerry"))
|
||||
.satisfies(ex -> assertThat(ex.contains(BeanCurrentlyInCreationException.class)).isTrue());
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> lbf.getBean("kerry"))
|
||||
.satisfies(ex -> assertThat(ex.contains(BeanCurrentlyInCreationException.class)).isTrue());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -903,16 +904,16 @@ class DefaultListableBeanFactoryTests {
|
|||
lbf.registerBeanDefinition("test", oldDef);
|
||||
lbf.registerAlias("test", "testX");
|
||||
|
||||
assertThatExceptionOfType(BeanDefinitionOverrideException.class).isThrownBy(() ->
|
||||
lbf.registerBeanDefinition("test", newDef))
|
||||
assertThatExceptionOfType(BeanDefinitionOverrideException.class)
|
||||
.isThrownBy(() -> lbf.registerBeanDefinition("test", newDef))
|
||||
.satisfies(ex -> {
|
||||
assertThat(ex.getBeanName()).isEqualTo("test");
|
||||
assertThat(ex.getBeanDefinition()).isEqualTo(newDef);
|
||||
assertThat(ex.getExistingDefinition()).isEqualTo(oldDef);
|
||||
});
|
||||
|
||||
assertThatExceptionOfType(BeanDefinitionOverrideException.class).isThrownBy(() ->
|
||||
lbf.registerBeanDefinition("testX", newDef))
|
||||
assertThatExceptionOfType(BeanDefinitionOverrideException.class)
|
||||
.isThrownBy(() -> lbf.registerBeanDefinition("testX", newDef))
|
||||
.satisfies(ex -> {
|
||||
assertThat(ex.getBeanName()).isEqualTo("testX");
|
||||
assertThat(ex.getBeanDefinition()).isEqualTo(newDef);
|
||||
|
@ -1320,6 +1321,29 @@ class DefaultListableBeanFactoryTests {
|
|||
assertThat(properties.getProperty("foo")).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withOverloadedSetters() {
|
||||
RootBeanDefinition rbd = new RootBeanDefinition(SetterOverload.class);
|
||||
rbd.getPropertyValues().add("object", "a String");
|
||||
lbf.registerBeanDefinition("overloaded", rbd);
|
||||
assertThat(lbf.getBean(SetterOverload.class).getObject()).isEqualTo("a String");
|
||||
|
||||
rbd = new RootBeanDefinition(SetterOverload.class);
|
||||
rbd.getPropertyValues().add("object", 1000);
|
||||
lbf.registerBeanDefinition("overloaded", rbd);
|
||||
assertThat(lbf.getBean(SetterOverload.class).getObject()).isEqualTo("1000");
|
||||
|
||||
rbd = new RootBeanDefinition(SetterOverload.class);
|
||||
rbd.getPropertyValues().add("value", 1000);
|
||||
lbf.registerBeanDefinition("overloaded", rbd);
|
||||
assertThat(lbf.getBean(SetterOverload.class).getObject()).isEqualTo("1000i");
|
||||
|
||||
rbd = new RootBeanDefinition(SetterOverload.class);
|
||||
rbd.getPropertyValues().add("value", Duration.ofSeconds(1000));
|
||||
lbf.registerBeanDefinition("overloaded", rbd);
|
||||
assertThat(lbf.getBean(SetterOverload.class).getObject()).isEqualTo("1000s");
|
||||
}
|
||||
|
||||
@Test
|
||||
void autowireWithNoDependencies() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(TestBean.class);
|
||||
|
@ -3219,6 +3243,7 @@ class DefaultListableBeanFactoryTests {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public record City(String name) {}
|
||||
|
||||
public static class CityRepository implements Repository<City, Long> {}
|
||||
|
@ -3328,6 +3353,32 @@ class DefaultListableBeanFactoryTests {
|
|||
}
|
||||
|
||||
|
||||
public static class SetterOverload {
|
||||
|
||||
public String value;
|
||||
|
||||
public void setObject(Integer length) {
|
||||
this.value = length + "i";
|
||||
}
|
||||
|
||||
public void setObject(String object) {
|
||||
this.value = object;
|
||||
}
|
||||
|
||||
public String getObject() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void setValue(int length) {
|
||||
this.value = length + "i";
|
||||
}
|
||||
|
||||
public void setValue(Duration duration) {
|
||||
this.value = duration.getSeconds() + "s";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Bean with a dependency on a {@link FactoryBean}.
|
||||
*/
|
||||
|
@ -3475,6 +3526,7 @@ class DefaultListableBeanFactoryTests {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Order
|
||||
private static class LowestPrecedenceTestBeanFactoryBean implements FactoryBean<TestBean> {
|
||||
|
||||
|
@ -3487,9 +3539,9 @@ class DefaultListableBeanFactoryTests {
|
|||
public Class<?> getObjectType() {
|
||||
return TestBean.class;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
private static class HighestPrecedenceTestBeanFactoryBean implements FactoryBean<TestBean> {
|
||||
|
||||
|
@ -3502,7 +3554,6 @@ class DefaultListableBeanFactoryTests {
|
|||
public Class<?> getObjectType() {
|
||||
return TestBean.class;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue