Skip to content

Commit 106a973

Browse files
committed
Prevent early FactoryBean creation for type checking purposes when coming from a factory method on yet another bean (e.g. from a configuration class)
Issue: SPR-11202
1 parent b124066 commit 106a973

File tree

3 files changed

+178
-19
lines changed

3 files changed

+178
-19
lines changed

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

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -739,28 +739,37 @@ class Holder { Class<?> value = null; }
739739
final Holder objectType = new Holder();
740740
String factoryBeanName = mbd.getFactoryBeanName();
741741
final String factoryMethodName = mbd.getFactoryMethodName();
742-
if (factoryBeanName != null && factoryMethodName != null) {
743-
// Try to obtain the FactoryBean's object type without instantiating it at all.
744-
BeanDefinition fbDef = getBeanDefinition(factoryBeanName);
745-
if (fbDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) fbDef).hasBeanClass()) {
746-
// CGLIB subclass methods hide generic parameters; look at the original user class.
747-
Class<?> fbClass = ClassUtils.getUserClass(((AbstractBeanDefinition) fbDef).getBeanClass());
748-
// Find the given factory method, taking into account that in the case of
749-
// @Bean methods, there may be parameters present.
750-
ReflectionUtils.doWithMethods(fbClass,
751-
new ReflectionUtils.MethodCallback() {
752-
@Override
753-
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
754-
if (method.getName().equals(factoryMethodName) &&
755-
FactoryBean.class.isAssignableFrom(method.getReturnType())) {
756-
objectType.value = GenericTypeResolver.resolveReturnTypeArgument(method, FactoryBean.class);
742+
743+
if (factoryBeanName != null) {
744+
if (factoryMethodName != null) {
745+
// Try to obtain the FactoryBean's object type without instantiating it at all.
746+
BeanDefinition fbDef = getBeanDefinition(factoryBeanName);
747+
if (fbDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) fbDef).hasBeanClass()) {
748+
// CGLIB subclass methods hide generic parameters; look at the original user class.
749+
Class<?> fbClass = ClassUtils.getUserClass(((AbstractBeanDefinition) fbDef).getBeanClass());
750+
// Find the given factory method, taking into account that in the case of
751+
// @Bean methods, there may be parameters present.
752+
ReflectionUtils.doWithMethods(fbClass,
753+
new ReflectionUtils.MethodCallback() {
754+
@Override
755+
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
756+
if (method.getName().equals(factoryMethodName) &&
757+
FactoryBean.class.isAssignableFrom(method.getReturnType())) {
758+
objectType.value = GenericTypeResolver.resolveReturnTypeArgument(method, FactoryBean.class);
759+
}
757760
}
758-
}
759-
});
760-
if (objectType.value != null) {
761-
return objectType.value;
761+
});
762+
if (objectType.value != null) {
763+
return objectType.value;
764+
}
762765
}
763766
}
767+
// If not resolvable above and the referenced factory bean doesn't exist yet,
768+
// exit here - we don't want to force the creation of another bean just to
769+
// obtain a FactoryBean's object type...
770+
if (!isBeanEligibleForMetadataCaching(factoryBeanName)) {
771+
return null;
772+
}
764773
}
765774

766775
FactoryBean<?> fb = (mbd.isSingleton() ?
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* Copyright 2002-2013 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.context.annotation;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.junit.After;
26+
import org.junit.Test;
27+
28+
import org.springframework.beans.factory.FactoryBean;
29+
import org.springframework.beans.factory.InitializingBean;
30+
import org.springframework.core.type.AnnotatedTypeMetadata;
31+
import org.springframework.core.type.AnnotationMetadata;
32+
import org.springframework.util.Assert;
33+
34+
import static org.junit.Assert.*;
35+
36+
/**
37+
* @author Dave Syer
38+
*/
39+
public class Spr11202Tests {
40+
41+
private AnnotationConfigApplicationContext context;
42+
43+
@After
44+
public void close() {
45+
if (context != null) {
46+
context.close();
47+
}
48+
}
49+
50+
@Test // Fails
51+
public void testWithImporter() {
52+
context = new AnnotationConfigApplicationContext(Wrapper.class);
53+
assertEquals("foo", context.getBean("value"));
54+
}
55+
56+
@Test // Passes
57+
public void testWithoutImporter() {
58+
context = new AnnotationConfigApplicationContext(Config.class);
59+
assertEquals("foo", context.getBean("value"));
60+
}
61+
62+
63+
@Configuration
64+
@Import(Selector.class)
65+
protected static class Wrapper {
66+
}
67+
68+
protected static class Selector implements ImportSelector {
69+
70+
@Override
71+
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
72+
return new String[] {Config.class.getName()};
73+
}
74+
}
75+
76+
@Configuration
77+
protected static class Config {
78+
79+
@Bean
80+
public FooFactoryBean foo() {
81+
return new FooFactoryBean();
82+
}
83+
84+
@Bean
85+
public String value() throws Exception {
86+
String name = foo().getObject().getName();
87+
Assert.state(name != null, "Name cannot be null");
88+
return name;
89+
}
90+
91+
@Bean
92+
@Conditional(NoBarCondition.class)
93+
public String bar() throws Exception {
94+
return "bar";
95+
}
96+
}
97+
98+
protected static class NoBarCondition implements Condition {
99+
100+
@Override
101+
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
102+
if (context.getBeanFactory().getBeanNamesForAnnotation(Bar.class).length > 0) {
103+
return false;
104+
}
105+
return true;
106+
}
107+
}
108+
109+
@Retention(RetentionPolicy.RUNTIME)
110+
@Documented
111+
@Target(ElementType.TYPE)
112+
protected static @interface Bar {
113+
}
114+
115+
protected static class FooFactoryBean implements FactoryBean<Foo>, InitializingBean {
116+
117+
private Foo foo = new Foo();
118+
119+
@Override
120+
public Foo getObject() throws Exception {
121+
return foo;
122+
}
123+
124+
@Override
125+
public Class<?> getObjectType() {
126+
return Foo.class;
127+
}
128+
129+
@Override
130+
public boolean isSingleton() {
131+
return true;
132+
}
133+
134+
@Override
135+
public void afterPropertiesSet() throws Exception {
136+
this.foo.name = "foo";
137+
}
138+
}
139+
140+
protected static class Foo {
141+
142+
private String name;
143+
144+
public String getName() {
145+
return name;
146+
}
147+
}
148+
149+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ private ListableBeanFactory initBeanFactory(Class<?>... configClasses) {
8080
RequiredAnnotationBeanPostProcessor rapp = new RequiredAnnotationBeanPostProcessor();
8181
rapp.setBeanFactory(factory);
8282
factory.addBeanPostProcessor(rapp);
83+
factory.freezeConfiguration();
8384
return factory;
8485
}
8586

0 commit comments

Comments
 (0)