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:
Stephane Nicoll
2022-02-14 14:31:19 +01:00
parent bfe9d4fc49
commit c5e1a774a5
33 changed files with 4119 additions and 11 deletions

View File

@@ -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(")");
}
}
}

View File

@@ -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}.

View File

@@ -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;
}
/**

View File

@@ -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));
}
}

View File

@@ -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) {