Skip to content

Commit 343bb2f

Browse files
committed
Support for SmartObjectFactory injection points with programmatic optionality and lenient not-unique handling
Issue: SPR-13943
1 parent b79e8a5 commit 343bb2f

File tree

4 files changed

+217
-3
lines changed

4 files changed

+217
-3
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.beans.factory;
18+
19+
import org.springframework.beans.BeansException;
20+
21+
/**
22+
* A variant of {@link ObjectFactory} designed specifically for injection points,
23+
* allowing for programmatic optionality and lenient not-unique handling.
24+
*
25+
* @author Juergen Hoeller
26+
* @since 4.3
27+
*/
28+
public interface SmartObjectFactory<T> extends ObjectFactory<T> {
29+
30+
/**
31+
* Return an instance (possibly shared or independent)
32+
* of the object managed by this factory.
33+
* @return an instance of the bean, or {@code null} if not available
34+
* @throws BeansException in case of creation errors
35+
* @see #getObject()
36+
*/
37+
T getIfAvailable() throws BeansException;
38+
39+
/**
40+
* Return an instance (possibly shared or independent)
41+
* of the object managed by this factory.
42+
* @return an instance of the bean, or {@code null} if not available or
43+
* not unique (i.e. multiple candidates found with none marked as primary)
44+
* @throws BeansException in case of creation errors
45+
* @see #getObject()
46+
*/
47+
T getIfUnique() throws BeansException;
48+
49+
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323
import java.lang.reflect.Field;
2424
import java.lang.reflect.ParameterizedType;
2525
import java.lang.reflect.Type;
26+
import java.util.Map;
2627

28+
import org.springframework.beans.BeansException;
29+
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
2730
import org.springframework.core.GenericCollectionTypeResolver;
2831
import org.springframework.core.GenericTypeResolver;
2932
import org.springframework.core.MethodParameter;
@@ -180,6 +183,23 @@ public boolean isEager() {
180183
return this.eager;
181184
}
182185

186+
/**
187+
* Resolve the specified not-unique scenario: by default,
188+
* throwing a {@link NoUniqueBeanDefinitionException}.
189+
* <p>Subclasses may override this to select one of the instances or
190+
* to opt out with no result at all through returning {@code null}.
191+
* @param type the requested bean type
192+
* @param matchingBeans a map of bean names and corresponding bean
193+
* instances which have been pre-selected for the given type
194+
* (qualifiers etc already applied)
195+
* @return a bean instance to proceed with, or {@code null} for none
196+
* @throws BeansException in case of the not-unique scenario being fatal
197+
* @since 4.3
198+
*/
199+
public Object resolveNotUnique(Class<?> type, Map<String, Object> matchingBeans) throws BeansException {
200+
throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet());
201+
}
202+
183203

