Skip to content

Commit d3c0855

Browse files
committed
Revisit GenericApplicationContext.registerBean constructor handling
Support for Kotlin primary constructor and non-default public constructors in addition to default instantiation, aligned with AnnotationConfigApplicationContext and model attribute processing. Issue: SPR-17292
1 parent 50c9542 commit d3c0855

File tree

5 files changed

+279
-91
lines changed

5 files changed

+279
-91
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1146,14 +1146,20 @@ protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd
11461146
}
11471147
}
11481148

1149-
// Need to determine the constructor...
1149+
// Candidate constructors for autowiring?
11501150
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
11511151
if (ctors != null ||
11521152
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
11531153
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
11541154
return autowireConstructor(beanName, mbd, ctors, args);
11551155
}
11561156

1157+
// Preferred constructors for default construction?
1158+
ctors = mbd.getPreferredConstructors();
1159+
if (ctors != null) {
1160+
return autowireConstructor(beanName, mbd, ctors, null);
1161+
}
1162+
11571163
// No special handling: simply use no-arg constructor.
11581164
return instantiateBean(beanName, mbd);
11591165
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.beans.factory.support;
1818

1919
import java.lang.reflect.AnnotatedElement;
20+
import java.lang.reflect.Constructor;
2021
import java.lang.reflect.Executable;
2122
import java.lang.reflect.Member;
2223
import java.lang.reflect.Method;
@@ -335,6 +336,18 @@ public ResolvableType getResolvableType() {
335336
return (targetType != null ? targetType : ResolvableType.forClass(getBeanClass()));
336337
}
337338

339+
/**
340+
* Determine preferred constructors to use for default construction, if any.
341+
* Constructor arguments will be autowired if necessary.
342+
* @return one or more preferred constructors, or {@code null} if none
343+
* (in which case the regular no-arg default constructor will be called)
344+
* @since 5.1
345+
*/
346+
@Nullable
347+
public Constructor<?>[] getPreferredConstructors() {
348+
return null;
349+
}
350+
338351
/**
339352
* Specify a factory method name that refers to a non-overloaded method.
340353
*/

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

Lines changed: 69 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,19 +17,21 @@
1717
package org.springframework.context.support;
1818

1919
import java.io.IOException;
20+
import java.lang.reflect.Constructor;
2021
import java.util.concurrent.atomic.AtomicBoolean;
2122
import java.util.function.Supplier;
2223

