Skip to content

Commit

Permalink
Introduce PREFERRED_CONSTRUCTORS_ATTRIBUTE on AbstractBeanDefinition
Browse files Browse the repository at this point in the history
Closes gh-30917
  • Loading branch information
jhoeller committed Jul 22, 2023
1 parent b53034f commit 4786e2b
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,10 @@ public CreateFromClassBeanDefinition(CreateFromClassBeanDefinition original) {
@Override
@Nullable
public Constructor<?>[] getPreferredConstructors() {
Constructor<?>[] fromAttribute = super.getPreferredConstructors();
if (fromAttribute != null) {
return fromAttribute;
}
return ConstructorResolver.determinePreferredConstructors(getBeanClass());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,20 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
*/
public static final int DEPENDENCY_CHECK_ALL = 3;

/**
* The name of an attribute that can be
* {@link org.springframework.core.AttributeAccessor#setAttribute set} on a
* {@link org.springframework.beans.factory.config.BeanDefinition} so that
* bean definitions can indicate one or more preferred constructors. This is
* analogous to {@code @Autowired} annotated constructors on the bean class.
* <p>The attribute value may be a single {@link java.lang.reflect.Constructor}
* reference or an array thereof.
* @since 6.1
* @see org.springframework.beans.factory.annotation.Autowired
* @see org.springframework.beans.factory.support.RootBeanDefinition#getPreferredConstructors()
*/
public static final String PREFERRED_CONSTRUCTORS_ATTRIBUTE = "preferredConstructors";

/**
* Constant that indicates the container should attempt to infer the
* {@link #setDestroyMethodName destroy method name} for a bean as opposed to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1698,13 +1698,17 @@ else if (mbd.isLazyInit()) {
*/
ResolvableType getTypeForFactoryBeanFromAttributes(AttributeAccessor attributes) {
Object attribute = attributes.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE);
if (attribute == null) {
return ResolvableType.NONE;
}
if (attribute instanceof ResolvableType resolvableType) {
return resolvableType;
}
if (attribute instanceof Class<?> clazz) {
return ResolvableType.forClass(clazz);
}
return ResolvableType.NONE;
throw new IllegalArgumentException("Invalid value type for attribute '" +
FactoryBean.OBJECT_TYPE_ATTRIBUTE + "': " + attribute.getClass());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,13 +384,28 @@ public ResolvableType getResolvableType() {
/**
* Determine preferred constructors to use for default construction, if any.
* Constructor arguments will be autowired if necessary.
* <p>As of 6.1, the default implementation of this method takes the
* {@link #PREFERRED_CONSTRUCTORS_ATTRIBUTE} attribute into account.
* Subclasses are encouraged to preserve this through a {@code super} call,
* either before or after their own preferred constructor determination.
* @return one or more preferred constructors, or {@code null} if none
* (in which case the regular no-arg default constructor will be called)
* @since 5.1
*/
@Nullable
public Constructor<?>[] getPreferredConstructors() {
return null;
Object attribute = getAttribute(PREFERRED_CONSTRUCTORS_ATTRIBUTE);
if (attribute == null) {
return null;
}
if (attribute instanceof Constructor<?> constructor) {
return new Constructor<?>[] {constructor};
}
if (attribute instanceof Constructor<?>[]) {
return (Constructor<?>[]) attribute;
}
throw new IllegalArgumentException("Invalid value type for attribute '" +
PREFERRED_CONSTRUCTORS_ATTRIBUTE + "': " + attribute.getClass());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import org.springframework.beans.factory.support.BeanDefinitionOverrideException;
import org.springframework.beans.factory.support.ChildBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ConstructorDependenciesBean;
Expand Down Expand Up @@ -1370,10 +1371,10 @@ void autowireWithTwoMatchesForConstructorDependency() {
lbf.registerBeanDefinition("rod2", bd2);
lbf.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());

assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() ->
lbf.autowire(ConstructorDependency.class, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false))
.withMessageContaining("rod")
.withMessageContaining("rod2");
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(() -> lbf.autowire(ConstructorDependency.class, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false))
.withMessageContaining("rod")
.withMessageContaining("rod2");
}

@Test
Expand Down Expand Up @@ -1432,6 +1433,57 @@ void autowireBeanByNameWithNoDependencyCheck() {
assertThat(bean.getSpouse()).isNull();
}

@Test
void autowirePreferredConstructors() {
lbf.registerBeanDefinition("spouse1", new RootBeanDefinition(TestBean.class));
lbf.registerBeanDefinition("spouse2", new RootBeanDefinition(TestBean.class));
RootBeanDefinition bd = new RootBeanDefinition(ConstructorDependenciesBean.class);
bd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
lbf.registerBeanDefinition("bean", bd);
lbf.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());

ConstructorDependenciesBean bean = lbf.getBean(ConstructorDependenciesBean.class);
Object spouse1 = lbf.getBean("spouse1");
Object spouse2 = lbf.getBean("spouse2");
assertThat(bean.getSpouse1()).isSameAs(spouse1);
assertThat(bean.getSpouse2()).isSameAs(spouse2);
}

@Test
void autowirePreferredConstructorsFromAttribute() {
lbf.registerBeanDefinition("spouse1", new RootBeanDefinition(TestBean.class));
lbf.registerBeanDefinition("spouse2", new RootBeanDefinition(TestBean.class));
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(ConstructorDependenciesBean.class);
bd.setAttribute(GenericBeanDefinition.PREFERRED_CONSTRUCTORS_ATTRIBUTE,
ConstructorDependenciesBean.class.getConstructors());
lbf.registerBeanDefinition("bean", bd);
lbf.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());

ConstructorDependenciesBean bean = lbf.getBean(ConstructorDependenciesBean.class);
Object spouse1 = lbf.getBean("spouse1");
Object spouse2 = lbf.getBean("spouse2");
assertThat(bean.getSpouse1()).isSameAs(spouse1);
assertThat(bean.getSpouse2()).isSameAs(spouse2);
}

