Add bean instance generator infrastructure
This commit provides the necessary infrastructure to let components contribute statements that are used to fully instantiate a bean instance. To ease code generation, a dedicated infrastructure to register bean definition is provided in the o.s.beans.factory.generator package. BeanDefinitionRegistrar offers a builder style API that provides a way to hide how injected elements are resolved at runtime and let contributors provide code that may throw a checked exception. BeanInstanceContributor is the interface that components can implement to contribute to a bean instance setup. DefaultBeanInstanceGenerator generates, for a particular bean definition, the necessary statements to instantiate a bean. Closes gh-28047
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aot.generator;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.support.MultiCodeBlock;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Code generator for {@link ResolvableType}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class ResolvableTypeGenerator {
|
||||
|
||||
/**
|
||||
* Generate a type signature for the specified {@link ResolvableType}.
|
||||
* @param target the type to generate
|
||||
* @return the representation of that type
|
||||
*/
|
||||
public CodeBlock generateTypeFor(ResolvableType target) {
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
generate(code, target, false);
|
||||
return code.build();
|
||||
}
|
||||
|
||||
private void generate(CodeBlock.Builder code, ResolvableType target, boolean forceResolvableType) {
|
||||
Class<?> type = ClassUtils.getUserClass(target.toClass());
|
||||
if (!target.hasGenerics()) {
|
||||
if (forceResolvableType) {
|
||||
code.add("$T.forClass($T.class)", ResolvableType.class, type);
|
||||
}
|
||||
else {
|
||||
code.add("$T.class", type);
|
||||
}
|
||||
}
|
||||
else {
|
||||
code.add("$T.forClassWithGenerics($T.class, ", ResolvableType.class, type);
|
||||
ResolvableType[] generics = target.getGenerics();
|
||||
boolean hasGenericParameter = Arrays.stream(generics).anyMatch(ResolvableType::hasGenerics);
|
||||
MultiCodeBlock multi = new MultiCodeBlock();
|
||||
for (int i = 0; i < generics.length; i++) {
|
||||
ResolvableType parameter = target.getGeneric(i);
|
||||
multi.add(parameterCode -> generate(parameterCode, parameter, hasGenericParameter));
|
||||
}
|
||||
code.add(multi.join(", ")).add(")");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.aot.hint.TypeHint.Builder;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Gather the need for reflection at runtime.
|
||||
@@ -49,6 +50,28 @@ public class ReflectionHints {
|
||||
return this.types.values().stream().map(TypeHint.Builder::build);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the reflection hints for the type defined by the specified
|
||||
* {@link TypeReference}.
|
||||
* @param type the type to inspect
|
||||
* @return the reflection hints for this type, or {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
public TypeHint getTypeHint(TypeReference type) {
|
||||
Builder typeHintBuilder = this.types.get(type);
|
||||
return (typeHintBuilder != null ? typeHintBuilder.build() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the reflection hints for the specified type.
|
||||
* @param type the type to inspect
|
||||
* @return the reflection hints for this type, or {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
public TypeHint getTypeHint(Class<?> type) {
|
||||
return getTypeHint(TypeReference.of(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register or customize reflection hints for the type defined by the
|
||||
* specified {@link TypeReference}.
|
||||
|
||||
@@ -47,41 +47,48 @@ public final class MultiStatement {
|
||||
/**
|
||||
* Add the specified {@link CodeBlock codeblock} rendered as-is.
|
||||
* @param codeBlock the code block to add
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @see #addStatement(CodeBlock) to add a code block that represents
|
||||
* a statement
|
||||
*/
|
||||
public void add(CodeBlock codeBlock) {
|
||||
public MultiStatement add(CodeBlock codeBlock) {
|
||||
this.statements.add(Statement.of(codeBlock));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link CodeBlock} rendered as-is using the specified callback.
|
||||
* @param code the callback to use
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @see #addStatement(CodeBlock) to add a code block that represents
|
||||
* a statement
|
||||
*/
|
||||
public void add(Consumer<Builder> code) {
|
||||
public MultiStatement add(Consumer<Builder> code) {
|
||||
CodeBlock.Builder builder = CodeBlock.builder();
|
||||
code.accept(builder);
|
||||
add(builder.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a statement.
|
||||
* @param statement the statement to add
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public void addStatement(CodeBlock statement) {
|
||||
public MultiStatement addStatement(CodeBlock statement) {
|
||||
this.statements.add(Statement.ofStatement(statement));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a statement using the specified callback.
|
||||
* @param code the callback to use
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public void addStatement(Consumer<Builder> code) {
|
||||
public MultiStatement addStatement(Consumer<Builder> code) {
|
||||
CodeBlock.Builder builder = CodeBlock.builder();
|
||||
code.accept(builder);
|
||||
addStatement(builder.build());
|
||||
return addStatement(builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,10 +96,11 @@ public final class MultiStatement {
|
||||
* arguments.
|
||||
* @param code the code of the statement
|
||||
* @param args the arguments for placeholders
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @see CodeBlock#of(String, Object...)
|
||||
*/
|
||||
public void addStatement(String code, Object... args) {
|
||||
addStatement(CodeBlock.of(code, args));
|
||||
public MultiStatement addStatement(String code, Object... args) {
|
||||
return addStatement(CodeBlock.of(code, args));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,9 +109,11 @@ public final class MultiStatement {
|
||||
* @param items the items to handle, each item is represented as a statement
|
||||
* @param itemGenerator the item generator
|
||||
* @param <T> the type of the item
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public <T> void addAll(Iterable<T> items, Function<T, CodeBlock> itemGenerator) {
|
||||
public <T> MultiStatement addAll(Iterable<T> items, Function<T, CodeBlock> itemGenerator) {
|
||||
items.forEach(element -> addStatement(itemGenerator.apply(element)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aot.generator;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.javapoet.support.CodeSnippet;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ResolvableTypeGenerator}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ResolvableTypeGeneratorTests {
|
||||
|
||||
@Test
|
||||
void generateTypeForResolvableTypeWithGenericParameter() {
|
||||
assertThat(generateTypeFor(
|
||||
ResolvableType.forClassWithGenerics(Function.class,
|
||||
ResolvableType.forClassWithGenerics(Supplier.class, String.class),
|
||||
ResolvableType.forClassWithGenerics(Supplier.class, Integer.class))))
|
||||
.isEqualTo("ResolvableType.forClassWithGenerics(Function.class, "
|
||||
+ "ResolvableType.forClassWithGenerics(Supplier.class, String.class), "
|
||||
+ "ResolvableType.forClassWithGenerics(Supplier.class, Integer.class))");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateTypeForResolvableTypeWithMixedParameter() {
|
||||
assertThat(generateTypeFor(
|
||||
ResolvableType.forClassWithGenerics(Function.class,
|
||||
ResolvableType.forClassWithGenerics(Supplier.class, String.class),
|
||||
ResolvableType.forClass(Integer.class))))
|
||||
.isEqualTo("ResolvableType.forClassWithGenerics(Function.class, "
|
||||
+ "ResolvableType.forClassWithGenerics(Supplier.class, String.class), "
|
||||
+ "ResolvableType.forClass(Integer.class))");
|
||||
}
|
||||
|
||||
private String generateTypeFor(ResolvableType type) {
|
||||
return CodeSnippet.process(new ResolvableTypeGenerator().generateTypeFor(type));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,10 +16,13 @@
|
||||
|
||||
package org.springframework.aot.hint;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -41,11 +44,34 @@ class ReflectionHintsTests {
|
||||
typeWithMemberCategories(String.class, MemberCategory.DECLARED_FIELDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTypeUsingType() {
|
||||
this.reflectionHints.registerType(TypeReference.of(String.class),
|
||||
hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS));
|
||||
assertThat(this.reflectionHints.getTypeHint(String.class)).satisfies(
|
||||
typeWithMemberCategories(String.class, MemberCategory.DECLARED_FIELDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTypeUsingTypeReference() {
|
||||
this.reflectionHints.registerType(String.class,
|
||||
hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS));
|
||||
assertThat(this.reflectionHints.getTypeHint(TypeReference.of(String.class))).satisfies(
|
||||
typeWithMemberCategories(String.class, MemberCategory.DECLARED_FIELDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTypeForNonExistingType() {
|
||||
assertThat(this.reflectionHints.getTypeHint(String.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerTypeReuseBuilder() {
|
||||
this.reflectionHints.registerType(TypeReference.of(String.class),
|
||||
typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
|
||||
this.reflectionHints.registerField(ReflectionUtils.findField(String.class, "value"));
|
||||
Field field = ReflectionUtils.findField(String.class, "value");
|
||||
assertThat(field).isNotNull();
|
||||
this.reflectionHints.registerField(field);
|
||||
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(String.class.getCanonicalName());
|
||||
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint -> assertThat(fieldHint.getName()).isEqualTo("value"));
|
||||
@@ -63,7 +89,9 @@ class ReflectionHintsTests {
|
||||
|
||||
@Test
|
||||
void registerField() {
|
||||
this.reflectionHints.registerField(ReflectionUtils.findField(TestType.class, "field"));
|
||||
Field field = ReflectionUtils.findField(TestType.class, "field");
|
||||
assertThat(field).isNotNull();
|
||||
this.reflectionHints.registerField(field);
|
||||
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName());
|
||||
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint ->
|
||||
@@ -92,7 +120,9 @@ class ReflectionHintsTests {
|
||||
|
||||
@Test
|
||||
void registerMethod() {
|
||||
this.reflectionHints.registerMethod(ReflectionUtils.findMethod(TestType.class, "setName", String.class));
|
||||
Method method = ReflectionUtils.findMethod(TestType.class, "setName", String.class);
|
||||
assertThat(method).isNotNull();
|
||||
this.reflectionHints.registerMethod(method);
|
||||
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName());
|
||||
assertThat(typeHint.fields()).isEmpty();
|
||||
@@ -119,6 +149,7 @@ class ReflectionHintsTests {
|
||||
@SuppressWarnings("unused")
|
||||
static class TestType {
|
||||
|
||||
@Nullable
|
||||
private String field;
|
||||
|
||||
void setName(String name) {
|
||||
|
||||
Reference in New Issue
Block a user