Skip to content

Commit c1738aa

Browse files
committed
Improve target detection for FactoryBeans with generic
Closes gh-28809
1 parent bbcc269 commit c1738aa

File tree

5 files changed

+136
-13
lines changed

5 files changed

+136
-13
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,23 +72,45 @@ class DefaultBeanRegistrationCodeFragments extends BeanRegistrationCodeFragments
7272
public Class<?> getTarget(RegisteredBean registeredBean,
7373
Executable constructorOrFactoryMethod) {
7474

75-
Class<?> target = extractDeclaringClass(constructorOrFactoryMethod);
75+
Class<?> target = extractDeclaringClass(registeredBean.getBeanType(),
76+
constructorOrFactoryMethod);
7677
while (target.getName().startsWith("java.") && registeredBean.isInnerBean()) {
7778
target = registeredBean.getParent().getBeanClass();
7879
}
7980
return target;
8081
}
8182

82-
private Class<?> extractDeclaringClass(Executable executable) {
83+
private Class<?> extractDeclaringClass(ResolvableType beanType, Executable executable) {
8384
Class<?> declaringClass = ClassUtils.getUserClass(executable.getDeclaringClass());
8485
if (executable instanceof Constructor<?>
8586
&& AccessVisibility.forMember(executable) == AccessVisibility.PUBLIC
8687
&& FactoryBean.class.isAssignableFrom(declaringClass)) {
87-
return ResolvableType.forType(declaringClass).as(FactoryBean.class).getGeneric(0).toClass();
88+
return extractTargetClassFromFactoryBean(declaringClass, beanType);
8889
}
8990
return executable.getDeclaringClass();
9091
}
9192

93+
/**
94+
* Extract the target class of a public {@link FactoryBean} based on its
95+
* constructor. If the implementation does not resolve the target class
96+
* because it itself uses a generic, attempt to extract it from the
97+
* bean type.
98+
* @param factoryBeanType the factory bean type
99+
* @param beanType the bean type
100+
* @return the target class to use
101+
*/
102+
private Class<?> extractTargetClassFromFactoryBean(Class<?> factoryBeanType, ResolvableType beanType) {
103+
ResolvableType target = ResolvableType.forType(factoryBeanType)
104+
.as(FactoryBean.class).getGeneric(0);
105+
if (target.getType().equals(Class.class)) {
106+
return target.toClass();
107+
}
108+
else if (factoryBeanType.isAssignableFrom(beanType.toClass())) {
109+
return beanType.as(FactoryBean.class).getGeneric(0).toClass();
110+
}
111+
return beanType.toClass();
112+
}
113+
92114
@Override
93115
public CodeBlock generateNewBeanDefinitionCode(GenerationContext generationContext,
94116
ResolvableType beanType, BeanRegistrationCode beanRegistrationCode) {

spring-beans/src/test/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragmentsTests.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.beans.factory.support.RootBeanDefinition;
2828
import org.springframework.beans.testfixture.beans.factory.DummyFactory;
2929
import org.springframework.beans.testfixture.beans.factory.aot.MockBeanRegistrationsCode;
30+
import org.springframework.core.ResolvableType;
3031
import org.springframework.core.testfixture.aot.generate.TestGenerationContext;
3132
import org.springframework.util.ReflectionUtils;
3233

@@ -57,6 +58,29 @@ void getTargetOnConstructorToPublicFactoryBean() {
5758
SimpleBeanFactoryBean.class.getDeclaredConstructors()[0])).isEqualTo(SimpleBean.class);
5859
}
5960

