Skip to content
This repository was archived by the owner on Feb 23, 2023. It is now read-only.

Commit f76a105

Browse files
committed
Write FactoryBean object type attribute
This commit improves support of factory beans by handling bean definitions that contain a FactoryBean#OBJECT_TYPE_ATTRIBUTE. Closes gh-1155
1 parent 20275d4 commit f76a105

File tree

8 files changed

+227
-2
lines changed

8 files changed

+227
-2
lines changed

spring-aot/src/main/java/org/springframework/aot/context/bootstrap/generator/bean/AbstractBeanRegistrationWriterSupplier.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,25 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
4141
}
4242

4343
@Override
44-
public BeanRegistrationWriter get(String beanName, BeanDefinition beanDefinition) {
44+
public final BeanRegistrationWriter get(String beanName, BeanDefinition beanDefinition) {
45+
if (!isCandidate(beanName, beanDefinition)) {
46+
return null;
47+
}
4548
BeanInstanceDescriptor beanInstanceDescriptor = resolveBeanInstanceDescriptor(beanDefinition);
4649
return (beanInstanceDescriptor != null) ? createInstance(beanName, beanDefinition, beanInstanceDescriptor) : null;
4750
}
4851

52+
/**
53+
* Check if the specified bean definition is a candidate for this instance.
54+
* @param beanName the bean name
55+
* @param beanDefinition the bean definition
56+
* @return {@code true} if this instance should provider a
57+
* {@link BeanRegistrationWriter} for this bean definition
58+
*/
59+
protected boolean isCandidate(String beanName, BeanDefinition beanDefinition) {
60+
return true;
61+
}
62+
4963
/**
5064
* Create a {@link BeanRegistrationWriter} based on a {@link BeanInstanceDescriptor}.
5165
* @param beanName the name of the bean

spring-aot/src/main/java/org/springframework/aot/context/bootstrap/generator/bean/support/ParameterWriter.java

+5
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
*/
4242
public final class ParameterWriter {
4343

44+
private final TypeWriter typeWriter = new TypeWriter();
45+
4446
/**
4547
* Write the specified parameter {@code value}.
4648
* @param value the value of the parameter
@@ -140,6 +142,9 @@ else if (value instanceof Enum) {
140142
else if (value instanceof Class) {
141143
code.add("$T.class", value);
142144
}
145+
else if (value instanceof ResolvableType) {
146+
code.add(this.typeWriter.generateTypeFor((ResolvableType) value));
147+
}
143148
}
144149

145150
private <T> CodeBlock writeAll(Iterable<T> items, Function<T, ResolvableType> elementType) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.springframework.beans.factory;
2+
3+
import java.util.function.Predicate;
4+
5+
import org.springframework.aot.context.bootstrap.generator.bean.AbstractBeanRegistrationWriterSupplier;
6+
import org.springframework.aot.context.bootstrap.generator.bean.BeanRegistrationWriter;
7+
import org.springframework.aot.context.bootstrap.generator.bean.BeanRegistrationWriterSupplier;
8+
import org.springframework.aot.context.bootstrap.generator.bean.DefaultBeanRegistrationWriter;
9+
import org.springframework.aot.context.bootstrap.generator.bean.descriptor.BeanInstanceDescriptor;
10+
import org.springframework.beans.factory.config.BeanDefinition;
11+
import org.springframework.core.ResolvableType;
12+
import org.springframework.core.annotation.Order;
13+
import org.springframework.lang.Nullable;
14+
15+
/**
16+
* A {@link BeanRegistrationWriterSupplier} for {@link FactoryBean factory beans} that
17+
* define the target type using the {@value FactoryBean#OBJECT_TYPE_ATTRIBUTE}.
18+
*
19+
* @author Stephane Nicoll
20+
*/
21+
@Order(0)
22+
class BeanFactoryBeanRegistrationWriterSupplier extends AbstractBeanRegistrationWriterSupplier {
23+
24+
@Override
25+
protected boolean isCandidate(String beanName, BeanDefinition beanDefinition) {
26+
Class<?> factoryBeanType = beanDefinition.getResolvableType().toClass();
27+
if (FactoryBean.class.isAssignableFrom(factoryBeanType)) {
28+
ResolvableType targetType = determineTargetType(beanDefinition);
29+
return (targetType != null && isCompatible(factoryBeanType, targetType));
30+
}
31+
return false;
32+
}
33+
34+
@Override
35+
protected BeanRegistrationWriter createInstance(String beanName, BeanDefinition beanDefinition,
36+
BeanInstanceDescriptor beanInstanceDescriptor) {
37+
return new DefaultBeanRegistrationWriter(beanName, beanDefinition, beanInstanceDescriptor) {
38+
@Override
39+
protected Predicate<String> getAttributeFilter() {
40+
return super.getAttributeFilter().or((candidate) ->
41+
candidate.equals(FactoryBean.OBJECT_TYPE_ATTRIBUTE));
42+
}
43+
};
44+
}
45+
46+
@Nullable
47+
private ResolvableType determineTargetType(BeanDefinition beanDefinition) {
48+
Object objectTypeAttribute = beanDefinition.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE);
49+
if (objectTypeAttribute instanceof ResolvableType) {
50+
return (ResolvableType) objectTypeAttribute;
51+
}
52+
if (objectTypeAttribute instanceof Class<?>) {
53+
return ResolvableType.forClass((Class<?>) objectTypeAttribute);
54+
}
55+
return null;
56+
}
57+
58+
private boolean isCompatible(Class<?> factoryBeanClass, ResolvableType candidate) {
59+
return ResolvableType.forClass(factoryBeanClass).as(FactoryBean.class)
60+
.getGeneric(0).isAssignableFrom(candidate);
61+
}
62+
63+
}

spring-aot/src/main/resources/META-INF/spring.factories

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ org.springframework.context.annotation.CommonAnnotationBeanDefinitionPostProcess
55

66
org.springframework.aot.context.bootstrap.generator.bean.BeanRegistrationWriterSupplier=\
77
org.springframework.aop.scope.ScopedProxyBeanRegistrationWriterSupplier,\
8+
org.springframework.beans.factory.BeanFactoryBeanRegistrationWriterSupplier,\
89
org.springframework.boot.actuate.autoconfigure.web.ManagementContextBeanRegistrationWriterSupplier,\
910
org.springframework.boot.autoconfigure.AutoConfigurationPackagesBeanRegistrationWriterSupplier,\
1011
org.springframework.boot.autoconfigure.context.properties.ConfigurationPropertiesBeanRegistrationWriterSupplier,\

spring-aot/src/test/java/org/springframework/aot/context/bootstrap/generator/bean/support/ParameterWriterTests.java

+8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.List;
2828
import java.util.Map;
2929
import java.util.Set;
30+
import java.util.function.Consumer;
3031
import java.util.stream.Stream;
3132

3233
import org.junit.jupiter.api.Test;
@@ -148,6 +149,13 @@ void writeClass() {
148149
assertThat(write(Integer.class, ResolvableType.forClass(Class.class))).isEqualTo("Integer.class");
149150
}
150151

152+
@Test
153+
void writeResolvableType() {
154+
ResolvableType type = ResolvableType.forClassWithGenerics(Consumer.class, Integer.class);
155+
assertThat(write(type, type)).hasImport(ResolvableType.class)
156+
.isEqualTo("ResolvableType.forClassWithGenerics(Consumer.class, Integer.class)");
157+
}
158+
151159
@Test
152160
void writeExecutableParameterTypesWithConstructor() {
153161
Constructor<?> constructor = ConstructorSample.class.getDeclaredConstructors()[0];

spring-aot/src/test/java/org/springframework/aot/context/bootstrap/generator/sample/factory/NumberHolder.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
package org.springframework.aot.context.bootstrap.generator.sample.factory;
1818

19-
public class NumberHolder<T extends Number> {
19+
import java.io.Serializable;
20+
21+
public class NumberHolder<T extends Number> implements Serializable {
2022

2123
private final T number;
2224

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.springframework.aot.context.bootstrap.generator.sample.factory;
2+
3+
import java.io.Serializable;
4+
5+
import org.springframework.beans.factory.config.AbstractFactoryBean;
6+
7+
public class TestGenericFactoryBean<T extends Serializable> extends AbstractFactoryBean<T> {
8+
9+
private final T instance;
10+
11+
public TestGenericFactoryBean(T instance) {
12+
this.instance = instance;
13+
}
14+
15+
@Override
16+
public Class<?> getObjectType() {
17+
return this.instance.getClass();
18+
}
19+
20+
@Override
21+
protected T createInstance() {
22+
return this.instance;
23+
}
24+
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package org.springframework.beans.factory;
2+
3+
import java.util.function.Consumer;
4+
import java.util.function.Function;
5+
6+
import org.junit.jupiter.api.Test;
7+
8+
import org.springframework.aot.context.bootstrap.generator.bean.BeanRegistrationWriter;
9+
import org.springframework.aot.context.bootstrap.generator.infrastructure.BootstrapWriterContext;
10+
import org.springframework.aot.context.bootstrap.generator.infrastructure.DefaultBootstrapWriterContext;
11+
import org.springframework.aot.context.bootstrap.generator.sample.factory.NumberHolder;
12+
import org.springframework.aot.context.bootstrap.generator.sample.factory.NumberHolderFactoryBean;
13+
import org.springframework.aot.context.bootstrap.generator.sample.factory.TestGenericFactoryBean;
14+
import org.springframework.aot.context.bootstrap.generator.test.CodeSnippet;
15+
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
16+
import org.springframework.beans.factory.support.RootBeanDefinition;
17+
import org.springframework.core.ResolvableType;
18+
import org.springframework.lang.Nullable;
19+
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
22+
/**
23+
* Tests for {@link BeanFactoryBeanRegistrationWriterSupplier}.
24+
*
25+
* @author Stephane Nicoll
26+
*/
27+
class BeanFactoryBeanRegistrationWriterSupplierTests {
28+
29+
@Test
30+
void getForFactoryBeanWithObjectTypeResolvableType() {
31+
RootBeanDefinition beanDefinition = new RootBeanDefinition();
32+
beanDefinition.setTargetType(TestGenericFactoryBean.class);
33+
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE,
34+
ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class));
35+
assertThat(get(beanDefinition)).isNotNull();
36+
}
37+
38+
@Test
39+
void writeBeanRegistrationForFactoryBeanWithObjectTypeResolvableType() {
40+
RootBeanDefinition beanDefinition = new RootBeanDefinition();
41+
beanDefinition.setTargetType(TestGenericFactoryBean.class);
42+
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE,
43+
ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class));
44+
BeanRegistrationWriter beanRegistrationWriter = get(beanDefinition);
45+
assertThat(beanRegistrationWriter).isNotNull();
46+
assertThat(CodeSnippet.of((code) -> beanRegistrationWriter.writeBeanRegistration(createBootstrapContext(), code))).lines().contains(
47+
"BeanDefinitionRegistrar.of(\"test\", ResolvableType.forClassWithGenerics(TestGenericFactoryBean.class, Serializable.class)).withConstructor(Serializable.class)",
48+
" .instanceSupplier((instanceContext) -> instanceContext.create(context, (attributes) -> new TestGenericFactoryBean(attributes.get(0)))).customize((bd) -> bd.setAttribute(\"factoryBeanObjectType\", ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class))).register(context);");
49+
}
50+
51+
@Test
52+
void getForFactoryBeanWithObjectTypeClass() {
53+
RootBeanDefinition beanDefinition = new RootBeanDefinition();
54+
beanDefinition.setTargetType(TestGenericFactoryBean.class);
55+
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, Integer.class);
56+
assertThat(get(beanDefinition)).isNotNull();
57+
}
58+
59+
@Test
60+
void writeBeanRegistrationForFactoryBeanWithObjectTypeClass() {
61+
RootBeanDefinition beanDefinition = new RootBeanDefinition();
62+
beanDefinition.setTargetType(TestGenericFactoryBean.class);
63+
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, Integer.class);
64+
BeanRegistrationWriter beanRegistrationWriter = get(beanDefinition);
65+
assertThat(beanRegistrationWriter).isNotNull();
66+
assertThat(CodeSnippet.of((code) -> beanRegistrationWriter.writeBeanRegistration(createBootstrapContext(), code))).lines().contains(
67+
"BeanDefinitionRegistrar.of(\"test\", ResolvableType.forClassWithGenerics(TestGenericFactoryBean.class, Serializable.class)).withConstructor(Serializable.class)",
68+
" .instanceSupplier((instanceContext) -> instanceContext.create(context, (attributes) -> new TestGenericFactoryBean(attributes.get(0)))).customize((bd) -> bd.setAttribute(\"factoryBeanObjectType\", Integer.class)).register(context);");
69+
}
70+
71+
@Test
72+
void getForFactoryBeanWithObjectTypeThatIsNotCompatibleWithBeanFactorBound() {
73+
RootBeanDefinition beanDefinition = new RootBeanDefinition();
74+
beanDefinition.setTargetType(TestGenericFactoryBean.class);
75+
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE,
76+
ResolvableType.forClassWithGenerics(Consumer.class, Integer.class));
77+
assertThat(get(beanDefinition)).isNull();
78+
}
79+
80+
@Test
81+
void getForFactoryBeanWithoutObjectTypeAttribute() {
82+
RootBeanDefinition beanDefinition = new RootBeanDefinition();
83+
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(
84+
NumberHolderFactoryBean.class, Integer.class));
85+
assertThat(get(beanDefinition)).isNull();
86+
}
87+
88+
@Test
89+
void getForNonFactoryBean() {
90+
RootBeanDefinition beanDefinition = new RootBeanDefinition();
91+
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(Function.class, Integer.class, String.class));
92+
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, Integer.class);
93+
assertThat(get(beanDefinition)).isNull();
94+
}
95+
96+
@Nullable
97+
private BeanRegistrationWriter get(RootBeanDefinition beanDefinition) {
98+
BeanFactoryBeanRegistrationWriterSupplier supplier = new BeanFactoryBeanRegistrationWriterSupplier();
99+
supplier.setBeanFactory(new DefaultListableBeanFactory());
100+
return supplier.get("test", beanDefinition);
101+
}
102+
103+
private static BootstrapWriterContext createBootstrapContext() {
104+
return new DefaultBootstrapWriterContext("com.example", "Test");
105+
}
106+
107+
}

0 commit comments

Comments
 (0)