From fd5dbddac4c41551446b3393780e078885cc484b Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 12 Aug 2014 22:07:12 +0200 Subject: [PATCH] BeanFactory supports bean creation arguments for by-type lookup as well Issue: SPR-11235 --- .../beans/factory/BeanFactory.java | 23 +++- .../support/DefaultListableBeanFactory.java | 15 ++- .../support/StaticListableBeanFactory.java | 13 +- .../DefaultListableBeanFactoryTests.java | 127 +++++++++++++++++- .../support/AbstractApplicationContext.java | 6 + .../jndi/support/SimpleJndiBeanFactory.java | 11 +- .../setup/StubWebApplicationContext.java | 7 +- 7 files changed, 187 insertions(+), 15 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java index 8c73a16fd7..a2a1648eb7 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -181,6 +181,27 @@ public interface BeanFactory { */ Object getBean(String name, Object... args) throws BeansException; + /** + * Return an instance, which may be shared or independent, of the specified bean. + *

Allows for specifying explicit constructor arguments / factory method arguments, + * overriding the specified default arguments (if any) in the bean definition. + * @param requiredType type the bean must match; can be an interface or superclass. + * {@code null} is disallowed. + *

This method goes into {@link ListableBeanFactory} by-type lookup territory + * but may also be translated into a conventional by-name lookup based on the name + * of the given type. For more extensive retrieval operations across sets of beans, + * use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}. + * @param args arguments to use if creating a prototype using explicit arguments to a + * static factory method. It is invalid to use a non-null args value in any other case. + * @return an instance of the bean + * @throws NoSuchBeanDefinitionException if there is no such bean definition + * @throws BeanDefinitionStoreException if arguments have been given but + * the affected bean isn't a prototype + * @throws BeansException if the bean could not be created + * @since 4.1 + */ + T getBean(Class requiredType, Object... args) throws BeansException; + /** * Does this bean factory contain a bean definition or externally registered singleton * instance with the given name? diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 2888fa65c5..8685cd4055 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -295,6 +295,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @Override public T getBean(Class requiredType) throws BeansException { + return getBean(requiredType, (Object[]) null); + } + + @Override + public T getBean(Class requiredType, Object... args) throws BeansException { Assert.notNull(requiredType, "Required type must not be null"); String[] beanNames = getBeanNamesForType(requiredType); if (beanNames.length > 1) { @@ -309,25 +314,25 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } } if (beanNames.length == 1) { - return getBean(beanNames[0], requiredType); + return getBean(beanNames[0], requiredType, args); } else if (beanNames.length > 1) { Map candidates = new HashMap(); for (String beanName : beanNames) { - candidates.put(beanName, getBean(beanName, requiredType)); + candidates.put(beanName, getBean(beanName, requiredType, args)); } String primaryCandidate = determinePrimaryCandidate(candidates, requiredType); if (primaryCandidate != null) { - return getBean(primaryCandidate, requiredType); + return getBean(primaryCandidate, requiredType, args); } String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType); if (priorityCandidate != null) { - return getBean(priorityCandidate, requiredType); + return getBean(priorityCandidate, requiredType, args); } throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet()); } else if (getParentBeanFactory() != null) { - return getParentBeanFactory().getBean(requiredType); + return getParentBeanFactory().getBean(requiredType, args); } else { throw new NoSuchBeanDefinitionException(requiredType); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java index 5ba6f39399..80ece144f6 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -133,11 +133,20 @@ public class StaticListableBeanFactory implements ListableBeanFactory { public Object getBean(String name, Object... args) throws BeansException { if (args != null) { throw new UnsupportedOperationException( - "StaticListableBeanFactory does not support explicit bean creation arguments)"); + "StaticListableBeanFactory does not support explicit bean creation arguments"); } return getBean(name); } + @Override + public T getBean(Class requiredType, Object... args) throws BeansException { + if (args != null) { + throw new UnsupportedOperationException( + "StaticListableBeanFactory does not support explicit bean creation arguments"); + } + return getBean(requiredType); + } + @Override public boolean containsBean(String name) { return this.beans.containsKey(name); 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 b41a24ec4a..19770a646b 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -1343,6 +1343,16 @@ public class DefaultListableBeanFactoryTests { lbf.getBean(TestBean.class); } + @Test + public void testGetBeanByTypeDefinedInParent() { + DefaultListableBeanFactory parent = new DefaultListableBeanFactory(); + RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class); + parent.registerBeanDefinition("bd1", bd1); + DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(parent); + TestBean bean = lbf.getBean(TestBean.class); + assertThat(bean.getBeanName(), equalTo("bd1")); + } + @Test(expected=NoUniqueBeanDefinitionException.class) public void testGetBeanByTypeWithAmbiguity() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); @@ -1449,6 +1459,95 @@ public class DefaultListableBeanFactoryTests { } } + @Test(expected = NoSuchBeanDefinitionException.class) + public void testGetBeanByTypeInstanceWithNoneFound() { + DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); + lbf.getBean(ConstructorDependency.class, 42); + } + + @Test + public void testGetBeanByTypeInstanceDefinedInParent() { + DefaultListableBeanFactory parent = new DefaultListableBeanFactory(); + RootBeanDefinition bd1 = createConstructorDependencyBeanDefinition(99); + parent.registerBeanDefinition("bd1", bd1); + DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(parent); + ConstructorDependency bean = lbf.getBean(ConstructorDependency.class, 42); + assertThat(bean.beanName, equalTo("bd1")); + assertThat(bean.spouseAge, equalTo(42)); + } + + @Test + public void testGetBeanByTypeInstanceWithAmbiguity() { + DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); + RootBeanDefinition bd1 = createConstructorDependencyBeanDefinition(99); + RootBeanDefinition bd2 = new RootBeanDefinition(ConstructorDependency.class); + bd2.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bd2.getConstructorArgumentValues().addGenericArgumentValue("43"); + + lbf.registerBeanDefinition("bd1", bd1); + lbf.registerBeanDefinition("bd2", bd2); + + thrown.expect(NoUniqueBeanDefinitionException.class); + lbf.getBean(ConstructorDependency.class, 42); + } + + @Test + public void testGetBeanByTypeInstanceWithPrimary() throws Exception { + DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); + RootBeanDefinition bd1 = createConstructorDependencyBeanDefinition(99); + RootBeanDefinition bd2 = createConstructorDependencyBeanDefinition(43); + bd2.setPrimary(true); + lbf.registerBeanDefinition("bd1", bd1); + lbf.registerBeanDefinition("bd2", bd2); + ConstructorDependency bean = lbf.getBean(ConstructorDependency.class, 42); + assertThat(bean.beanName, equalTo("bd2")); + assertThat(bean.spouseAge, equalTo(42)); + } + + @Test + public void testGetBeanByTypeInstanceWithMultiplePrimary() throws Exception { + DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); + RootBeanDefinition bd1 = createConstructorDependencyBeanDefinition(99); + RootBeanDefinition bd2 = createConstructorDependencyBeanDefinition(43); + bd1.setPrimary(true); + bd2.setPrimary(true); + + lbf.registerBeanDefinition("bd1", bd1); + lbf.registerBeanDefinition("bd2", bd2); + thrown.expect(NoUniqueBeanDefinitionException.class); + thrown.expectMessage(containsString("more than one 'primary'")); + lbf.getBean(ConstructorDependency.class, 42); + } + + @Test + public void testGetBeanByTypeInstanceFiltersOutNonAutowireCandidates() { + DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); + RootBeanDefinition bd1 = createConstructorDependencyBeanDefinition(99); + RootBeanDefinition bd2 = createConstructorDependencyBeanDefinition(43); + RootBeanDefinition na1 = createConstructorDependencyBeanDefinition(21); + na1.setAutowireCandidate(false); + + lbf.registerBeanDefinition("bd1", bd1); + lbf.registerBeanDefinition("na1", na1); + ConstructorDependency actual = lbf.getBean(ConstructorDependency.class, 42); // na1 was filtered + assertThat(actual.beanName, equalTo("bd1")); + + lbf.registerBeanDefinition("bd2", bd2); + try { + lbf.getBean(TestBean.class, 67); + fail("Should have thrown NoSuchBeanDefinitionException"); + } catch (NoSuchBeanDefinitionException ex) { + // expected + } + } + + private RootBeanDefinition createConstructorDependencyBeanDefinition(int age) { + RootBeanDefinition bd1 = new RootBeanDefinition(ConstructorDependency.class); + bd1.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bd1.getConstructorArgumentValues().addGenericArgumentValue(String.valueOf(age)); + return bd1; + } + @Test public void testAutowireBeanByType() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); @@ -2525,18 +2624,31 @@ public class DefaultListableBeanFactoryTests { } - public static class ConstructorDependency { + public static class ConstructorDependency implements BeanNameAware { public TestBean spouse; + public int spouseAge; + + private String beanName; + public ConstructorDependency(TestBean spouse) { this.spouse = spouse; } + public ConstructorDependency(int spouseAge) { + this.spouseAge = spouseAge; + } + @SuppressWarnings("unused") private ConstructorDependency(TestBean spouse, TestBean otherSpouse) { throw new IllegalArgumentException("Should never be called"); } + + @Override + public void setBeanName(String name) { + this.beanName = name; + } } @@ -2879,6 +2991,7 @@ public class DefaultListableBeanFactoryTests { } } + @SuppressWarnings("unused") private static class KnowsIfInstantiated { @@ -2898,11 +3011,16 @@ public class DefaultListableBeanFactoryTests { } + @Priority(5) - private static class HighPriorityTestBean extends TestBean {} + private static class HighPriorityTestBean extends TestBean { + } + @Priority(500) - private static class LowPriorityTestBean extends TestBean {} + private static class LowPriorityTestBean extends TestBean { + } + private static class NullTestBeanFactoryBean implements FactoryBean { @@ -2922,5 +3040,4 @@ public class DefaultListableBeanFactoryTests { } } - } diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 0c83f6a3fa..75e5e3d873 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -974,6 +974,12 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader return getBeanFactory().getBean(name, args); } + @Override + public T getBean(Class requiredType, Object... args) throws BeansException { + assertBeanFactoryActive(); + return getBeanFactory().getBean(requiredType, args); + } + @Override public boolean containsBean(String name) { return getBeanFactory().containsBean(name); diff --git a/spring-context/src/main/java/org/springframework/jndi/support/SimpleJndiBeanFactory.java b/spring-context/src/main/java/org/springframework/jndi/support/SimpleJndiBeanFactory.java index 650164768d..4d2f8ef48f 100644 --- a/spring-context/src/main/java/org/springframework/jndi/support/SimpleJndiBeanFactory.java +++ b/spring-context/src/main/java/org/springframework/jndi/support/SimpleJndiBeanFactory.java @@ -134,11 +134,20 @@ public class SimpleJndiBeanFactory extends JndiLocatorSupport implements BeanFac public Object getBean(String name, Object... args) throws BeansException { if (args != null) { throw new UnsupportedOperationException( - "SimpleJndiBeanFactory does not support explicit bean creation arguments)"); + "SimpleJndiBeanFactory does not support explicit bean creation arguments"); } return getBean(name); } + @Override + public T getBean(Class requiredType, Object... args) throws BeansException { + if (args != null) { + throw new UnsupportedOperationException( + "SimpleJndiBeanFactory does not support explicit bean creation arguments"); + } + return getBean(requiredType); + } + @Override public boolean containsBean(String name) { if (this.singletonObjects.containsKey(name) || this.resourceTypes.containsKey(name)) { diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java index d2db9a443a..ea73248eb7 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -167,6 +167,11 @@ class StubWebApplicationContext implements WebApplicationContext { return this.beanFactory.getBean(name, args); } + @Override + public T getBean(Class requiredType, Object... args) throws BeansException { + return this.beanFactory.getBean(requiredType, args); + } + @Override public boolean containsBean(String name) { return this.beanFactory.containsBean(name);