61+
@Test
62+
void getTargetOnConstructorToPublicGenericFactoryBeanExtractTargetFromFactoryBeanType() {
63+
RegisteredBean registeredBean = registerTestBean(ResolvableType
64+
.forClassWithGenerics(GenericFactoryBean.class, SimpleBean.class));
65+
assertThat(createInstance(registeredBean).getTarget(registeredBean,
66+
GenericFactoryBean.class.getDeclaredConstructors()[0])).isEqualTo(SimpleBean.class);
67+
}
68+
69+
@Test
70+
void getTargetOnConstructorToPublicGenericFactoryBeanWithBoundExtractTargetFromFactoryBeanType() {
71+
RegisteredBean registeredBean = registerTestBean(ResolvableType
72+
.forClassWithGenerics(NumberFactoryBean.class, Integer.class));
73+
assertThat(createInstance(registeredBean).getTarget(registeredBean,
74+
NumberFactoryBean.class.getDeclaredConstructors()[0])).isEqualTo(Integer.class);
75+
}
76+
77+
@Test
78+
void getTargetOnConstructorToPublicGenericFactoryBeanUseBeanTypeAsFallback() {
79+
RegisteredBean registeredBean = registerTestBean(SimpleBean.class);
80+
assertThat(createInstance(registeredBean).getTarget(registeredBean,
81+
GenericFactoryBean.class.getDeclaredConstructors()[0])).isEqualTo(SimpleBean.class);
82+
}
83+
6084
@Test
6185
void getTargetOnConstructorToProtectedFactoryBean() {
6286
RegisteredBean registeredBean = registerTestBean(SimpleBean.class);
@@ -138,6 +162,12 @@ private RegisteredBean registerTestBean(Class<?> beanType) {
138162
return RegisteredBean.of(this.beanFactory, "testBean");
139163
}
140164

165+
private RegisteredBean registerTestBean(ResolvableType beanType) {
166+
this.beanFactory.registerBeanDefinition("testBean",
167+
new RootBeanDefinition(beanType));
168+
return RegisteredBean.of(this.beanFactory, "testBean");
169+
}
170+
141171
private BeanRegistrationCodeFragments createInstance(RegisteredBean registeredBean) {
142172
return new DefaultBeanRegistrationCodeFragments(this.beanRegistrationsCode, registeredBean,
143173
new BeanDefinitionMethodGeneratorFactory(this.beanFactory));
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2002-2022 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+
* https://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.aot;
18+
19+
import org.springframework.beans.BeanUtils;
20+
import org.springframework.beans.factory.FactoryBean;
21+
import org.springframework.lang.Nullable;
22+
23+
/**
24+
* A public {@link FactoryBean} with a generic type.
25+
*
26+
* @author Stephane Nicoll
27+
*/
28+
public class GenericFactoryBean<T> implements FactoryBean<T> {
29+
30+
private final Class<T> beanType;
31+
32+
public GenericFactoryBean(Class<T> beanType) {
33+
this.beanType = beanType;
34+
}
35+
36+
@Nullable
37+
@Override
38+
public T getObject() throws Exception {
39+
return BeanUtils.instantiateClass(this.beanType);
40+
}
41+
42+
@Nullable
43+
@Override
44+
public Class<?> getObjectType() {
45+
return this.beanType;
46+
}
47+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2002-2022 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+
* https://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.aot;
18+
19+
/**
20+
* A {@link GenericFactoryBean} that has a bound for the target type.
21+
*
22+
* @author Stephane Nicoll
23+
*/
24+
public class NumberFactoryBean<T extends Number> extends GenericFactoryBean<T> {
25+
26+
public NumberFactoryBean(Class<T> beanType) {
27+
super(beanType);
28+
}
29+
30+
}

spring-beans/src/testFixtures/java/org/springframework/beans/factory/aot/SimpleBeanFactoryBean.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,14 @@
1919
import org.springframework.beans.factory.FactoryBean;
2020

2121
/**
22-
* A public {@link FactoryBean}.
22+
* A public {@link FactoryBean} with a resolved generic for {@link GenericFactoryBean}.
2323
*
2424
* @author Stephane Nicoll
2525
*/
26-
public class SimpleBeanFactoryBean implements FactoryBean<SimpleBean> {
26+
public class SimpleBeanFactoryBean extends GenericFactoryBean<SimpleBean> {
2727

28-
@Override
29-
public SimpleBean getObject() throws Exception {
30-
return new SimpleBean();
31-
}
32-
33-
@Override
34-
public Class<?> getObjectType() {
35-
return SimpleBean.class;
28+
public SimpleBeanFactoryBean() {
29+
super(SimpleBean.class);
3630
}
3731

3832
}

0 commit comments

Comments
 (0)