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 2ede680a31..83d468d29c 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 @@ -1146,7 +1146,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac } } - // Need to determine the constructor... + // Candidate constructors for autowiring? Constructor[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors != null || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR || @@ -1154,6 +1154,12 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac return autowireConstructor(beanName, mbd, ctors, args); } + // Preferred constructors for default construction? + ctors = mbd.getPreferredConstructors(); + if (ctors != null) { + return autowireConstructor(beanName, mbd, ctors, null); + } + // No special handling: simply use no-arg constructor. return instantiateBean(beanName, mbd); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java index f2af6a31b7..c8de0f5bee 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -17,6 +17,7 @@ package org.springframework.beans.factory.support; import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Member; import java.lang.reflect.Method; @@ -335,6 +336,18 @@ public class RootBeanDefinition extends AbstractBeanDefinition { return (targetType != null ? targetType : ResolvableType.forClass(getBeanClass())); } + /** + * Determine preferred constructors to use for default construction, if any. + * Constructor arguments will be autowired if necessary. + * @return one or more preferred constructors, or {@code null} if none + * (in which case the regular no-arg default constructor will be called) + * @since 5.1 + */ + @Nullable + public Constructor[] getPreferredConstructors() { + return null; + } + /** * Specify a factory method name that refers to a non-overloaded method. */ 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 1a73c6be72..00f6f61684 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-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -17,9 +17,11 @@ package org.springframework.context.support; import java.io.IOException; +import java.lang.reflect.Constructor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; +import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; @@ -27,9 +29,9 @@ import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionCustomizer; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContext; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; @@ -360,9 +362,10 @@ public class GenericApplicationContext extends AbstractApplicationContext implem * Register a bean from the given bean class, optionally customizing its * bean definition metadata (typically declared as a lambda expression * or method reference). - * @param beanClass the class of the bean - * @param customizers one or more callbacks for customizing the - * factory's {@link BeanDefinition}, e.g. setting a lazy-init or primary flag + * @param beanClass the class of the bean (resolving a public constructor + * to be autowired, possibly simply the default constructor) + * @param customizers one or more callbacks for customizing the factory's + * {@link BeanDefinition}, e.g. setting a lazy-init or primary flag * @since 5.0 * @see #registerBean(String, Class, Supplier, BeanDefinitionCustomizer...) */ @@ -376,13 +379,16 @@ public class GenericApplicationContext extends AbstractApplicationContext implem * method reference), optionally customizing its bean definition metadata * (again typically declared as a lambda expression or method reference). * @param beanName the name of the bean (may be {@code null}) - * @param beanClass the class of the bean + * @param beanClass the class of the bean (resolving a public constructor + * to be autowired, possibly simply the default constructor) * @param customizers one or more callbacks for customizing the * factory's {@link BeanDefinition}, e.g. setting a lazy-init or primary flag * @since 5.0 * @see #registerBean(String, Class, Supplier, BeanDefinitionCustomizer...) */ - public final void registerBean(@Nullable String beanName, Class beanClass, BeanDefinitionCustomizer... customizers) { + public final void registerBean( + @Nullable String beanName, Class beanClass, BeanDefinitionCustomizer... customizers) { + registerBean(beanName, beanClass, null, customizers); } @@ -393,12 +399,14 @@ public class GenericApplicationContext extends AbstractApplicationContext implem * (again typically declared as a lambda expression or method reference). * @param beanClass the class of the bean * @param supplier a callback for creating an instance of the bean - * @param customizers one or more callbacks for customizing the - * factory's {@link BeanDefinition}, e.g. setting a lazy-init or primary flag + * @param customizers one or more callbacks for customizing the factory's + * {@link BeanDefinition}, e.g. setting a lazy-init or primary flag * @since 5.0 * @see #registerBean(String, Class, Supplier, BeanDefinitionCustomizer...) */ - public final void registerBean(Class beanClass, Supplier supplier, BeanDefinitionCustomizer... customizers) { + public final void registerBean( + Class beanClass, Supplier supplier, BeanDefinitionCustomizer... customizers) { + registerBean(null, beanClass, supplier, customizers); } @@ -410,22 +418,63 @@ public class GenericApplicationContext extends AbstractApplicationContext implem *

