Custom resolution of preferred constructors for createBean(Class)
Avoids side effects of traditional AUTOWIRE_CONSTRUCTOR algorithm. Closes gh-30041
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -246,7 +246,8 @@ public abstract class BeanUtils {
|
||||
// A single public constructor
|
||||
return (Constructor<T>) ctors[0];
|
||||
}
|
||||
else if (ctors.length == 0){
|
||||
else if (ctors.length == 0) {
|
||||
// No public constructors -> check non-public
|
||||
ctors = clazz.getDeclaredConstructors();
|
||||
if (ctors.length == 1) {
|
||||
// A single non-public constructor, e.g. from a non-public record type
|
||||
|
||||
@@ -130,10 +130,10 @@ public interface AutowireCapableBeanFactory extends BeanFactory {
|
||||
* {@link BeanPostProcessor BeanPostProcessors}.
|
||||
* <p>Note: This is intended for creating a fresh instance, populating annotated
|
||||
* fields and methods as well as applying all standard bean initialization callbacks.
|
||||
* Constructor resolution is done via {@link #AUTOWIRE_CONSTRUCTOR}, also influenced
|
||||
* by {@link SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors}.
|
||||
* It does <i>not</i> imply traditional by-name or by-type autowiring of properties;
|
||||
* use {@link #createBean(Class, int, boolean)} for those purposes.
|
||||
* Constructor resolution is based on Kotlin primary / single public / single non-public,
|
||||
* with a fallback to the default constructor in ambiguous scenarios, also influenced
|
||||
* by {@link SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors}
|
||||
* (e.g. for annotation-driven constructor selection).
|
||||
* @param beanClass the class of the bean to create
|
||||
* @return the new bean instance
|
||||
* @throws BeansException if instantiation or wiring failed
|
||||
|
||||
@@ -315,8 +315,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T createBean(Class<T> beanClass) throws BeansException {
|
||||
// Use non-singleton bean definition, to avoid registering bean as dependent bean.
|
||||
RootBeanDefinition bd = new RootBeanDefinition(beanClass);
|
||||
bd.setAutowireMode(AUTOWIRE_CONSTRUCTOR);
|
||||
RootBeanDefinition bd = new CreateFromClassBeanDefinition(beanClass);
|
||||
bd.setScope(SCOPE_PROTOTYPE);
|
||||
bd.allowCaching = ClassUtils.isCacheSafe(beanClass, getBeanClassLoader());
|
||||
return (T) createBean(beanClass.getName(), bd, null);
|
||||
@@ -1909,6 +1908,36 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link RootBeanDefinition} subclass for {@code #createBean} calls with
|
||||
* flexible selection of a Kotlin primary / single public / single non-public
|
||||
* constructor candidate in addition to the default constructor.
|
||||
* @see BeanUtils#getResolvableConstructor(Class)
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private static class CreateFromClassBeanDefinition extends RootBeanDefinition {
|
||||
|
||||
public CreateFromClassBeanDefinition(Class<?> beanClass) {
|
||||
super(beanClass);
|
||||
}
|
||||
|
||||
public CreateFromClassBeanDefinition(CreateFromClassBeanDefinition original) {
|
||||
super(original);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Constructor<?>[] getPreferredConstructors() {
|
||||
return ConstructorResolver.determinePreferredConstructors(getBeanClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootBeanDefinition cloneBeanDefinition() {
|
||||
return new CreateFromClassBeanDefinition(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Special DependencyDescriptor variant for Spring's good old autowire="byType" mode.
|
||||
* Always optional; never considering the parameter name for choosing a primary candidate.
|
||||
@@ -1941,7 +1970,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
public void doWith(Method method) throws IllegalArgumentException {
|
||||
if (isFactoryBeanMethod(method)) {
|
||||
ResolvableType returnType = ResolvableType.forMethodReturnType(method);
|
||||
ResolvableType candidate = returnType.as(FactoryBean.class).getGeneric();
|
||||
|
||||
@@ -1222,6 +1222,54 @@ class ConstructorResolver {
|
||||
return old;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link BeanUtils#getResolvableConstructor(Class)} for alignment.
|
||||
* This variant adds a lenient fallback to the default constructor if available, similar to
|
||||
* {@link org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors}.
|
||||
*/
|
||||
@Nullable
|
||||
static Constructor<?>[] determinePreferredConstructors(Class<?> clazz) {
|
||||
Constructor<?> primaryCtor = BeanUtils.findPrimaryConstructor(clazz);
|
||||
|
||||
Constructor<?> defaultCtor;
|
||||
try {
|
||||
defaultCtor = clazz.getDeclaredConstructor();
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
defaultCtor = null;
|
||||
}
|
||||
|
||||
if (primaryCtor != null) {
|
||||
if (defaultCtor != null && !primaryCtor.equals(defaultCtor)) {
|
||||
return new Constructor<?>[] {primaryCtor, defaultCtor};
|
||||
}
|
||||
else {
|
||||
return new Constructor<?>[] {primaryCtor};
|
||||
}
|
||||
}
|
||||
|
||||
Constructor<?>[] ctors = clazz.getConstructors();
|
||||
if (ctors.length == 1) {
|
||||
// A single public constructor, potentially in combination with a non-public default constructor
|
||||
if (defaultCtor != null && !ctors[0].equals(defaultCtor)) {
|
||||
return new Constructor<?>[] {ctors[0], defaultCtor};
|
||||
}
|
||||
else {
|
||||
return ctors;
|
||||
}
|
||||
}
|
||||
else if (ctors.length == 0) {
|
||||
// No public constructors -> check non-public
|
||||
ctors = clazz.getDeclaredConstructors();
|
||||
if (ctors.length == 1) {
|
||||
// A single non-public constructor, e.g. from a non-public record type
|
||||
return ctors;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Private inner class for holding argument combinations.
|
||||
|
||||
@@ -2126,6 +2126,23 @@ class DefaultListableBeanFactoryTests {
|
||||
assertThat(tb.wasDestroyed()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createBeanWithNonDefaultConstructor() {
|
||||
lbf.registerBeanDefinition("otherTestBean", new RootBeanDefinition(TestBean.class));
|
||||
TestBeanRecipient tb = lbf.createBean(TestBeanRecipient.class);
|
||||
assertThat(lbf.containsSingleton("otherTestBean")).isTrue();
|
||||
assertThat(tb.testBean).isEqualTo(lbf.getBean("otherTestBean"));
|
||||
lbf.destroyBean(tb);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createBeanWithPreferredDefaultConstructor() {
|
||||
lbf.registerBeanDefinition("otherTestBean", new RootBeanDefinition(TestBean.class));
|
||||
TestBean tb = lbf.createBean(TestBean.class);
|
||||
assertThat(lbf.containsSingleton("otherTestBean")).isFalse();
|
||||
lbf.destroyBean(tb);
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureBean() {
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.quartz.spi.TriggerFiredBundle;
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.PropertyAccessorFactory;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -86,8 +87,9 @@ public class SpringBeanJobFactory extends AdaptableJobFactory
|
||||
@Override
|
||||
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
|
||||
Object job = (this.applicationContext != null ?
|
||||
// to be replaced with createBean(Class) in 6.1
|
||||
this.applicationContext.getAutowireCapableBeanFactory().createBean(
|
||||
bundle.getJobDetail().getJobClass()) :
|
||||
bundle.getJobDetail().getJobClass(), AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false) :
|
||||
super.createJobInstance(bundle));
|
||||
|
||||
if (isEligibleForPropertyPopulation(job)) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -558,7 +558,7 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
|
||||
|
||||
|
||||
/**
|
||||
* {@link RootBeanDefinition} marker subclass for {@code #registerBean} based
|
||||
* {@link RootBeanDefinition} subclass for {@code #registerBean} based
|
||||
* registrations with flexible autowiring for public constructors.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
|
||||
@@ -144,7 +144,7 @@ public final class SpringBeanContainer implements BeanContainer {
|
||||
try {
|
||||
if (lifecycleOptions.useJpaCompliantCreation()) {
|
||||
return new SpringContainedBean<>(
|
||||
this.beanFactory.createBean(beanType),
|
||||
this.beanFactory.createBean(beanType, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false),
|
||||
this.beanFactory::destroyBean);
|
||||
}
|
||||
else {
|
||||
|
||||
Reference in New Issue
Block a user