Browse Source

Custom resolution of preferred constructors for createBean(Class)

Avoids side effects of traditional AUTOWIRE_CONSTRUCTOR algorithm.

Closes gh-30041
pull/30069/head
Juergen Hoeller 2 years ago
parent
commit
f8cb0fa2a0
  1. 5
      spring-beans/src/main/java/org/springframework/beans/BeanUtils.java
  2. 8
      spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java
  3. 35
      spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java
  4. 48
      spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java
  5. 17
      spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java
  6. 4
      spring-context-support/src/main/java/org/springframework/scheduling/quartz/SpringBeanJobFactory.java
  7. 4
      spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java
  8. 2
      spring-orm/src/main/java/org/springframework/orm/hibernate5/SpringBeanContainer.java

5
spring-beans/src/main/java/org/springframework/beans/BeanUtils.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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

8
spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java

@ -130,10 +130,10 @@ public interface AutowireCapableBeanFactory extends BeanFactory { @@ -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

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

@ -315,8 +315,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac @@ -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 @@ -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 @@ -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();

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

@ -1222,6 +1222,54 @@ class ConstructorResolver { @@ -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.

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

@ -2126,6 +2126,23 @@ class DefaultListableBeanFactoryTests { @@ -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();

4
spring-context-support/src/main/java/org/springframework/scheduling/quartz/SpringBeanJobFactory.java

@ -22,6 +22,7 @@ import org.quartz.spi.TriggerFiredBundle; @@ -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 @@ -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)) {

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

@ -1,5 +1,5 @@ @@ -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 @@ -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")

2
spring-orm/src/main/java/org/springframework/orm/hibernate5/SpringBeanContainer.java

@ -144,7 +144,7 @@ public final class SpringBeanContainer implements BeanContainer { @@ -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 {

Loading…
Cancel
Save