From 3ee6286eb5d2993c66fcb468ecc48d4c7afde0a4 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sun, 18 Dec 2016 21:05:45 +0100 Subject: [PATCH] Support for functional instance supplier callback at BeanDefinition level Issue: SPR-14832 --- .../AbstractAutowireCapableBeanFactory.java | 8 +++++ .../support/AbstractBeanDefinition.java | 27 +++++++++++++++ .../factory/support/RootBeanDefinition.java | 34 +++++++++++++++++++ .../GenericApplicationContextTests.java | 28 ++++++++++++++- 4 files changed, 96 insertions(+), 1 deletion(-) 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 c3c7183c75..dadb3fba83 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 @@ -38,6 +38,7 @@ import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.function.Supplier; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanWrapper; @@ -1036,6 +1037,13 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac "Bean class isn't public, and non-public access not allowed: " + beanClass.getName()); } + Supplier instanceSupplier = mbd.getInstanceSupplier(); + if (instanceSupplier != null) { + BeanWrapper bw = new BeanWrapperImpl(instanceSupplier.get()); + initBeanWrapper(bw); + return bw; + } + if (mbd.getFactoryMethodName() != null) { return instantiateUsingFactoryMethod(beanName, mbd, args); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java index 7b2e264787..9c42e31b14 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java @@ -22,6 +22,7 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; import org.springframework.beans.BeanMetadataAttributeAccessor; import org.springframework.beans.MutablePropertyValues; @@ -165,6 +166,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess private MethodOverrides methodOverrides = new MethodOverrides(); + private Supplier instanceSupplier; + private String factoryBeanName; private String factoryMethodName; @@ -234,6 +237,7 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess setPrimary(originalAbd.isPrimary()); setNonPublicAccessAllowed(originalAbd.isNonPublicAccessAllowed()); setLenientConstructorResolution(originalAbd.isLenientConstructorResolution()); + setInstanceSupplier(originalAbd.getInstanceSupplier()); setInitMethodName(originalAbd.getInitMethodName()); setEnforceInitMethod(originalAbd.isEnforceInitMethod()); setDestroyMethodName(originalAbd.getDestroyMethodName()); @@ -298,6 +302,7 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess setDependsOn(otherAbd.getDependsOn()); setNonPublicAccessAllowed(otherAbd.isNonPublicAccessAllowed()); setLenientConstructorResolution(otherAbd.isLenientConstructorResolution()); + setInstanceSupplier(otherAbd.getInstanceSupplier()); if (StringUtils.hasLength(otherAbd.getInitMethodName())) { setInitMethodName(otherAbd.getInitMethodName()); setEnforceInitMethod(otherAbd.isEnforceInitMethod()); @@ -738,6 +743,28 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess } + /** + * Specify a callback for creating an instance of the bean, + * as an alternative to a declaratively specified factory method. + *

If such a callback is set, it will override any other constructor + * or factory method metadata. However, bean property population and + * potential annotation-driven injection will still apply as usual. + * @since 5.0 + * @see #setConstructorArgumentValues(ConstructorArgumentValues) + * @see #setPropertyValues(MutablePropertyValues) + */ + public void setInstanceSupplier(Supplier instanceSupplier) { + this.instanceSupplier = instanceSupplier; + } + + /** + * Return a callback for creating an instance of the bean, if any. + * @since 5.0 + */ + public Supplier getInstanceSupplier() { + return this.instanceSupplier; + } + @Override public void setFactoryBeanName(String factoryBeanName) { this.factoryBeanName = factoryBeanName; 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 f04ea65166..64908bd340 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 @@ -22,6 +22,7 @@ import java.lang.reflect.Member; import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; +import java.util.function.Supplier; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.BeanDefinition; @@ -116,12 +117,45 @@ public class RootBeanDefinition extends AbstractBeanDefinition { /** * Create a new RootBeanDefinition for a singleton. * @param beanClass the class of the bean to instantiate + * @see #setBeanClass */ public RootBeanDefinition(Class beanClass) { super(); setBeanClass(beanClass); } + /** + * Create a new RootBeanDefinition for a singleton bean, constructing each instance + * through calling the given supplier (possibly a lambda or method reference). + * @param beanClass the class of the bean to instantiate + * @param instanceSupplier the supplier to construct a bean instance, + * as an alternative to a declaratively specified factory method + * @since 5.0 + * @see #setInstanceSupplier(Supplier) + */ + public RootBeanDefinition(Class beanClass, Supplier instanceSupplier) { + super(); + setBeanClass(beanClass); + setInstanceSupplier(instanceSupplier); + } + + /** + * Create a new RootBeanDefinition for a scoped bean, constructing each instance + * through calling the given supplier (possibly a lambda or method reference). + * @param beanClass the class of the bean to instantiate + * @param scope the name of the corresponding scope + * @param instanceSupplier the supplier to construct a bean instance, + * as an alternative to a declaratively specified factory method + * @since 5.0 + * @see #setInstanceSupplier(Supplier) + */ + public RootBeanDefinition(Class beanClass, String scope, Supplier instanceSupplier) { + super(); + setBeanClass(beanClass); + setScope(scope); + setInstanceSupplier(instanceSupplier); + } + /** * Create a new RootBeanDefinition for a singleton, * using the given autowire mode. 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 e215830d46..0e1512df9c 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-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -43,6 +43,7 @@ public class GenericApplicationContextTests { ac.registerBeanDefinition("testBean", new RootBeanDefinition(String.class)); ac.refresh(); + assertEquals("", ac.getBean("testBean")); assertSame(ac.getBean("testBean"), ac.getBean(String.class)); assertSame(ac.getBean("testBean"), ac.getBean(CharSequence.class)); @@ -55,6 +56,31 @@ public class GenericApplicationContextTests { } } + @Test + public void withSingletonSupplier() { + GenericApplicationContext ac = new GenericApplicationContext(); + ac.registerBeanDefinition("testBean", new RootBeanDefinition(String.class, ac::toString)); + ac.refresh(); + + assertSame(ac.getBean("testBean"), ac.getBean("testBean")); + assertSame(ac.getBean("testBean"), ac.getBean(String.class)); + assertSame(ac.getBean("testBean"), ac.getBean(CharSequence.class)); + assertEquals(ac.toString(), ac.getBean("testBean")); + } + + @Test + public void withScopedSupplier() { + GenericApplicationContext ac = new GenericApplicationContext(); + ac.registerBeanDefinition("testBean", + new RootBeanDefinition(String.class, RootBeanDefinition.SCOPE_PROTOTYPE, ac::toString)); + ac.refresh(); + + assertNotSame(ac.getBean("testBean"), ac.getBean("testBean")); + assertEquals(ac.getBean("testBean"), ac.getBean(String.class)); + assertEquals(ac.getBean("testBean"), ac.getBean(CharSequence.class)); + assertEquals(ac.toString(), ac.getBean("testBean")); + } + @Test public void accessAfterClosing() { GenericApplicationContext ac = new GenericApplicationContext();