Skip to content

Commit 4786e2b

Browse files
committed
Introduce PREFERRED_CONSTRUCTORS_ATTRIBUTE on AbstractBeanDefinition
Closes gh-30917
1 parent b53034f commit 4786e2b

File tree

6 files changed

+135
-42
lines changed

6 files changed

+135
-42
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1942,6 +1942,10 @@ public CreateFromClassBeanDefinition(CreateFromClassBeanDefinition original) {
19421942
@Override
19431943
@Nullable
19441944
public Constructor<?>[] getPreferredConstructors() {
1945+
Constructor<?>[] fromAttribute = super.getPreferredConstructors();
1946+
if (fromAttribute != null) {
1947+
return fromAttribute;
1948+
}
19451949
return ConstructorResolver.determinePreferredConstructors(getBeanClass());
19461950
}
19471951

spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,20 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
125125
*/
126126
public static final int DEPENDENCY_CHECK_ALL = 3;
127127

128+
/**
129+
* The name of an attribute that can be
130+
* {@link org.springframework.core.AttributeAccessor#setAttribute set} on a
131+
* {@link org.springframework.beans.factory.config.BeanDefinition} so that
132+
* bean definitions can indicate one or more preferred constructors. This is
133+
* analogous to {@code @Autowired} annotated constructors on the bean class.
134+
* <p>The attribute value may be a single {@link java.lang.reflect.Constructor}
135+
* reference or an array thereof.
136+
* @since 6.1
137+
* @see org.springframework.beans.factory.annotation.Autowired
138+
* @see org.springframework.beans.factory.support.RootBeanDefinition#getPreferredConstructors()
139+
*/
140+
public static final String PREFERRED_CONSTRUCTORS_ATTRIBUTE = "preferredConstructors";
141+
128142
/**
129143
* Constant that indicates the container should attempt to infer the
130144
* {@link #setDestroyMethodName destroy method name} for a bean as opposed to

spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1698,13 +1698,17 @@ else if (mbd.isLazyInit()) {
16981698
*/
16991699
ResolvableType getTypeForFactoryBeanFromAttributes(AttributeAccessor attributes) {
17001700
Object attribute = attributes.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE);
1701+
if (attribute == null) {
1702+
return ResolvableType.NONE;
1703+
}
17011704
if (attribute instanceof ResolvableType resolvableType) {
17021705
return resolvableType;
17031706
}
17041707
if (attribute instanceof Class<?> clazz) {
17051708
return ResolvableType.forClass(clazz);
17061709
}
1707-
return ResolvableType.NONE;
1710+
throw new IllegalArgumentException("Invalid value type for attribute '" +
1711+
FactoryBean.OBJECT_TYPE_ATTRIBUTE + "': " + attribute.getClass());
17081712
}
17091713

17101714
/**

spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,13 +384,28 @@ public ResolvableType getResolvableType() {
384384
/**
385385
* Determine preferred constructors to use for default construction, if any.
386386
* Constructor arguments will be autowired if necessary.
387+
* <p>As of 6.1, the default implementation of this method takes the
388+
* {@link #PREFERRED_CONSTRUCTORS_ATTRIBUTE} attribute into account.
389+
* Subclasses are encouraged to preserve this through a {@code super} call,
390+
* either before or after their own preferred constructor determination.
387391
* @return one or more preferred constructors, or {@code null} if none
388392
* (in which case the regular no-arg default constructor will be called)
389393
* @since 5.1
390394
*/
391395
@Nullable
392396
public Constructor<?>[] getPreferredConstructors() {
393-
return null;
397+
Object attribute = getAttribute(PREFERRED_CONSTRUCTORS_ATTRIBUTE);
398+
if (attribute == null) {
399+
return null;
400+
}
401+
if (attribute instanceof Constructor<?> constructor) {
402+
return new Constructor<?>[] {constructor};
403+
}
404+
if (attribute instanceof Constructor<?>[]) {
405+
return (Constructor<?>[]) attribute;
406+
}
407+
throw new IllegalArgumentException("Invalid value type for attribute '" +
408+
PREFERRED_CONSTRUCTORS_ATTRIBUTE + "': " + attribute.getClass());
394409
}
395410

