Skip to content

Commit 6bed180

Browse files
committed
Fixed detection of qualifier annotations on scoped-proxy factory methods
Issue: SPR-11116
1 parent dfed8af commit 6bed180

File tree

3 files changed

+92
-14
lines changed

3 files changed

+92
-14
lines changed

spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,17 @@ public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder defini
5050

5151
String originalBeanName = definition.getBeanName();
5252
BeanDefinition targetDefinition = definition.getBeanDefinition();
53+
String targetBeanName = getTargetBeanName(originalBeanName);
5354

5455
// Create a scoped proxy definition for the original bean name,
5556
// "hiding" the target bean in an internal target definition.
5657
RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
58+
proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
5759
proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
5860
proxyDefinition.setSource(definition.getSource());
5961
proxyDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
6062

61-
String targetBeanName = getTargetBeanName(originalBeanName);
6263
proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
63-
6464
if (proxyTargetClass) {
6565
targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
6666
// ScopedProxyFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here.

spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
import org.springframework.beans.TypeConverter;
2727
import org.springframework.beans.factory.BeanFactory;
2828
import org.springframework.beans.factory.BeanFactoryAware;
29+
import org.springframework.beans.factory.config.BeanDefinition;
2930
import org.springframework.beans.factory.config.BeanDefinitionHolder;
31+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
3032
import org.springframework.beans.factory.config.DependencyDescriptor;
3133
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
3234
import org.springframework.beans.factory.support.AutowireCandidateResolver;
@@ -227,18 +229,22 @@ protected boolean checkQualifier(
227229

228230
Class<? extends Annotation> type = annotation.annotationType();
229231
RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition();
232+
230233
AutowireCandidateQualifier qualifier = bd.getQualifier(type.getName());
231234
if (qualifier == null) {
232235
qualifier = bd.getQualifier(ClassUtils.getShortName(type));
233236
}
234237
if (qualifier == null) {
235-
Annotation targetAnnotation = null;
236-
Method resolvedFactoryMethod = bd.getResolvedFactoryMethod();
237-
if (resolvedFactoryMethod != null) {
238-
targetAnnotation = AnnotationUtils.getAnnotation(resolvedFactoryMethod, type);
238+
// First, check annotation on factory method, if applicable
239+
Annotation targetAnnotation = getFactoryMethodAnnotation(bd, type);
240+
if (targetAnnotation == null) {
241+
RootBeanDefinition dbd = getResolvedDecoratedDefinition(bd);
242+
if (dbd != null) {
243+
targetAnnotation = getFactoryMethodAnnotation(dbd, type);
244+
}
239245
}
240246
if (targetAnnotation == null) {
241-
// look for matching annotation on the target class
247+
// Look for matching annotation on the target class
242248
if (this.beanFactory != null) {
243249
Class<?> beanType = this.beanFactory.getType(bdHolder.getBeanName());
244250
if (beanType != null) {
@@ -253,30 +259,31 @@ protected boolean checkQualifier(
253259
return true;
254260
}
255261
}
262+
256263
Map<String, Object> attributes = AnnotationUtils.getAnnotationAttributes(annotation);
257264
if (attributes.isEmpty() && qualifier == null) {
258-
// if no attributes, the qualifier must be present
265+
// If no attributes, the qualifier must be present
259266
return false;
260267
}
261268
for (Map.Entry<String, Object> entry : attributes.entrySet()) {
262269
String attributeName = entry.getKey();
263270
Object expectedValue = entry.getValue();
264271
Object actualValue = null;
265-
// check qualifier first
272+
// Check qualifier first
266273
if (qualifier != null) {
267274
actualValue = qualifier.getAttribute(attributeName);
268275
}
269276
if (actualValue == null) {
270-
// fall back on bean definition attribute
277+
// Fall back on bean definition attribute
271278
actualValue = bd.getAttribute(attributeName);
272279
}
273280
if (actualValue == null && attributeName.equals(AutowireCandidateQualifier.VALUE_KEY) &&
274281
expectedValue instanceof String && bdHolder.matchesName((String) expectedValue)) {
275-
// fall back on bean name (or alias) match
282+
// Fall back on bean name (or alias) match
276283
continue;
277284
}
278285
if (actualValue == null && qualifier != null) {
279-
// fall back on default, but only if the qualifier is present
286+
// Fall back on default, but only if the qualifier is present
280287
actualValue = AnnotationUtils.getDefaultValue(annotation, attributeName);
281288
}
282289
if (actualValue != null) {
@@ -289,6 +296,25 @@ protected boolean checkQualifier(
289296
return true;
290297
}
291298

299+
protected RootBeanDefinition getResolvedDecoratedDefinition(RootBeanDefinition rbd) {
300+
BeanDefinitionHolder decDef = rbd.getDecoratedDefinition();
301+
if (decDef != null && this.beanFactory instanceof ConfigurableListableBeanFactory) {
302+
ConfigurableListableBeanFactory clbf = (ConfigurableListableBeanFactory) this.beanFactory;
303+
if (clbf.containsBeanDefinition(decDef.getBeanName())) {
304+
BeanDefinition dbd = clbf.getMergedBeanDefinition(decDef.getBeanName());
305+
if (dbd instanceof RootBeanDefinition) {
306+
return (RootBeanDefinition) dbd;
307+
}
308+
}
309+
}
310+
return null;
311+
}
312+
313+
protected Annotation getFactoryMethodAnnotation(RootBeanDefinition bd, Class<? extends Annotation> type) {
314+
Method resolvedFactoryMethod = bd.getResolvedFactoryMethod();
315+
return (resolvedFactoryMethod != null ? AnnotationUtils.getAnnotation(resolvedFactoryMethod, type) : null);
316+
}
317+
292318

293319
/**
294320
* Determine whether the given dependency carries a value annotation.

spring-context/src/test/java/org/springframework/context/annotation/configuration/BeanMethodQualificationTests.java

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,17 @@
2020
import java.lang.annotation.RetentionPolicy;
2121

2222
import org.junit.Test;
23-
import org.springframework.tests.sample.beans.TestBean;
2423

2524
import org.springframework.beans.factory.annotation.Autowired;
2625
import org.springframework.beans.factory.annotation.Qualifier;
2726
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
2827
import org.springframework.context.annotation.Bean;
2928
import org.springframework.context.annotation.Configuration;
3029
import org.springframework.context.annotation.Lazy;
30+
import org.springframework.context.annotation.Scope;
31+
import org.springframework.context.annotation.ScopedProxyMode;
3132
import org.springframework.stereotype.Component;
33+
import org.springframework.tests.sample.beans.TestBean;
3234

3335
import static org.hamcrest.CoreMatchers.*;
3436
import static org.junit.Assert.*;
@@ -51,6 +53,24 @@ public void testStandard() {
5153
assertThat(pojo.testBean.getName(), equalTo("interesting"));
5254
}
5355

56+
@Test
57+
public void testScoped() {
58+
AnnotationConfigApplicationContext ctx =
59+
new AnnotationConfigApplicationContext(ScopedConfig.class, StandardPojo.class);
60+
assertFalse(ctx.getBeanFactory().containsSingleton("testBean1"));
61+
StandardPojo pojo = ctx.getBean(StandardPojo.class);
62+
assertThat(pojo.testBean.getName(), equalTo("interesting"));
63+
}
64+
65+
@Test
66+
public void testScopedProxy() {
67+
AnnotationConfigApplicationContext ctx =
68+
new AnnotationConfigApplicationContext(ScopedProxyConfig.class, StandardPojo.class);
69+
assertTrue(ctx.getBeanFactory().containsSingleton("testBean1")); // a shared scoped proxy
70+
StandardPojo pojo = ctx.getBean(StandardPojo.class);
71+
assertThat(pojo.testBean.getName(), equalTo("interesting"));
72+
}
73+
5474
@Test
5575
public void testCustom() {
5676
AnnotationConfigApplicationContext ctx =
@@ -63,7 +83,8 @@ public void testCustom() {
6383

6484
@Configuration
6585
static class StandardConfig {
66-
@Bean @Lazy @Qualifier("interesting")
86+
87+
@Bean @Qualifier("interesting") @Lazy
6788
public TestBean testBean1() {
6889
return new TestBean("interesting");
6990
}
@@ -74,13 +95,43 @@ public TestBean testBean2() {
7495
}
7596
}
7697

98+
@Configuration
99+
static class ScopedConfig {
100+
101+
@Bean @Qualifier("interesting") @Scope("prototype")
102+
public TestBean testBean1() {
103+
return new TestBean("interesting");
104+
}
105+
106+
@Bean @Qualifier("boring") @Scope("prototype")
107+
public TestBean testBean2() {
108+
return new TestBean("boring");
109+
}
110+
}
111+
112+
@Configuration
113+
static class ScopedProxyConfig {
114+
115+
@Bean @Qualifier("interesting") @Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
116+
public TestBean testBean1() {
117+
return new TestBean("interesting");
118+
}
119+
120+
@Bean @Qualifier("boring") @Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
121+
public TestBean testBean2() {
122+
return new TestBean("boring");
123+
}
124+
}
125+
77126
@Component @Lazy
78127
static class StandardPojo {
128+
79129
@Autowired @Qualifier("interesting") TestBean testBean;
80130
}
81131

82132
@Configuration
83133
static class CustomConfig {
134+
84135
@InterestingBean
85136
public TestBean testBean1() {
86137
return new TestBean("interesting");
@@ -94,6 +145,7 @@ public TestBean testBean2() {
94145

95146
@InterestingPojo
96147
static class CustomPojo {
148+
97149
@InterestingNeed TestBean testBean;
98150
}
99151

0 commit comments

Comments
 (0)