diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java index ea4d3b052b..7647f4b667 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java @@ -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) 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 diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java index 5c87f8a984..fb6e92b1fa 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java @@ -130,10 +130,10 @@ public interface AutowireCapableBeanFactory extends BeanFactory { * {@link BeanPostProcessor BeanPostProcessors}. *

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 not 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 diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index cbb7381e40..1739dd6fb8 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -315,8 +315,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac @SuppressWarnings("unchecked") public T createBean(Class 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(); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java index 5fee3b952e..2d7c5faab4 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -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. diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java index b338fda1e0..6a84b11baf 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java @@ -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(); diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SpringBeanJobFactory.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SpringBeanJobFactory.java index cdeab7f5f2..796f56ce02 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SpringBeanJobFactory.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SpringBeanJobFactory.java @@ -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)) { diff --git a/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java index c172acc02d..85da53b39b 100644 --- a/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java @@ -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") diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/SpringBeanContainer.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/SpringBeanContainer.java index 912ebf544a..e33013c448 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/SpringBeanContainer.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/SpringBeanContainer.java @@ -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 {