@Test
void autowirePreferredConstructorFromAttribute() throws Exception {
lbf.registerBeanDefinition("spouse1", new RootBeanDefinition(TestBean.class));
lbf.registerBeanDefinition("spouse2", new RootBeanDefinition(TestBean.class));
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(ConstructorDependenciesBean.class);
bd.setAttribute(GenericBeanDefinition.PREFERRED_CONSTRUCTORS_ATTRIBUTE,
ConstructorDependenciesBean.class.getConstructor(TestBean.class));
lbf.registerBeanDefinition("bean", bd);
lbf.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());

ConstructorDependenciesBean bean = lbf.getBean(ConstructorDependenciesBean.class);
Object spouse = lbf.getBean("spouse1");
assertThat(bean.getSpouse1()).isSameAs(spouse);
assertThat(bean.getSpouse2()).isNull();
}

@Test
void dependsOnCycle() {
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
Expand All @@ -1441,11 +1493,11 @@ void dependsOnCycle() {
bd2.setDependsOn("tb1");
lbf.registerBeanDefinition("tb2", bd2);

assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
lbf.preInstantiateSingletons())
.withMessageContaining("Circular")
.withMessageContaining("'tb2'")
.withMessageContaining("'tb1'");
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> lbf.preInstantiateSingletons())
.withMessageContaining("Circular")
.withMessageContaining("'tb2'")
.withMessageContaining("'tb1'");
}

@Test
Expand All @@ -1460,11 +1512,11 @@ void implicitDependsOnCycle() {
bd3.setDependsOn("tb1");
lbf.registerBeanDefinition("tb3", bd3);

assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
lbf::preInstantiateSingletons)
.withMessageContaining("Circular")
.withMessageContaining("'tb3'")
.withMessageContaining("'tb1'");
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(lbf::preInstantiateSingletons)
.withMessageContaining("Circular")
.withMessageContaining("'tb3'")
.withMessageContaining("'tb1'");
}