This method can be overridden to adapt the registration mechanism for * all {@code registerBean} methods (since they all delegate to this one). * @param beanName the name of the bean (may be {@code null}) - * @param beanClass the class of the bean (may be {@code null} if a name is given) - * @param supplier a callback for creating an instance of the bean - * @param customizers one or more callbacks for customizing the - * factory's {@link BeanDefinition}, e.g. setting a lazy-init or primary flag + * @param beanClass the class of the bean + * @param supplier a callback for creating an instance of the bean (in case + * of {@code null}, resolving a public constructor to be autowired instead) + * @param customizers one or more callbacks for customizing the factory's + * {@link BeanDefinition}, e.g. setting a lazy-init or primary flag * @since 5.0 */ - public void registerBean(@Nullable String beanName, Class beanClass, @Nullable Supplier supplier, - BeanDefinitionCustomizer... customizers) { + public void registerBean(@Nullable String beanName, Class beanClass, + @Nullable Supplier supplier, BeanDefinitionCustomizer... customizers) { - BeanDefinitionBuilder builder = (supplier != null ? - BeanDefinitionBuilder.genericBeanDefinition(beanClass, supplier) : - BeanDefinitionBuilder.genericBeanDefinition(beanClass)); - BeanDefinition beanDefinition = builder.applyCustomizers(customizers).getRawBeanDefinition(); + ClassDerivedBeanDefinition beanDefinition = new ClassDerivedBeanDefinition(beanClass); + if (supplier != null) { + beanDefinition.setInstanceSupplier(supplier); + } + for (BeanDefinitionCustomizer customizer : customizers) { + customizer.customize(beanDefinition); + } String nameToUse = (beanName != null ? beanName : beanClass.getName()); registerBeanDefinition(nameToUse, beanDefinition); } + + /** + * {@link RootBeanDefinition} marker subclass for {@code #registerBean} based + * registrations with flexible autowiring for public constructors. + */ + @SuppressWarnings("serial") + private static class ClassDerivedBeanDefinition extends RootBeanDefinition { + + public ClassDerivedBeanDefinition(Class beanClass) { + super(beanClass); + } + + public ClassDerivedBeanDefinition(ClassDerivedBeanDefinition original) { + super(original); + } + + @Override + @Nullable + public Constructor[] getPreferredConstructors() { + Class clazz = getBeanClass(); + Constructor primaryCtor = BeanUtils.findPrimaryConstructor(clazz); + if (primaryCtor != null) { + return new Constructor[] {primaryCtor}; + } + Constructor[] publicCtors = clazz.getConstructors(); + if (publicCtors.length > 0) { + return publicCtors; + } + return null; + } + + @Override + public RootBeanDefinition cloneBeanDefinition() { + return new ClassDerivedBeanDefinition(this); + } + } + } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java index 13d5efa091..b38753e97c 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java @@ -90,6 +90,21 @@ public class AnnotationConfigApplicationContextTests { assertThat(testBean.name, equalTo("foo")); } + @Test + public void getBeanByTypeRaisesNoSuchBeanDefinitionException() { + ApplicationContext context = new AnnotationConfigApplicationContext(Config.class); + + // attempt to retrieve a bean that does not exist + Class targetType = Pattern.class; + try { + context.getBean(targetType); + fail("Should have thrown NoSuchBeanDefinitionException"); + } + catch (NoSuchBeanDefinitionException ex) { + assertThat(ex.getMessage(), containsString(format("No qualifying bean of type '%s'", targetType.getName()))); + } + } + /** * Tests that Configuration classes are registered according to convention * @see org.springframework.beans.factory.support.DefaultBeanNameGenerator#generateBeanName @@ -116,6 +131,41 @@ public class AnnotationConfigApplicationContextTests { assertNotNull(configObject); } + @Test + public void autowiringIsEnabledByDefault() { + ApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class); + assertThat(context.getBean(TestBean.class).name, equalTo("foo")); + } + + @Test + public void nullReturningBeanPostProcessor() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(AutowiredConfig.class); + context.getBeanFactory().addBeanPostProcessor(new BeanPostProcessor() { + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) { + return (bean instanceof TestBean ? null : bean); + } + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + return bean; + } + }); + context.getBeanFactory().addBeanPostProcessor(new BeanPostProcessor() { + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) { + bean.getClass().getName(); + return bean; + } + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + bean.getClass().getName(); + return bean; + } + }); + context.refresh(); + } + @Test public void individualBeans() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); @@ -299,74 +349,6 @@ public class AnnotationConfigApplicationContextTests { assertEquals(FactoryBean.class, context.getType("&fb")); } - @Test - public void getBeanByTypeRaisesNoSuchBeanDefinitionException() { - ApplicationContext context = new AnnotationConfigApplicationContext(Config.class); - - // attempt to retrieve a bean that does not exist - Class targetType = Pattern.class; - try { - context.getBean(targetType); - fail("Should have thrown NoSuchBeanDefinitionException"); - } - catch (NoSuchBeanDefinitionException ex) { - assertThat(ex.getMessage(), containsString(format("No qualifying bean of type '%s'", targetType.getName()))); - } - } - - @Test - public void getBeanByTypeAmbiguityRaisesException() { - ApplicationContext context = new AnnotationConfigApplicationContext(TwoTestBeanConfig.class); - - try { - context.getBean(TestBean.class); - } - catch (NoSuchBeanDefinitionException ex) { - assertThat(ex.getMessage(), - allOf( - containsString("No qualifying bean of type '" + TestBean.class.getName() + "'"), - containsString("tb1"), - containsString("tb2") - ) - ); - } - } - - @Test - public void autowiringIsEnabledByDefault() { - ApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class); - assertThat(context.getBean(TestBean.class).name, equalTo("foo")); - } - - @Test - public void nullReturningBeanPostProcessor() { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.register(AutowiredConfig.class); - context.getBeanFactory().addBeanPostProcessor(new BeanPostProcessor() { - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) { - return (bean instanceof TestBean ? null : bean); - } - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) { - return bean; - } - }); - context.getBeanFactory().addBeanPostProcessor(new BeanPostProcessor() { - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) { - bean.getClass().getName(); - return bean; - } - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) { - bean.getClass().getName(); - return bean; - } - }); - context.refresh(); - } - @Configuration static class Config { diff --git a/spring-context/src/test/java/org/springframework/context/support/GenericApplicationContextTests.java b/spring-context/src/test/java/org/springframework/context/support/GenericApplicationContextTests.java index 6a42e68062..4c10913ebc 100644 --- a/spring-context/src/test/java/org/springframework/context/support/GenericApplicationContextTests.java +++ b/spring-context/src/test/java/org/springframework/context/support/GenericApplicationContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -19,8 +19,10 @@ package org.springframework.context.support; import org.junit.Test; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.util.ObjectUtils; import static org.junit.Assert.*; @@ -104,4 +106,140 @@ public class GenericApplicationContextTests { } } + @Test + public void individualBeans() { + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBean(BeanA.class); + context.registerBean(BeanB.class); + context.registerBean(BeanC.class); + context.refresh(); + + assertSame(context.getBean(BeanB.class), context.getBean(BeanA.class).b); + assertSame(context.getBean(BeanC.class), context.getBean(BeanA.class).c); + assertSame(context, context.getBean(BeanB.class).applicationContext); + } + + @Test + public void individualNamedBeans() { + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBean("a", BeanA.class); + context.registerBean("b", BeanB.class); + context.registerBean("c", BeanC.class); + context.refresh(); + + assertSame(context.getBean("b"), context.getBean("a", BeanA.class).b); + assertSame(context.getBean("c"), context.getBean("a", BeanA.class).c); + assertSame(context, context.getBean("b", BeanB.class).applicationContext); + } + + @Test + public void individualBeanWithSupplier() { + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBean(BeanA.class, + () -> new BeanA(context.getBean(BeanB.class), context.getBean(BeanC.class))); + context.registerBean(BeanB.class, BeanB::new); + context.registerBean(BeanC.class, BeanC::new); + context.refresh(); + + assertTrue(context.getBeanFactory().containsSingleton(BeanA.class.getName())); + assertSame(context.getBean(BeanB.class), context.getBean(BeanA.class).b); + assertSame(context.getBean(BeanC.class), context.getBean(BeanA.class).c); + assertSame(context, context.getBean(BeanB.class).applicationContext); + + assertArrayEquals(new String[] {BeanA.class.getName()}, + context.getDefaultListableBeanFactory().getDependentBeans(BeanB.class.getName())); + assertArrayEquals(new String[] {BeanA.class.getName()}, + context.getDefaultListableBeanFactory().getDependentBeans(BeanC.class.getName())); + } + + @Test + public void individualBeanWithSupplierAndCustomizer() { + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBean(BeanA.class, + () -> new BeanA(context.getBean(BeanB.class), context.getBean(BeanC.class)), + bd -> bd.setLazyInit(true)); + context.registerBean(BeanB.class, BeanB::new); + context.registerBean(BeanC.class, BeanC::new); + context.refresh(); + + assertFalse(context.getBeanFactory().containsSingleton(BeanA.class.getName())); + assertSame(context.getBean(BeanB.class), context.getBean(BeanA.class).b); + assertSame(context.getBean(BeanC.class), context.getBean(BeanA.class).c); + assertSame(context, context.getBean(BeanB.class).applicationContext); + } + + @Test + public void individualNamedBeanWithSupplier() { + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBean("a", BeanA.class, + () -> new BeanA(context.getBean(BeanB.class), context.getBean(BeanC.class))); + context.registerBean("b", BeanB.class, BeanB::new); + context.registerBean("c", BeanC.class, BeanC::new); + context.refresh(); + + assertTrue(context.getBeanFactory().containsSingleton("a")); + assertSame(context.getBean("b", BeanB.class), context.getBean(BeanA.class).b); + assertSame(context.getBean("c"), context.getBean("a", BeanA.class).c); + assertSame(context, context.getBean("b", BeanB.class).applicationContext); + } + + @Test + public void individualNamedBeanWithSupplierAndCustomizer() { + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBean("a", BeanA.class, + () -> new BeanA(context.getBean(BeanB.class), context.getBean(BeanC.class)), + bd -> bd.setLazyInit(true)); + context.registerBean("b", BeanB.class, BeanB::new); + context.registerBean("c", BeanC.class, BeanC::new); + context.refresh(); + + assertFalse(context.getBeanFactory().containsSingleton("a")); + assertSame(context.getBean("b", BeanB.class), context.getBean(BeanA.class).b); + assertSame(context.getBean("c"), context.getBean("a", BeanA.class).c); + assertSame(context, context.getBean("b", BeanB.class).applicationContext); + } + + @Test + public void individualBeanWithNullReturningSupplier() { + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBean("a", BeanA.class, () -> null); + context.registerBean("b", BeanB.class, BeanB::new); + context.registerBean("c", BeanC.class, BeanC::new); + context.refresh(); + + assertTrue(ObjectUtils.containsElement(context.getBeanNamesForType(BeanA.class), "a")); + assertTrue(ObjectUtils.containsElement(context.getBeanNamesForType(BeanB.class), "b")); + assertTrue(ObjectUtils.containsElement(context.getBeanNamesForType(BeanC.class), "c")); + assertTrue(context.getBeansOfType(BeanA.class).isEmpty()); + assertSame(context.getBean(BeanB.class), context.getBeansOfType(BeanB.class).values().iterator().next()); + assertSame(context.getBean(BeanC.class), context.getBeansOfType(BeanC.class).values().iterator().next()); + } + + + static class BeanA { + + BeanB b; + BeanC c; + + public BeanA(BeanB b, BeanC c) { + this.b = b; + this.c = c; + } + } + + static class BeanB implements ApplicationContextAware { + + ApplicationContext applicationContext; + + public BeanB() { + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + } + + static class BeanC {} + }