396411
/**

spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java

Lines changed: 92 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import org.springframework.beans.factory.support.BeanDefinitionOverrideException;
6666
import org.springframework.beans.factory.support.ChildBeanDefinition;
6767
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
68+
import org.springframework.beans.factory.support.GenericBeanDefinition;
6869
import org.springframework.beans.factory.support.ManagedList;
6970
import org.springframework.beans.factory.support.RootBeanDefinition;
7071
import org.springframework.beans.factory.xml.ConstructorDependenciesBean;
@@ -1370,10 +1371,10 @@ void autowireWithTwoMatchesForConstructorDependency() {
13701371
lbf.registerBeanDefinition("rod2", bd2);
13711372
lbf.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
13721373

1373-
assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() ->
1374-
lbf.autowire(ConstructorDependency.class, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false))
1375-
.withMessageContaining("rod")
1376-
.withMessageContaining("rod2");
1374+
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
1375+
.isThrownBy(() -> lbf.autowire(ConstructorDependency.class, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false))
1376+
.withMessageContaining("rod")
1377+
.withMessageContaining("rod2");
13771378
}
13781379

13791380
@Test
@@ -1432,6 +1433,57 @@ void autowireBeanByNameWithNoDependencyCheck() {
14321433
assertThat(bean.getSpouse()).isNull();
14331434
}
14341435

1436+
@Test
1437+
void autowirePreferredConstructors() {
1438+
lbf.registerBeanDefinition("spouse1", new RootBeanDefinition(TestBean.class));
1439+
lbf.registerBeanDefinition("spouse2", new RootBeanDefinition(TestBean.class));
1440+
RootBeanDefinition bd = new RootBeanDefinition(ConstructorDependenciesBean.class);
1441+
bd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
1442+
lbf.registerBeanDefinition("bean", bd);
1443+
lbf.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
1444+
1445+
ConstructorDependenciesBean bean = lbf.getBean(ConstructorDependenciesBean.class);
1446+
Object spouse1 = lbf.getBean("spouse1");
1447+
Object spouse2 = lbf.getBean("spouse2");
1448+
assertThat(bean.getSpouse1()).isSameAs(spouse1);
1449+
assertThat(bean.getSpouse2()).isSameAs(spouse2);
1450+
}
1451+
1452+
@Test
1453+
void autowirePreferredConstructorsFromAttribute() {
1454+
lbf.registerBeanDefinition("spouse1", new RootBeanDefinition(TestBean.class));
1455+
lbf.registerBeanDefinition("spouse2", new RootBeanDefinition(TestBean.class));
1456+
GenericBeanDefinition bd = new GenericBeanDefinition();
1457+
bd.setBeanClass(ConstructorDependenciesBean.class);
1458+
bd.setAttribute(GenericBeanDefinition.PREFERRED_CONSTRUCTORS_ATTRIBUTE,
1459+
ConstructorDependenciesBean.class.getConstructors());
1460+
lbf.registerBeanDefinition("bean", bd);
1461+
lbf.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
1462+
1463+
ConstructorDependenciesBean bean = lbf.getBean(ConstructorDependenciesBean.class);
1464+
Object spouse1 = lbf.getBean("spouse1");
1465+
Object spouse2 = lbf.getBean("spouse2");
1466+
assertThat(bean.getSpouse1()).isSameAs(spouse1);
1467+
assertThat(bean.getSpouse2()).isSameAs(spouse2);
1468+
}
1469+
1470+
@Test
1471+
void autowirePreferredConstructorFromAttribute() throws Exception {
1472+
lbf.registerBeanDefinition("spouse1", new RootBeanDefinition(TestBean.class));
1473+
lbf.registerBeanDefinition("spouse2", new RootBeanDefinition(TestBean.class));
1474+
GenericBeanDefinition bd = new GenericBeanDefinition();
1475+
bd.setBeanClass(ConstructorDependenciesBean.class);
1476+
bd.setAttribute(GenericBeanDefinition.PREFERRED_CONSTRUCTORS_ATTRIBUTE,
1477+
ConstructorDependenciesBean.class.getConstructor(TestBean.class));
1478+
lbf.registerBeanDefinition("bean", bd);
1479+
lbf.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
1480+
1481+
ConstructorDependenciesBean bean = lbf.getBean(ConstructorDependenciesBean.class);
1482+
Object spouse = lbf.getBean("spouse1");
1483+
assertThat(bean.getSpouse1()).isSameAs(spouse);
1484+
assertThat(bean.getSpouse2()).isNull();
1485+
}
1486+
14351487
@Test
14361488
void dependsOnCycle() {
14371489
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
@@ -1441,11 +1493,11 @@ void dependsOnCycle() {
14411493
bd2.setDependsOn("tb1");
14421494
lbf.registerBeanDefinition("tb2", bd2);
14431495

1444-
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
1445-
lbf.preInstantiateSingletons())
1446-
.withMessageContaining("Circular")
1447-
.withMessageContaining("'tb2'")
1448-
.withMessageContaining("'tb1'");
1496+
assertThatExceptionOfType(BeanCreationException.class)
1497+
.isThrownBy(() -> lbf.preInstantiateSingletons())
1498+
.withMessageContaining("Circular")
1499+
.withMessageContaining("'tb2'")
1500+
.withMessageContaining("'tb1'");
14491501
}
14501502

14511503
@Test
@@ -1460,11 +1512,11 @@ void implicitDependsOnCycle() {
14601512
bd3.setDependsOn("tb1");
14611513
lbf.registerBeanDefinition("tb3", bd3);
14621514

1463-
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
1464-
lbf::preInstantiateSingletons)
1465-
.withMessageContaining("Circular")
1466-
.withMessageContaining("'tb3'")
1467-
.withMessageContaining("'tb1'");
1515+
assertThatExceptionOfType(BeanCreationException.class)
1516+
.isThrownBy(lbf::preInstantiateSingletons)
1517+
.withMessageContaining("Circular")
1518+
.withMessageContaining("'tb3'")
1519+
.withMessageContaining("'tb1'");
14681520
}
14691521

14701522
@Test
@@ -1562,9 +1614,9 @@ void getBeanByTypeWithMultiplePrimary() {
15621614
lbf.registerBeanDefinition("bd1", bd1);
15631615
lbf.registerBeanDefinition("bd2", bd2);
15641616

1565-
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(() ->
1566-
lbf.getBean(TestBean.class))
1567-
.withMessageContaining("more than one 'primary'");
1617+
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
1618+
.isThrownBy(() -> lbf.getBean(TestBean.class))
1619+
.withMessageContaining("more than one 'primary'");
15681620
}
15691621

15701622
@Test
@@ -1607,10 +1659,10 @@ void getBeanByTypeWithMultiplePriority() {
16071659
lbf.registerBeanDefinition("bd1", bd1);
16081660
lbf.registerBeanDefinition("bd2", bd2);
16091661

1610-
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(() ->
1611-
lbf.getBean(TestBean.class))
1612-
.withMessageContaining("Multiple beans found with the same priority")
1613-
.withMessageContaining("5"); // conflicting priority
1662+
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
1663+
.isThrownBy(() -> lbf.getBean(TestBean.class))
1664+
.withMessageContaining("Multiple beans found with the same priority")
1665+
.withMessageContaining("5"); // conflicting priority
16141666
}
16151667

16161668
@Test
@@ -1815,9 +1867,9 @@ void getBeanByTypeInstanceWithMultiplePrimary() {
18151867
lbf.registerBeanDefinition("bd1", bd1);
18161868
lbf.registerBeanDefinition("bd2", bd2);
18171869

1818-
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(() ->
1819-
lbf.getBean(ConstructorDependency.class, 42))
1820-
.withMessageContaining("more than one 'primary'");
1870+
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
1871+
.isThrownBy(() -> lbf.getBean(ConstructorDependency.class, 42))
1872+
.withMessageContaining("more than one 'primary'");
18211873
}
18221874

18231875
@Test
@@ -2004,10 +2056,10 @@ void autowireBeanByTypeWithTwoMatches() {
20042056
lbf.registerBeanDefinition("test", bd);
20052057
lbf.registerBeanDefinition("spouse", bd2);
20062058

2007-
assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() ->
2008-
lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true))
2009-
.withMessageContaining("test")
2010-
.withMessageContaining("spouse");
2059+
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
2060+
.isThrownBy(() -> lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true))
2061+
.withMessageContaining("test")
2062+
.withMessageContaining("spouse");
20112063
}
20122064

20132065
@Test
@@ -2071,10 +2123,10 @@ void autowireBeanByTypeWithIdenticalPriorityCandidates() {
20712123
lbf.registerBeanDefinition("test", bd);
20722124
lbf.registerBeanDefinition("spouse", bd2);
20732125

2074-
assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() ->
2075-
lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true))
2076-
.withCauseExactlyInstanceOf(NoUniqueBeanDefinitionException.class)
2077-
.withMessageContaining("5");
2126+
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
2127+
.isThrownBy(() -> lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true))
2128+
.withCauseExactlyInstanceOf(NoUniqueBeanDefinitionException.class)
2129+
.withMessageContaining("5");
20782130
}
20792131

20802132
@Test
@@ -2337,20 +2389,20 @@ void constructorDependencyWithUnresolvableClass() {
23372389
void beanDefinitionWithInterface() {
23382390
lbf.registerBeanDefinition("test", new RootBeanDefinition(ITestBean.class));
23392391

2340-
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
2341-
lbf.getBean("test"))
2342-
.withMessageContaining("interface")
2343-
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("test"));
2392+
assertThatExceptionOfType(BeanCreationException.class)
2393+
.isThrownBy(() -> lbf.getBean("test"))
2394+
.withMessageContaining("interface")
2395+
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("test"));
23442396
}
23452397

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

2350-
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
2351-
lbf.getBean("test"))
2352-
.withMessageContaining("abstract")
2353-
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("test"));
2402+
assertThatExceptionOfType(BeanCreationException.class)
2403+
.isThrownBy(() -> lbf.getBean("test"))
2404+
.withMessageContaining("abstract")
2405+
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("test"));
23542406
}
23552407

23562408
@Test

spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,10 @@ public ClassDerivedBeanDefinition(ClassDerivedBeanDefinition original) {
575575
@Override
576576
@Nullable
577577
public Constructor<?>[] getPreferredConstructors() {
578+
Constructor<?>[] fromAttribute = super.getPreferredConstructors();
579+
if (fromAttribute != null) {
580+
return fromAttribute;
581+
}
578582
Class<?> clazz = getBeanClass();
579583
Constructor<?> primaryCtor = BeanUtils.findPrimaryConstructor(clazz);
580584
if (primaryCtor != null) {

0 commit comments

Comments
 (0)