@Test
Expand Down Expand Up @@ -1562,9 +1614,9 @@ void getBeanByTypeWithMultiplePrimary() {
lbf.registerBeanDefinition("bd1", bd1);
lbf.registerBeanDefinition("bd2", bd2);

assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(() ->
lbf.getBean(TestBean.class))
.withMessageContaining("more than one 'primary'");
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
.isThrownBy(() -> lbf.getBean(TestBean.class))
.withMessageContaining("more than one 'primary'");
}

@Test
Expand Down Expand Up @@ -1607,10 +1659,10 @@ void getBeanByTypeWithMultiplePriority() {
lbf.registerBeanDefinition("bd1", bd1);
lbf.registerBeanDefinition("bd2", bd2);

assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(() ->
lbf.getBean(TestBean.class))
.withMessageContaining("Multiple beans found with the same priority")
.withMessageContaining("5"); // conflicting priority
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
.isThrownBy(() -> lbf.getBean(TestBean.class))
.withMessageContaining("Multiple beans found with the same priority")
.withMessageContaining("5"); // conflicting priority
}

@Test
Expand Down Expand Up @@ -1815,9 +1867,9 @@ void getBeanByTypeInstanceWithMultiplePrimary() {
lbf.registerBeanDefinition("bd1", bd1);
lbf.registerBeanDefinition("bd2", bd2);

assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(() ->
lbf.getBean(ConstructorDependency.class, 42))
.withMessageContaining("more than one 'primary'");
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
.isThrownBy(() -> lbf.getBean(ConstructorDependency.class, 42))
.withMessageContaining("more than one 'primary'");
}

@Test
Expand Down Expand Up @@ -2004,10 +2056,10 @@ void autowireBeanByTypeWithTwoMatches() {
lbf.registerBeanDefinition("test", bd);
lbf.registerBeanDefinition("spouse", bd2);

assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() ->
lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true))
.withMessageContaining("test")
.withMessageContaining("spouse");
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(() -> lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true))
.withMessageContaining("test")
.withMessageContaining("spouse");
}

@Test
Expand Down Expand Up @@ -2071,10 +2123,10 @@ void autowireBeanByTypeWithIdenticalPriorityCandidates() {
lbf.registerBeanDefinition("test", bd);
lbf.registerBeanDefinition("spouse", bd2);

assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() ->
lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true))
.withCauseExactlyInstanceOf(NoUniqueBeanDefinitionException.class)
.withMessageContaining("5");
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(() -> lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true))
.withCauseExactlyInstanceOf(NoUniqueBeanDefinitionException.class)
.withMessageContaining("5");
}

@Test
Expand Down Expand Up @@ -2337,20 +2389,20 @@ void constructorDependencyWithUnresolvableClass() {
void beanDefinitionWithInterface() {
lbf.registerBeanDefinition("test", new RootBeanDefinition(ITestBean.class));

assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
lbf.getBean("test"))
.withMessageContaining("interface")
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("test"));
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> lbf.getBean("test"))
.withMessageContaining("interface")
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("test"));
}

@Test
void beanDefinitionWithAbstractClass() {
lbf.registerBeanDefinition("test", new RootBeanDefinition(AbstractBeanFactory.class));

assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
lbf.getBean("test"))
.withMessageContaining("abstract")
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("test"));
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> lbf.getBean("test"))
.withMessageContaining("abstract")
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("test"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,10 @@ public ClassDerivedBeanDefinition(ClassDerivedBeanDefinition original) {
@Override
@Nullable
public Constructor<?>[] getPreferredConstructors() {
Constructor<?>[] fromAttribute = super.getPreferredConstructors();
if (fromAttribute != null) {
return fromAttribute;
}
Class<?> clazz = getBeanClass();
Constructor<?> primaryCtor = BeanUtils.findPrimaryConstructor(clazz);
if (primaryCtor != null) {
Expand Down

0 comments on commit 4786e2b

Please sign in to comment.