Skip to content

Commit 75c1e50

Browse files
committed
Improve type determination for factory beans during condition evaluation
Previously, BeanTypeRegistry did not correctly determine the type that would be created by a factory bean if that factory bean was returned from a bean method with arguments on a configuration class found via component scanning. The key difference is that bean definitions for bean methods on configuration classes found via component scanning use ASM-based metadata rather than reflection-based metadata. The ASM-based method data does not provide direct access to the Method that will create the bean. In this case, BeanTypeRegistry was falling back to looking for a method with the matching name and no arguments. Therefore, if the bean method had any arguments it would fail to find the method and would, therefore, be unable to determine the type of bean produced by the factory bean. This commit updates BeanTypeRegistry to use logic that is very similar to Spring Framework's ConstructorResolver's resolveFactoryMethodIfPossible method to locate the method that will produce the factory bean. It looks for a single method with the required name with any number of arguments. If it finds multiple methods with the required name and different arguments it returns null, just as ConstructorResolver does. Closes gh-6755
1 parent 1dc231f commit 75c1e50

File tree

4 files changed

+154
-4
lines changed

4 files changed

+154
-4
lines changed

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/BeanTypeRegistry.java

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2015 the original author or authors.
2+
* Copyright 2012-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@
3737
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
3838
import org.springframework.beans.factory.config.BeanDefinition;
3939
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
40+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
4041
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
4142
import org.springframework.beans.factory.support.RootBeanDefinition;
4243
import org.springframework.core.ResolvableType;
@@ -137,8 +138,34 @@ private Method getFactoryMethod(ConfigurableListableBeanFactory beanFactory,
137138
.getBeanDefinition(definition.getFactoryBeanName());
138139
Class<?> factoryClass = ClassUtils.forName(factoryDefinition.getBeanClassName(),
139140
beanFactory.getBeanClassLoader());
140-
return ReflectionUtils.findMethod(factoryClass,
141-
definition.getFactoryMethodName());
141+
Method uniqueMethod = null;
142+
for (Method candidate : getCandidateFactoryMethods(definition, factoryClass)) {
143+
if (candidate.getName().equals(definition.getFactoryMethodName())) {
144+
if (uniqueMethod == null) {
145+
uniqueMethod = candidate;
146+
}
147+
else if (!matchingArguments(candidate, uniqueMethod)) {
148+
return null;
149+
}
150+
}
151+
}
152+
return uniqueMethod;
153+
}
154+
155+
private Method[] getCandidateFactoryMethods(BeanDefinition definition,
156+
Class<?> factoryClass) {
157+
return shouldConsiderNonPublicMethods(definition)
158+
? ReflectionUtils.getAllDeclaredMethods(factoryClass)
159+
: factoryClass.getMethods();
160+
}
161+
162+
private boolean shouldConsiderNonPublicMethods(BeanDefinition definition) {
163+
return (definition instanceof AbstractBeanDefinition)
164+
&& ((AbstractBeanDefinition) definition).isNonPublicAccessAllowed();
165+
}
166+
167+
private boolean matchingArguments(Method candidate, Method current) {
168+
return Arrays.equals(candidate.getParameterTypes(), current.getParameterTypes());
142169
}
143170

144171
private Class<?> getDirectFactoryBeanGeneric(

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2015 the original author or authors.
2+
* Copyright 2012-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,10 +23,15 @@
2323
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
2424
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
2525
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
26+
import org.springframework.boot.autoconfigure.condition.scan.ScannedFactoryBeanConfiguration;
27+
import org.springframework.boot.autoconfigure.condition.scan.ScannedFactoryBeanWithBeanMethodArgumentsConfiguration;
2628
import org.springframework.boot.test.EnvironmentTestUtils;
2729
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
2830
import org.springframework.context.annotation.Bean;
31+
import org.springframework.context.annotation.ComponentScan;
32+
import org.springframework.context.annotation.ComponentScan.Filter;
2933
import org.springframework.context.annotation.Configuration;
34+
import org.springframework.context.annotation.FilterType;
3035
import org.springframework.context.annotation.Import;
3136
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
3237
import org.springframework.context.annotation.ImportResource;
@@ -130,6 +135,27 @@ public void testOnMissingBeanConditionWithFactoryBean() {
130135
equalTo("fromFactory"));
131136
}
132137