184204
/**
185205
* Increase this descriptor's nesting level.

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

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import org.springframework.beans.factory.ObjectFactory;
6060
import org.springframework.beans.factory.SmartFactoryBean;
6161
import org.springframework.beans.factory.SmartInitializingSingleton;
62+
import org.springframework.beans.factory.SmartObjectFactory;
6263
import org.springframework.beans.factory.config.BeanDefinition;
6364
import org.springframework.beans.factory.config.BeanDefinitionHolder;
6465
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
@@ -1006,7 +1007,8 @@ public Object resolveDependency(DependencyDescriptor descriptor, String beanName
10061007
if (descriptor.getDependencyType().equals(javaUtilOptionalClass)) {
10071008
return new OptionalDependencyFactory().createOptionalDependency(descriptor, beanName);
10081009
}
1009-
else if (ObjectFactory.class == descriptor.getDependencyType()) {
1010+
else if (ObjectFactory.class == descriptor.getDependencyType() ||
1011+
SmartObjectFactory.class == descriptor.getDependencyType()) {
10101012
return new DependencyObjectFactory(descriptor, beanName);
10111013
}
10121014
else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
@@ -1054,7 +1056,7 @@ public Object doResolveDependency(DependencyDescriptor descriptor, String beanNa
10541056
String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor);
10551057
if (primaryBeanName == null) {
10561058
if (multipleBeans == NOT_MULTIPLE_BEANS || descriptor.isRequired()) {
1057-
throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet());
1059+
return descriptor.resolveNotUnique(type, matchingBeans);
10581060
}
10591061
else {
10601062
// In case of an optional Collection/Map, silently ignore a non-unique case:
@@ -1474,7 +1476,7 @@ public boolean isRequired() {
14741476
/**
14751477
* Serializable ObjectFactory for lazy resolution of a dependency.
14761478
*/
1477-
private class DependencyObjectFactory implements ObjectFactory<Object>, Serializable {
1479+
private class DependencyObjectFactory implements SmartObjectFactory<Object>, Serializable {
14781480

14791481
private final DependencyDescriptor descriptor;
14801482

@@ -1498,6 +1500,42 @@ public Object getObject() throws BeansException {
14981500
return doResolveDependency(this.descriptor, this.beanName, null, null);
14991501
}
15001502
}
1503+
1504+
@Override
1505+
public Object getIfAvailable() throws BeansException {
1506+
if (this.optional) {
1507+
return new OptionalDependencyFactory().createOptionalDependency(this.descriptor, this.beanName);
1508+
}
1509+
else {
1510+
DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) {
1511+
@Override
1512+
public boolean isRequired() {
1513+
return false;
1514+
}
1515+
};
1516+
return doResolveDependency(descriptorToUse, this.beanName, null, null);
1517+
}
1518+
}
1519+
1520+
@Override
1521+
public Object getIfUnique() throws BeansException {
1522+
DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) {
1523+
@Override
1524+
public boolean isRequired() {
1525+
return false;
1526+
}
1527+
@Override
1528+
public Object resolveNotUnique(Class<?> type, Map<String, Object> matchingBeans) {
1529+
return null;
1530+
}
1531+
};
1532+
if (this.optional) {
1533+
return new OptionalDependencyFactory().createOptionalDependency(descriptorToUse, this.beanName);
1534+
}
1535+
else {
1536+
return doResolveDependency(descriptorToUse, this.beanName, null, null);
1537+
}
1538+
}
15011539
}
15021540

15031541

spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@
4141
import org.springframework.beans.factory.BeanCreationException;
4242
import org.springframework.beans.factory.BeanFactory;
4343
import org.springframework.beans.factory.FactoryBean;
44+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
45+
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
4446
import org.springframework.beans.factory.ObjectFactory;
47+
import org.springframework.beans.factory.SmartObjectFactory;
4548
import org.springframework.beans.factory.UnsatisfiedDependencyException;
4649
import org.springframework.beans.factory.config.BeanDefinition;
4750
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -1044,6 +1047,91 @@ public void testObjectFactorySerialization() throws Exception {
10441047
bf.destroySingletons();
10451048
}
10461049