24+
import org.springframework.beans.BeanUtils;
2325
import org.springframework.beans.BeansException;
2426
import org.springframework.beans.factory.BeanDefinitionStoreException;
2527
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2628
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
2729
import org.springframework.beans.factory.config.BeanDefinition;
2830
import org.springframework.beans.factory.config.BeanDefinitionCustomizer;
2931
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
30-
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
3132
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
3233
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
34+
import org.springframework.beans.factory.support.RootBeanDefinition;
3335
import org.springframework.context.ApplicationContext;
3436
import org.springframework.core.io.Resource;
3537
import org.springframework.core.io.ResourceLoader;
@@ -360,9 +362,10 @@ public boolean isAlias(String beanName) {
360362
* Register a bean from the given bean class, optionally customizing its
361363
* bean definition metadata (typically declared as a lambda expression
362364
* or method reference).
363-
* @param beanClass the class of the bean
364-
* @param customizers one or more callbacks for customizing the
365-
* factory's {@link BeanDefinition}, e.g. setting a lazy-init or primary flag
365+
* @param beanClass the class of the bean (resolving a public constructor
366+
* to be autowired, possibly simply the default constructor)
367+
* @param customizers one or more callbacks for customizing the factory's
368+
* {@link BeanDefinition}, e.g. setting a lazy-init or primary flag
366369
* @since 5.0
367370
* @see #registerBean(String, Class, Supplier, BeanDefinitionCustomizer...)
368371
*/
@@ -376,13 +379,16 @@ public final <T> void registerBean(Class<T> beanClass, BeanDefinitionCustomizer.
376379
* method reference), optionally customizing its bean definition metadata
377380
* (again typically declared as a lambda expression or method reference).
378381
* @param beanName the name of the bean (may be {@code null})
379-
* @param beanClass the class of the bean
382+
* @param beanClass the class of the bean (resolving a public constructor
383+
* to be autowired, possibly simply the default constructor)
380384
* @param customizers one or more callbacks for customizing the
381385
* factory's {@link BeanDefinition}, e.g. setting a lazy-init or primary flag
382386
* @since 5.0
383387
* @see #registerBean(String, Class, Supplier, BeanDefinitionCustomizer...)
384388
*/
385-
public final <T> void registerBean(@Nullable String beanName, Class<T> beanClass, BeanDefinitionCustomizer... customizers) {
389+
public final <T> void registerBean(
390+
@Nullable String beanName, Class<T> beanClass, BeanDefinitionCustomizer... customizers) {
391+
386392
registerBean(beanName, beanClass, null, customizers);
387393
}
388394

@@ -393,12 +399,14 @@ public final <T> void registerBean(@Nullable String beanName, Class<T> beanClass
393399
* (again typically declared as a lambda expression or method reference).
394400
* @param beanClass the class of the bean
395401
* @param supplier a callback for creating an instance of the bean
396-
* @param customizers one or more callbacks for customizing the
397-
* factory's {@link BeanDefinition}, e.g. setting a lazy-init or primary flag
402+
* @param customizers one or more callbacks for customizing the factory's
403+
* {@link BeanDefinition}, e.g. setting a lazy-init or primary flag
398404
* @since 5.0
399405
* @see #registerBean(String, Class, Supplier, BeanDefinitionCustomizer...)
400406
*/
401-
public final <T> void registerBean(Class<T> beanClass, Supplier<T> supplier, BeanDefinitionCustomizer... customizers) {
407+
public final <T> void registerBean(
408+
Class<T> beanClass, Supplier<T> supplier, BeanDefinitionCustomizer... customizers) {
409+
402410
registerBean(null, beanClass, supplier, customizers);
403411
}
404412

@@ -410,22 +418,63 @@ public final <T> void registerBean(Class<T> beanClass, Supplier<T> supplier, Bea
410418
* <p>This method can be overridden to adapt the registration mechanism for
411419
* all {@code registerBean} methods (since they all delegate to this one).
412420
* @param beanName the name of the bean (may be {@code null})
413-
* @param beanClass the class of the bean (may be {@code null} if a name is given)
414-
* @param supplier a callback for creating an instance of the bean
415-
* @param customizers one or more callbacks for customizing the
416-
* factory's {@link BeanDefinition}, e.g. setting a lazy-init or primary flag
421+
* @param beanClass the class of the bean
422+
* @param supplier a callback for creating an instance of the bean (in case
423+
* of {@code null}, resolving a public constructor to be autowired instead)
424+
* @param customizers one or more callbacks for customizing the factory's
425+
* {@link BeanDefinition}, e.g. setting a lazy-init or primary flag
417426
* @since 5.0
418427
*/
419-
public <T> void registerBean(@Nullable String beanName, Class<T> beanClass, @Nullable Supplier<T> supplier,
420-
BeanDefinitionCustomizer... customizers) {
428+
public <T> void registerBean(@Nullable String beanName, Class<T> beanClass,
429+
@Nullable Supplier<T> supplier, BeanDefinitionCustomizer... customizers) {
421430

422-
BeanDefinitionBuilder builder = (supplier != null ?
423-
BeanDefinitionBuilder.genericBeanDefinition(beanClass, supplier) :
424-
BeanDefinitionBuilder.genericBeanDefinition(beanClass));
425-
BeanDefinition beanDefinition = builder.applyCustomizers(customizers).getRawBeanDefinition();
431+
ClassDerivedBeanDefinition beanDefinition = new ClassDerivedBeanDefinition(beanClass);
432+
if (supplier != null) {
433+
beanDefinition.setInstanceSupplier(supplier);
434+
}
435+
for (BeanDefinitionCustomizer customizer : customizers) {
436+
customizer.customize(beanDefinition);
437+
}
426438

427439
String nameToUse = (beanName != null ? beanName : beanClass.getName());
428440
registerBeanDefinition(nameToUse, beanDefinition);
429441
}
430442

443+
444+
/**
445+
* {@link RootBeanDefinition} marker subclass for {@code #registerBean} based
446+
* registrations with flexible autowiring for public constructors.
447+
*/
448+
@SuppressWarnings("serial")
449+
private static class ClassDerivedBeanDefinition extends RootBeanDefinition {
450+
451+
public ClassDerivedBeanDefinition(Class<?> beanClass) {
452+
super(beanClass);
453+
}
454+
455+
public ClassDerivedBeanDefinition(ClassDerivedBeanDefinition original) {
456+
super(original);
457+
}
458+
459+
@Override
460+
@Nullable
461+
public Constructor<?>[] getPreferredConstructors() {
462+
Class<?> clazz = getBeanClass();
463+
Constructor<?> primaryCtor = BeanUtils.findPrimaryConstructor(clazz);
464+
if (primaryCtor != null) {
465+
return new Constructor<?>[] {primaryCtor};
466+
}
467+
Constructor<?>[] publicCtors = clazz.getConstructors();
468+
if (publicCtors.length > 0) {
469+
return publicCtors;
470+
}
471+
return null;
472+
}
473+
474+
@Override
475+
public RootBeanDefinition cloneBeanDefinition() {
476+
return new ClassDerivedBeanDefinition(this);
477+
}
478+
}
479+
431480
}

spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java

Lines changed: 50 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,21 @@ public void getBeanByType() {
9090
assertThat(testBean.name, equalTo("foo"));
9191
}
9292

93+
@Test
94+
public void getBeanByTypeRaisesNoSuchBeanDefinitionException() {
95+
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
96+
97+
// attempt to retrieve a bean that does not exist
98+
Class<?> targetType = Pattern.class;
99+
try {
100+
context.getBean(targetType);
101+
fail("Should have thrown NoSuchBeanDefinitionException");
102+
}
103+
catch (NoSuchBeanDefinitionException ex) {
104+
assertThat(ex.getMessage(), containsString(format("No qualifying bean of type '%s'", targetType.getName())));
105+
}
106+
}
107+
93108
/**
94109
* Tests that Configuration classes are registered according to convention
95110
* @see org.springframework.beans.factory.support.DefaultBeanNameGenerator#generateBeanName
@@ -116,6 +131,41 @@ public void explicitConfigClassBeanNameIsRespected() {
116131
assertNotNull(configObject);
117132
}
118133

134+
@Test
135+
public void autowiringIsEnabledByDefault() {
136+
ApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
137+
assertThat(context.getBean(TestBean.class).name, equalTo("foo"));
138+
}
139+
140+
@Test
141+
public void nullReturningBeanPostProcessor() {
142+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
143+
context.register(AutowiredConfig.class);
144+
context.getBeanFactory().addBeanPostProcessor(new BeanPostProcessor() {
145+
@Override
146+
public Object postProcessBeforeInitialization(Object bean, String beanName) {
147+
return (bean instanceof TestBean ? null : bean);
148+
}
149+
@Override
150+
public Object postProcessAfterInitialization(Object bean, String beanName) {
151+
return bean;
152+
}
153+
});
154+
context.getBeanFactory().addBeanPostProcessor(new BeanPostProcessor() {
155+
@Override
156+
public Object postProcessBeforeInitialization(Object bean, String beanName) {
157+
bean.getClass().getName();
158+
return bean;
159+
}
160+
@Override
161+
public Object postProcessAfterInitialization(Object bean, String beanName) {
162+
bean.getClass().getName();
163+
return bean;
164+
}
165+
});
166+
context.refresh();
167+
}
168+
119169
@Test
120170
public void individualBeans() {
121171
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
@@ -299,74 +349,6 @@ public void individualBeanWithFactoryBeanSupplierAndTargetType() {
299349
assertEquals(FactoryBean.class, context.getType("&fb"));
300350
}
301351

302-
@Test
303-
public void getBeanByTypeRaisesNoSuchBeanDefinitionException() {
304-
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
305-
306-
// attempt to retrieve a bean that does not exist
307-
Class<?> targetType = Pattern.class;
308-
try {
309-
context.getBean(targetType);
310-
fail("Should have thrown NoSuchBeanDefinitionException");
311-
}
312-
catch (NoSuchBeanDefinitionException ex) {
313-
assertThat(ex.getMessage(), containsString(format("No qualifying bean of type '%s'", targetType.getName())));
314-
}
315-
}
316-
317-
@Test
318-
public void getBeanByTypeAmbiguityRaisesException() {
319-
ApplicationContext context = new AnnotationConfigApplicationContext(TwoTestBeanConfig.class);
320-
321-
try {
322-
context.getBean(TestBean.class);
323-
}
324-
catch (NoSuchBeanDefinitionException ex) {
325-
assertThat(ex.getMessage(),
326-
allOf(
327-
containsString("No qualifying bean of type '" + TestBean.class.getName() + "'"),
328-
containsString("tb1"),
329-
containsString("tb2")
330-
)
331-
);
332-
}
333-
}
334-
335-
@Test
336-
public void autowiringIsEnabledByDefault() {
337-
ApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
338-
assertThat(context.getBean(TestBean.class).name, equalTo("foo"));
339-
}
340-
341-
@Test
342-
public void nullReturningBeanPostProcessor() {
343-
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
344-
context.register(AutowiredConfig.class);
345-
context.getBeanFactory().addBeanPostProcessor(new BeanPostProcessor() {
346-
@Override
347-
public Object postProcessBeforeInitialization(Object bean, String beanName) {
348-
return (bean instanceof TestBean ? null : bean);
349-
}
350-
@Override
351-
public Object postProcessAfterInitialization(Object bean, String beanName) {
352-
return bean;
353-
}
354-
});
355-
context.getBeanFactory().addBeanPostProcessor(new BeanPostProcessor() {
356-
@Override
357-
public Object postProcessBeforeInitialization(Object bean, String beanName) {
358-
bean.getClass().getName();
359-
return bean;
360-
}
361-
@Override
362-
public Object postProcessAfterInitialization(Object bean, String beanName) {
363-
bean.getClass().getName();
364-
return bean;
365-
}
366-
});
367-
context.refresh();
368-
}
369-
370352

371353
@Configuration
372354
static class Config {

0 commit comments

Comments
 (0)