138+
@Test
139+
public void testOnMissingBeanConditionWithComponentScannedFactoryBean() {
140+
this.context.register(ComponentScannedFactoryBeanBeanMethodConfiguration.class,
141+
ConditionalOnFactoryBean.class,
142+
PropertyPlaceholderAutoConfiguration.class);
143+
this.context.refresh();
144+
assertThat(this.context.getBean(ExampleBean.class).toString(),
145+
equalTo("fromFactory"));
146+
}
147+
148+
@Test
149+
public void testOnMissingBeanConditionWithComponentScannedFactoryBeanWithBeanMethodArguments() {
150+
this.context.register(
151+
ComponentScannedFactoryBeanBeanMethodWithArgumentsConfiguration.class,
152+
ConditionalOnFactoryBean.class,
153+
PropertyPlaceholderAutoConfiguration.class);
154+
this.context.refresh();
155+
assertThat(this.context.getBean(ExampleBean.class).toString(),
156+
equalTo("fromFactory"));
157+
}
158+
133159
@Test
134160
public void testOnMissingBeanConditionWithFactoryBeanWithBeanMethodArguments() {
135161
this.context.register(FactoryBeanWithBeanMethodArgumentsConfiguration.class,
@@ -241,6 +267,18 @@ public FactoryBean<ExampleBean> exampleBeanFactoryBean() {
241267
}
242268
}
243269

270+
@Configuration
271+
@ComponentScan(basePackages = "org.springframework.boot.autoconfigure.condition.scan", includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = ScannedFactoryBeanConfiguration.class))
272+
protected static class ComponentScannedFactoryBeanBeanMethodConfiguration {
273+
274+
}
275+
276+
@Configuration
277+
@ComponentScan(basePackages = "org.springframework.boot.autoconfigure.condition.scan", includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = ScannedFactoryBeanWithBeanMethodArgumentsConfiguration.class))
278+
protected static class ComponentScannedFactoryBeanBeanMethodWithArgumentsConfiguration {
279+
280+
}
281+
244282
@Configuration
245283
protected static class FactoryBeanWithBeanMethodArgumentsConfiguration {
246284
@Bean
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2012-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.boot.autoconfigure.condition.scan;
18+
19+
import org.springframework.beans.factory.FactoryBean;
20+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBeanTests.ExampleBean;
21+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBeanTests.ExampleFactoryBean;
22+
import org.springframework.context.annotation.Bean;
23+
import org.springframework.context.annotation.Configuration;
24+
25+
/**
26+
* Configuration for a factory bean produced by a bean method on a configuration class
27+
* found via component scanning.
28+
*
29+
* @author Andy Wilkinson
30+
*/
31+
@Configuration
32+
public class ScannedFactoryBeanConfiguration {
33+
34+
@Bean
35+
public FactoryBean<ExampleBean> exampleBeanFactoryBean() {
36+
return new ExampleFactoryBean("foo");
37+
}
38+
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2012-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.boot.autoconfigure.condition.scan;
18+
19+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBeanTests.ExampleFactoryBean;
20+
import org.springframework.context.annotation.Bean;
21+
import org.springframework.context.annotation.Configuration;
22+
23+
/**
24+
* Configuration for a factory bean produced by a bean method with arguments on a
25+
* configuration class found via component scanning.
26+
*
27+
* @author Andy Wilkinson
28+
*/
29+
@Configuration
30+
public class ScannedFactoryBeanWithBeanMethodArgumentsConfiguration {
31+
32+
@Bean
33+
public Foo foo() {
34+
return new Foo();
35+
}
36+
37+
@Bean
38+
public ExampleFactoryBean exampleBeanFactoryBean(Foo foo) {
39+
return new ExampleFactoryBean("foo");
40+
}
41+
42+
static class Foo {
43+
44+
}
45+
46+
}

0 commit comments

Comments
 (0)