1050+
@Test
1051+
public void testSmartObjectFactoryInjection() {
1052+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
1053+
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
1054+
bpp.setBeanFactory(bf);
1055+
bf.addBeanPostProcessor(bpp);
1056+
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SmartObjectFactoryInjectionBean.class));
1057+
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
1058+
1059+
SmartObjectFactoryInjectionBean bean = (SmartObjectFactoryInjectionBean) bf.getBean("annotatedBean");
1060+
assertSame(bf.getBean("testBean"), bean.getTestBean());
1061+
assertSame(bf.getBean("testBean"), bean.getOptionalTestBean());
1062+
assertSame(bf.getBean("testBean"), bean.getUniqueTestBean());
1063+
bf.destroySingletons();
1064+
}
1065+
1066+
@Test
1067+
public void testSmartObjectFactoryInjectionWithTargetNotAvailable() {
1068+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
1069+
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
1070+
bpp.setBeanFactory(bf);
1071+
bf.addBeanPostProcessor(bpp);
1072+
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SmartObjectFactoryInjectionBean.class));
1073+
1074+
SmartObjectFactoryInjectionBean bean = (SmartObjectFactoryInjectionBean) bf.getBean("annotatedBean");
1075+
try {
1076+
bean.getTestBean();
1077+
fail("Should have thrown NoSuchBeanDefinitionException");
1078+
}
1079+
catch (NoSuchBeanDefinitionException ex) {
1080+
// expected
1081+
}
1082+
assertNull(bean.getOptionalTestBean());
1083+
assertNull(bean.getUniqueTestBean());
1084+
bf.destroySingletons();
1085+
}
1086+
1087+
@Test
1088+
public void testSmartObjectFactoryInjectionWithTargetNotUnique() {
1089+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
1090+
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
1091+
bpp.setBeanFactory(bf);
1092+
bf.addBeanPostProcessor(bpp);
1093+
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SmartObjectFactoryInjectionBean.class));
1094+
bf.registerBeanDefinition("testBean1", new RootBeanDefinition(TestBean.class));
1095+
bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class));
1096+
1097+
SmartObjectFactoryInjectionBean bean = (SmartObjectFactoryInjectionBean) bf.getBean("annotatedBean");
1098+
try {
1099+
bean.getTestBean();
1100+
fail("Should have thrown NoUniqueBeanDefinitionException");
1101+
}
1102+
catch (NoUniqueBeanDefinitionException ex) {
1103+
// expected
1104+
}
1105+
try {
1106+
bean.getOptionalTestBean();
1107+
fail("Should have thrown NoUniqueBeanDefinitionException");
1108+
}
1109+
catch (NoUniqueBeanDefinitionException ex) {
1110+
// expected
1111+
}
1112+
assertNull(bean.getUniqueTestBean());
1113+
bf.destroySingletons();
1114+
}
1115+
1116+
@Test
1117+
public void testSmartObjectFactoryInjectionWithTargetPrimary() {
1118+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
1119+
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
1120+
bpp.setBeanFactory(bf);
1121+
bf.addBeanPostProcessor(bpp);
1122+
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SmartObjectFactoryInjectionBean.class));
1123+
RootBeanDefinition tb1 = new RootBeanDefinition(TestBean.class);
1124+
tb1.setPrimary(true);
1125+
bf.registerBeanDefinition("testBean1", tb1);
1126+
bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class));
1127+
1128+
SmartObjectFactoryInjectionBean bean = (SmartObjectFactoryInjectionBean) bf.getBean("annotatedBean");
1129+
assertSame(bf.getBean("testBean1"), bean.getTestBean());
1130+
assertSame(bf.getBean("testBean1"), bean.getOptionalTestBean());
1131+
assertSame(bf.getBean("testBean1"), bean.getUniqueTestBean());
1132+
bf.destroySingletons();
1133+
}
1134+
10471135
@Test
10481136
public void testCustomAnnotationRequiredFieldResourceInjection() {
10491137
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
@@ -2461,6 +2549,25 @@ public TestBean getTestBean() {
24612549
}
24622550

24632551

2552+
public static class SmartObjectFactoryInjectionBean {
2553+
2554+
@Autowired
2555+
private SmartObjectFactory<TestBean> testBeanFactory;
2556+
2557+
public TestBean getTestBean() {
2558+
return this.testBeanFactory.getObject();
2559+
}
2560+
2561+
public TestBean getOptionalTestBean() {
2562+
return this.testBeanFactory.getIfAvailable();
2563+
}
2564+
2565+
public TestBean getUniqueTestBean() {
2566+
return this.testBeanFactory.getIfUnique();
2567+
}
2568+
}
2569+
2570+
24642571
public static class CustomAnnotationRequiredFieldResourceInjectionBean {
24652572

24662573
@MyAutowired(optional = false)

0 commit comments

Comments
 (0)