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,51 @@
/*
* 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.beans.factory.generator;
import org.springframework.aot.generator.CodeContribution;
/**
* Strategy interface to be implemented by components that participates in a
* bean instance setup so that the generated code provides an equivalent
* setup.
*
* @author Stephane Nicoll
* @since 6.0
*/
@FunctionalInterface
public interface BeanInstanceContributor {
/**
* A {@link BeanInstanceContributor} that does not contribute anything
* to the {@link CodeContribution}.
*/
BeanInstanceContributor NO_OP = contribution -> { };
/**
* Contribute to the specified {@link CodeContribution}.
* <p>Implementation of this interface can assume the following variables
* to be accessible:
* <ul>
* <li>{@code beanFactory}: the general {@code DefaultListableBeanFactory}</li>
* <li>{@code instanceContext}: the {@code BeanInstanceContext} callback</li>
* <li>{@code bean}: the variable that refers to the bean instance</li>
* </ul>
* @param contribution the {@link CodeContribution} to use
*/
void contribute(CodeContribution contribution);
}

View File

@@ -0,0 +1,218 @@
/*
* 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.beans.factory.generator;
import java.lang.reflect.Executable;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.springframework.aot.generator.ResolvableTypeGenerator;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedSet;
import org.springframework.core.ResolvableType;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.CodeBlock.Builder;
import org.springframework.javapoet.support.MultiCodeBlock;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
* Support for writing parameters.
*
* @author Stephane Nicoll
* @since 6.0
*/
public final class BeanParameterGenerator {
private final ResolvableTypeGenerator typeGenerator = new ResolvableTypeGenerator();
private final BiConsumer<BeanDefinition, Builder> innerBeanDefinitionWriter;
/**
* Create an instance with the callback to use to write an inner bean
* definition.
* @param innerBeanDefinitionWriter the inner bean definition writer
*/
public BeanParameterGenerator(BiConsumer<BeanDefinition, Builder> innerBeanDefinitionWriter) {
this.innerBeanDefinitionWriter = innerBeanDefinitionWriter;
}
/**
* Create an instance with no support for inner bean definitions.
*/
public BeanParameterGenerator() {
this((beanDefinition, builder) -> {
throw new IllegalStateException("Inner bean definition is not supported by this instance");
});
}
/**
* Write the specified parameter {@code value}.
* @param value the value of the parameter
* @return the value of the parameter
*/
public CodeBlock writeParameterValue(@Nullable Object value) {
return writeParameterValue(value, () -> ResolvableType.forInstance(value));
}
/**
* Write the specified parameter {@code value}.
* @param value the value of the parameter
* @param parameterType the type of the parameter
* @return the value of the parameter
*/
public CodeBlock writeParameterValue(@Nullable Object value, Supplier<ResolvableType> parameterType) {
Builder code = CodeBlock.builder();
writeParameterValue(code, value, parameterType);
return code.build();
}
/**
* Write the parameter types of the specified {@link Executable}.
* @param executable the executable
* @return the parameter types of the executable as a comma separated list
*/
public CodeBlock writeExecutableParameterTypes(Executable executable) {
Class<?>[] parameterTypes = Arrays.stream(executable.getParameters())
.map(Parameter::getType).toArray(Class<?>[]::new);
return CodeBlock.of(Arrays.stream(parameterTypes).map(d -> "$T.class")
.collect(Collectors.joining(", ")), (Object[]) parameterTypes);
}
private void writeParameterValue(Builder code, @Nullable Object value, Supplier<ResolvableType> parameterTypeSupplier) {
if (value == null) {
code.add("null");
return;
}
ResolvableType parameterType = parameterTypeSupplier.get();
if (parameterType.isArray()) {
code.add("new $T { ", parameterType.toClass());
code.add(writeAll(Arrays.asList(ObjectUtils.toObjectArray(value)),
item -> parameterType.getComponentType()));
code.add(" }");
}
else if (value instanceof List<?> list) {
if (list.isEmpty()) {
code.add("$T.emptyList()", Collections.class);
}
else {
Class<?> listType = (value instanceof ManagedList ? ManagedList.class : List.class);
code.add("$T.of(", listType);
ResolvableType collectionType = parameterType.as(List.class).getGenerics()[0];
code.add(writeAll(list, item -> collectionType));
code.add(")");
}
}
else if (value instanceof Set<?> set) {
if (set.isEmpty()) {
code.add("$T.emptySet()", Collections.class);
}
else {
Class<?> setType = (value instanceof ManagedSet ? ManagedSet.class : Set.class);
code.add("$T.of(", setType);
ResolvableType collectionType = parameterType.as(Set.class).getGenerics()[0];
code.add(writeAll(set, item -> collectionType));
code.add(")");
}
}
else if (value instanceof Map<?, ?> map) {
if (map.size() <= 10) {
code.add("$T.of(", Map.class);
List<Object> parameters = new ArrayList<>();
map.forEach((mapKey, mapValue) -> {
parameters.add(mapKey);
parameters.add(mapValue);
});
code.add(writeAll(parameters, ResolvableType::forInstance));
code.add(")");
}
}
else if (value instanceof Character character) {
String result = '\'' + characterLiteralWithoutSingleQuotes(character) + '\'';
code.add(result);
}
else if (isPrimitiveOrWrapper(value)) {
code.add("$L", value);
}
else if (value instanceof String) {
code.add("$S", value);
}
else if (value instanceof Enum<?> enumValue) {
code.add("$T.$N", enumValue.getClass(), enumValue.name());
}
else if (value instanceof Class) {
code.add("$T.class", value);
}
else if (value instanceof ResolvableType) {
code.add(this.typeGenerator.generateTypeFor((ResolvableType) value));
}
else if (value instanceof BeanDefinition) {
this.innerBeanDefinitionWriter.accept((BeanDefinition) value, code);
}
else if (value instanceof BeanReference) {
code.add("new $T($S)", RuntimeBeanReference.class, ((BeanReference) value).getBeanName());
}
else {
throw new IllegalArgumentException("Parameter of type " + parameterType + " is not supported");
}
}
private <T> CodeBlock writeAll(Iterable<T> items, Function<T, ResolvableType> elementType) {
MultiCodeBlock multi = new MultiCodeBlock();
items.forEach(item -> multi.add(code ->
writeParameterValue(code, item, () -> elementType.apply(item))));
return multi.join(", ");
}
private boolean isPrimitiveOrWrapper(Object value) {
Class<?> valueType = value.getClass();
return (valueType.isPrimitive() || valueType == Double.class || valueType == Float.class
|| valueType == Long.class || valueType == Integer.class || valueType == Short.class
|| valueType == Character.class || valueType == Byte.class || valueType == Boolean.class);
}
// Copied from com.squareup.javapoet.Util
private static String characterLiteralWithoutSingleQuotes(char c) {
// see https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6
return switch (c) {
case '\b' -> "\\b"; /* \u0008: backspace (BS) */
case '\t' -> "\\t"; /* \u0009: horizontal tab (HT) */
case '\n' -> "\\n"; /* \u000a: linefeed (LF) */
case '\f' -> "\\f"; /* \u000c: form feed (FF) */
case '\r' -> "\\r"; /* \u000d: carriage return (CR) */
case '\"' -> "\""; /* \u0022: double quote (") */
case '\'' -> "\\'"; /* \u0027: single quote (') */
case '\\' -> "\\\\"; /* \u005c: backslash (\) */
default -> Character.isISOControl(c) ? String.format("\\u%04x", (int) c) : Character.toString(c);
};
}
}

View File

@@ -0,0 +1,165 @@
/*
* 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.beans.factory.generator;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.aot.generator.CodeContribution;
import org.springframework.aot.generator.DefaultCodeContribution;
import org.springframework.aot.generator.ProtectedAccess.Options;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.javapoet.CodeBlock;
import org.springframework.util.ClassUtils;
/**
* Write the necessary statements to instantiate a bean.
*
* @author Stephane Nicoll
*/
class DefaultBeanInstanceGenerator {
private static final Options BEAN_INSTANCE_OPTIONS = new Options(false, true);
private final Executable instanceCreator;
private final List<BeanInstanceContributor> contributors;
private final InjectionGenerator injectionGenerator;
DefaultBeanInstanceGenerator(Executable instanceCreator, List<BeanInstanceContributor> contributors) {
this.instanceCreator = instanceCreator;
this.contributors = List.copyOf(contributors);
this.injectionGenerator = new InjectionGenerator();
}
/**
* Return the necessary code to instantiate and post-process the bean
* handled by this instance.
* @param runtimeHints the runtime hints instance to use
* @return a code contribution that provides an initialized bean instance
*/
public CodeContribution generateBeanInstance(RuntimeHints runtimeHints) {
DefaultCodeContribution contribution = new DefaultCodeContribution(runtimeHints);
contribution.protectedAccess().analyze(this.instanceCreator, BEAN_INSTANCE_OPTIONS);
if (this.instanceCreator instanceof Constructor<?> constructor) {
writeBeanInstantiation(contribution, constructor);
}
else if (this.instanceCreator instanceof Method method) {
writeBeanInstantiation(contribution, method);
}
return contribution;
}
private void writeBeanInstantiation(CodeContribution contribution, Constructor<?> constructor) {
Class<?> declaringType = ClassUtils.getUserClass(constructor.getDeclaringClass());
boolean innerClass = isInnerClass(declaringType);
boolean multiStatements = !this.contributors.isEmpty();
int minArgs = isInnerClass(declaringType) ? 2 : 1;
CodeBlock.Builder code = CodeBlock.builder();
// Shortcut for common case
if (!multiStatements && constructor.getParameterTypes().length < minArgs) {
if (innerClass) {
code.add("() -> beanFactory.getBean($T.class).new $L()",
declaringType.getEnclosingClass(), declaringType.getSimpleName());
}
else {
// Only apply the shortcut if there's one candidate
if (declaringType.getDeclaredConstructors().length > 1) {
code.add("() -> new $T()", declaringType);
}
else {
code.add("$T::new", declaringType);
}
}
contribution.statements().addStatement(code.build());
return;
}
contribution.runtimeHints().reflection().registerConstructor(constructor,
hint -> hint.withMode(ExecutableMode.INTROSPECT));
code.add("(instanceContext) ->");
branch(multiStatements, () -> code.beginControlFlow(""), () -> code.add(" "));
if (multiStatements) {
code.add("$T bean = ", declaringType);
}
code.add(this.injectionGenerator.writeInstantiation(constructor));
contribution.statements().addStatement(code.build());
if (multiStatements) {
for (BeanInstanceContributor contributor : this.contributors) {
contributor.contribute(contribution);
}
contribution.statements().addStatement("return bean")
.add(codeBlock -> codeBlock.unindent().add("}"));
}
}
private static boolean isInnerClass(Class<?> type) {
return type.isMemberClass() && !Modifier.isStatic(type.getModifiers());
}
private void writeBeanInstantiation(CodeContribution contribution, Method method) {
// Factory method can be introspected
contribution.runtimeHints().reflection().registerMethod(method,
hint -> hint.withMode(ExecutableMode.INTROSPECT));
List<Class<?>> parameterTypes = new ArrayList<>(Arrays.asList(method.getParameterTypes()));
boolean multiStatements = !this.contributors.isEmpty();
Class<?> declaringType = method.getDeclaringClass();
CodeBlock.Builder code = CodeBlock.builder();
// Shortcut for common case
if (!multiStatements && parameterTypes.isEmpty()) {
code.add("() -> ");
branch(Modifier.isStatic(method.getModifiers()),
() -> code.add("$T", declaringType),
() -> code.add("beanFactory.getBean($T.class)", declaringType));
code.add(".$L()", method.getName());
contribution.statements().addStatement(code.build());
return;
}
code.add("(instanceContext) ->");
branch(multiStatements, () -> code.beginControlFlow(""), () -> code.add(" "));
if (multiStatements) {
code.add("$T bean = ", method.getReturnType());
}
code.add(this.injectionGenerator.writeInstantiation(method));
contribution.statements().addStatement(code.build());
if (multiStatements) {
for (BeanInstanceContributor contributor : this.contributors) {
contributor.contribute(contribution);
}
contribution.statements().addStatement("return bean")
.add(codeBlock -> codeBlock.unindent().add("}"));
}
}
private static void branch(boolean condition, Runnable ifTrue, Runnable ifFalse) {
if (condition) {
ifTrue.run();
}
else {
ifFalse.run();
}
}
}

View File

@@ -0,0 +1,239 @@
/*
* 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.beans.factory.generator;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.beans.factory.generator.config.BeanDefinitionRegistrar.BeanInstanceContext;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.CodeBlock.Builder;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* Generate the necessary code to {@link #writeInstantiation(Executable)
* create a bean instance} or {@link #writeInjection(Member, boolean)
* inject dependencies}.
* <p/>
* The generator assumes a number of variables to be accessible:
* <ul>
* <li>{@code beanFactory}: the general {@code DefaultListableBeanFactory}</li>
* <li>{@code instanceContext}: the {@link BeanInstanceContext} callback</li>
* <li>{@code bean}: the variable that refers to the bean instance</li>
* </ul>
*
* @author Stephane Nicoll
* @author Brian Clozel
*/
public class InjectionGenerator {
private final BeanParameterGenerator parameterGenerator = new BeanParameterGenerator();
/**
* Write the necessary code to instantiate an object using the specified
* {@link Executable}. The code is suitable to be assigned to a variable
* or used as a {@literal return} statement.
* @param creator the executable to invoke to create an instance of the
* requested object
* @return the code to instantiate an object using the specified executable
*/
public CodeBlock writeInstantiation(Executable creator) {
if (creator instanceof Constructor<?> constructor) {
return write(constructor);
}
if (creator instanceof Method method) {
return writeMethodInstantiation(method);
}
throw new IllegalArgumentException("Could not handle creator " + creator);
}
/**
* Write the code to inject a value resolved by {@link BeanInstanceContext}
* in the specified {@link Member}.
* @param member the field or method to inject
* @param required whether the value is required
* @return a statement that injects a value to the specified membmer
*/
public CodeBlock writeInjection(Member member, boolean required) {
if (member instanceof Method method) {
return writeMethodInjection(method, required);
}
if (member instanceof Field field) {
return writeFieldInjection(field, required);
}
throw new IllegalArgumentException("Could not handle member " + member);
}
private CodeBlock write(Constructor<?> creator) {
Builder code = CodeBlock.builder();
Class<?> declaringType = ClassUtils.getUserClass(creator.getDeclaringClass());
boolean innerClass = isInnerClass(declaringType);
Class<?>[] parameterTypes = Arrays.stream(creator.getParameters()).map(Parameter::getType)
.toArray(Class<?>[]::new);
// Shortcut for common case
if (innerClass && parameterTypes.length == 1) {
code.add("beanFactory.getBean($T.class).new $L()", declaringType.getEnclosingClass(),
declaringType.getSimpleName());
return code.build();
}
if (parameterTypes.length == 0) {
code.add("new $T()", declaringType);
return code.build();
}
boolean isAmbiguous = Arrays.stream(creator.getDeclaringClass().getDeclaredConstructors())
.filter(constructor -> constructor.getParameterCount() == parameterTypes.length).count() > 1;
code.add("instanceContext.create(beanFactory, (attributes) ->");
List<CodeBlock> parameters = resolveParameters(creator.getParameters(), isAmbiguous);
if (innerClass) { // Remove the implicit argument
parameters.remove(0);
}
code.add(" ");
if (innerClass) {
code.add("beanFactory.getBean($T.class).new $L(", declaringType.getEnclosingClass(),
declaringType.getSimpleName());
}
else {
code.add("new $T(", declaringType);
}
for (int i = 0; i < parameters.size(); i++) {
code.add(parameters.get(i));
if (i < parameters.size() - 1) {
code.add(", ");
}
}
code.add(")");
code.add(")");
return code.build();
}
private static boolean isInnerClass(Class<?> type) {
return type.isMemberClass() && !Modifier.isStatic(type.getModifiers());
}
private CodeBlock writeMethodInstantiation(Method injectionPoint) {
if (injectionPoint.getParameterCount() == 0) {
Builder code = CodeBlock.builder();
Class<?> declaringType = injectionPoint.getDeclaringClass();
if (Modifier.isStatic(injectionPoint.getModifiers())) {
code.add("$T", declaringType);
}
else {
code.add("beanFactory.getBean($T.class)", declaringType);
}
code.add(".$L()", injectionPoint.getName());
return code.build();
}
return write(injectionPoint, code -> code.add(".create(beanFactory, (attributes) ->"), true);
}
private CodeBlock writeMethodInjection(Method injectionPoint, boolean required) {
Consumer<Builder> attributesResolver = code -> {
if (required) {
code.add(".invoke(beanFactory, (attributes) ->");
}
else {
code.add(".resolve(beanFactory, false).ifResolved((attributes) ->");
}
};
return write(injectionPoint, attributesResolver, false);
}
private CodeBlock write(Method injectionPoint, Consumer<Builder> attributesResolver, boolean instantiation) {
Builder code = CodeBlock.builder();
code.add("instanceContext");
if (!instantiation) {
code.add(".method($S, ", injectionPoint.getName());
code.add(this.parameterGenerator.writeExecutableParameterTypes(injectionPoint));
code.add(")\n").indent().indent();
}
attributesResolver.accept(code);
List<CodeBlock> parameters = resolveParameters(injectionPoint.getParameters(), false);
code.add(" ");
if (instantiation) {
if (Modifier.isStatic(injectionPoint.getModifiers())) {
code.add("$T", injectionPoint.getDeclaringClass());
}
else {
code.add("beanFactory.getBean($T.class)", injectionPoint.getDeclaringClass());
}
}
else {
code.add("bean");
}
code.add(".$L(", injectionPoint.getName());
code.add(CodeBlock.join(parameters, ", "));
code.add(")");
code.add(")");
if (!instantiation) {
code.unindent().unindent();
}
return code.build();
}
CodeBlock writeFieldInjection(Field injectionPoint, boolean required) {
Builder code = CodeBlock.builder();
code.add("instanceContext.field($S, $T.class", injectionPoint.getName(), injectionPoint.getType());
code.add(")\n").indent().indent();
if (required) {
code.add(".invoke(beanFactory, (attributes) ->");
}
else {
code.add(".resolve(beanFactory, false).ifResolved((attributes) ->");
}
boolean hasAssignment = Modifier.isPrivate(injectionPoint.getModifiers());
if (hasAssignment) {
code.beginControlFlow("");
String fieldName = String.format("%sField", injectionPoint.getName());
code.addStatement("$T $L = $T.findField($T.class, $S, $T.class)", Field.class, fieldName, ReflectionUtils.class,
injectionPoint.getDeclaringClass(), injectionPoint.getName(), injectionPoint.getType());
code.addStatement("$T.makeAccessible($L)", ReflectionUtils.class, fieldName);
code.addStatement("$T.setField($L, bean, attributes.get(0))", ReflectionUtils.class, fieldName);
code.unindent().add("}");
}
else {
code.add(" bean.$L = attributes.get(0)", injectionPoint.getName());
}
code.add(")").unindent().unindent();
return code.build();
}
private List<CodeBlock> resolveParameters(Parameter[] parameters, boolean shouldCast) {
List<CodeBlock> parameterValues = new ArrayList<>();
for (int i = 0; i < parameters.length; i++) {
if (shouldCast) {
parameterValues.add(CodeBlock.of("attributes.get($L, $T.class)", i, parameters[i].getType()));
}
else {
parameterValues.add(CodeBlock.of("attributes.get($L)", i));
}
}
return parameterValues;
}
}

View File

@@ -0,0 +1,408 @@
/*
* 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.beans.factory.generator.config;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
/**
* {@link BeanDefinition} registration mechanism offering transparent
* dependency resolution, as well as exception management.
*
* <p>Used by code generators and for internal use within the framework
* only.
*
* @author Stephane Nicoll
* @since 6.0
*/
public final class BeanDefinitionRegistrar {
private static final Log logger = LogFactory.getLog(BeanDefinitionRegistrar.class);
@Nullable
private final String beanName;
private final Class<?> beanClass;
@Nullable
private final ResolvableType beanType;
private final BeanDefinitionBuilder builder;
private final List<Consumer<RootBeanDefinition>> customizers;
@Nullable
private Executable instanceCreator;
@Nullable
private RootBeanDefinition beanDefinition;
private BeanDefinitionRegistrar(@Nullable String beanName, Class<?> beanClass, @Nullable ResolvableType beanType) {
this.beanName = beanName;
this.beanClass = beanClass;
this.beanType = beanType;
this.builder = BeanDefinitionBuilder.rootBeanDefinition(beanClass);
this.customizers = new ArrayList<>();
}
/**
* Initialize the registration of a bean with the specified name and type.
* @param beanName the name of the bean
* @param beanType the type of the bean
* @return a registrar for the specified bean
*/
public static BeanDefinitionRegistrar of(String beanName, ResolvableType beanType) {
return new BeanDefinitionRegistrar(beanName, beanType.toClass(), beanType);
}
/**
* Initialize the registration of a bean with the specified name and type.
* @param beanName the name of the bean
* @param beanType the type of the bean
* @return a registrar for the specified bean
*/
public static BeanDefinitionRegistrar of(String beanName, Class<?> beanType) {
return new BeanDefinitionRegistrar(beanName, beanType, null);
}
/**
* Initialize the registration of an inner bean with the specified type.
* @param beanType the type of the inner bean
* @return a registrar for the specified inner bean
*/
public static BeanDefinitionRegistrar inner(ResolvableType beanType) {
return new BeanDefinitionRegistrar(null, beanType.toClass(), beanType);
}
/**
* Initialize the registration of an inner bean with the specified type.
* @param beanType the type of the inner bean
* @return a registrar for the specified inner bean
*/
public static BeanDefinitionRegistrar inner(Class<?> beanType) {
return new BeanDefinitionRegistrar(null, beanType, null);
}
/**
* Customize the {@link RootBeanDefinition} using the specified consumer.
* @param bd a consumer for the bean definition
* @return {@code this}, to facilitate method chaining
*/
public BeanDefinitionRegistrar customize(ThrowableConsumer<RootBeanDefinition> bd) {
this.customizers.add(bd);
return this;
}
/**
* Specify the factory method to use to instantiate the bean.
* @param declaredType the {@link Method#getDeclaringClass() declared type}
* of the factory method.
* @param name the name of the method
* @param parameterTypes the parameter types of the method
* @return {@code this}, to facilitate method chaining
* @see RootBeanDefinition#getResolvedFactoryMethod()
*/
public BeanDefinitionRegistrar withFactoryMethod(Class<?> declaredType, String name, Class<?>... parameterTypes) {
this.instanceCreator = getMethod(declaredType, name, parameterTypes);
return this;
}
/**
* Specify the constructor to use to instantiate the bean.
* @param parameterTypes the parameter types of the constructor
* @return {@code this}, to facilitate method chaining
*/
public BeanDefinitionRegistrar withConstructor(Class<?>... parameterTypes) {
this.instanceCreator = getConstructor(this.beanClass, parameterTypes);
return this;
}
/**
* Specify how the bean instance should be created and initialized, using
* the {@link BeanInstanceContext} to resolve dependencies if necessary.
* @param instanceContext the {@link BeanInstanceContext} to use
* @return {@code this}, to facilitate method chaining
*/
public BeanDefinitionRegistrar instanceSupplier(ThrowableFunction<BeanInstanceContext, ?> instanceContext) {
return customize(beanDefinition -> beanDefinition.setInstanceSupplier(() ->
instanceContext.apply(createBeanInstanceContext())));
}
/**
* Specify how the bean instance should be created and initialized.
* @return {@code this}, to facilitate method chaining
*/
public BeanDefinitionRegistrar instanceSupplier(ThrowableSupplier<?> instanceSupplier) {
return customize(beanDefinition -> beanDefinition.setInstanceSupplier(instanceSupplier));
}
/**
* Register the {@link RootBeanDefinition} defined by this instance to
* the specified bean factory.
* @param beanFactory the bean factory to use
*/
public void register(DefaultListableBeanFactory beanFactory) {
if (logger.isDebugEnabled()) {
logger.debug("Register bean definition with name '" + this.beanName + "'");
}
BeanDefinition beanDefinition = toBeanDefinition();
if (this.beanName == null) {
throw new IllegalStateException("Bean name not set. Could not register " + beanDefinition);
}
beanFactory.registerBeanDefinition(this.beanName, beanDefinition);
}
/**
* Return the {@link RootBeanDefinition} defined by this instance.
* @return the bean definition
*/
public RootBeanDefinition toBeanDefinition() {
try {
this.beanDefinition = createBeanDefinition();
return this.beanDefinition;
}
catch (Exception ex) {
throw new FatalBeanException("Failed to create bean definition for bean with name '" + this.beanName + "'", ex);
}
}
private RootBeanDefinition createBeanDefinition() {
RootBeanDefinition bd = (RootBeanDefinition) this.builder.getBeanDefinition();
if (this.beanType != null) {
bd.setTargetType(this.beanType);
}
if (this.instanceCreator instanceof Method) {
bd.setResolvedFactoryMethod((Method) this.instanceCreator);
}
this.customizers.forEach(customizer -> customizer.accept(bd));
return bd;
}
private BeanInstanceContext createBeanInstanceContext() {
String resolvedBeanName = this.beanName != null ? this.beanName : createInnerBeanName();
return new BeanInstanceContext(resolvedBeanName, this.beanClass);
}
private String createInnerBeanName() {
return "(inner bean)" + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR +
(this.beanDefinition != null ? ObjectUtils.getIdentityHexString(this.beanDefinition) : 0);
}
@Nullable
private BeanDefinition resolveBeanDefinition(DefaultListableBeanFactory beanFactory) {
return this.beanDefinition;
}
private static Constructor<?> getConstructor(Class<?> beanType, Class<?>... parameterTypes) {
try {
return beanType.getDeclaredConstructor(parameterTypes);
}
catch (NoSuchMethodException ex) {
String message = String.format("No constructor with type(s) [%s] found on %s",
toCommaSeparatedNames(parameterTypes), beanType.getName());
throw new IllegalArgumentException(message, ex);
}
}
private static Method getMethod(Class<?> declaredType, String methodName, Class<?>... parameterTypes) {
Method method = ReflectionUtils.findMethod(declaredType, methodName, parameterTypes);
if (method == null) {
String message = String.format("No method '%s' with type(s) [%s] found on %s", methodName,
toCommaSeparatedNames(parameterTypes), declaredType.getName());
throw new IllegalArgumentException(message);
}
return MethodIntrospector.selectInvocableMethod(method, declaredType);
}
private static String toCommaSeparatedNames(Class<?>... parameterTypes) {
return Arrays.stream(parameterTypes).map(Class::getName).collect(Collectors.joining(", "));
}
/**
* Callback interface used by instance suppliers that need to resolve
* dependencies for the {@link Executable} used to create the instance
* as well as any {@link Member} that should be handled by the context.
*/
public final class BeanInstanceContext {
private final String beanName;
private final Class<?> beanType;
private BeanInstanceContext(String beanName, Class<?> beanType) {
this.beanName = beanName;
this.beanType = beanType;
}
/**
* Return a bean instance using the specified {@code factory}.
* @param beanFactory the bean factory to use
* @param factory a function that returns a bean instance based on
* the resolved attributes required by its instance creator
* @param <T> the type of the bean
* @return the bean instance
*/
public <T> T create(DefaultListableBeanFactory beanFactory, ThrowableFunction<InjectedElementAttributes, T> factory) {
return resolveInstanceCreator(BeanDefinitionRegistrar.this.instanceCreator).create(beanFactory, factory);
}
private InjectedElementResolver resolveInstanceCreator(@Nullable Executable instanceCreator) {
if (instanceCreator instanceof Method) {
return new InjectedConstructionResolver(instanceCreator, instanceCreator.getDeclaringClass(), this.beanName,
BeanDefinitionRegistrar.this::resolveBeanDefinition);
}
if (instanceCreator instanceof Constructor) {
return new InjectedConstructionResolver(instanceCreator, this.beanType, this.beanName,
BeanDefinitionRegistrar.this::resolveBeanDefinition);
}
throw new IllegalStateException("No factory method or constructor is set");
}
/**
* Create an {@link InjectedElementResolver} for the specified field.
* @param name the name of the field
* @param type the type of the field
* @return a resolved for the specified field
*/
public InjectedElementResolver field(String name, Class<?> type) {
return new InjectedFieldResolver(getField(name, type), this.beanName);
}
/**
* Create an {@link InjectedElementResolver} for the specified bean method.
* @param name the name of the method on the target bean
* @param parameterTypes the method parameter types
* @return a resolved for the specified bean method
*/
public InjectedElementResolver method(String name, Class<?>... parameterTypes) {
return new InjectedMethodResolver(getMethod(this.beanType, name, parameterTypes), this.beanType, this.beanName);
}
private Field getField(String fieldName, Class<?> fieldType) {
Field field = ReflectionUtils.findField(this.beanType, fieldName, fieldType);
if (field == null) {
throw new IllegalArgumentException("No field '" + fieldName + "' with type " + fieldType.getName() + " found on " + this.beanType);
}
return field;
}
}
/**
* A {@link Consumer} that allows to invoke code that throws a checked exception.
*
* @author Stephane Nicoll
* @param <T> the type of the input to the operation
*/
@FunctionalInterface
public interface ThrowableConsumer<T> extends Consumer<T> {
void acceptWithException(T t) throws Exception;
@Override
default void accept(T t) {
try {
acceptWithException(t);
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw new RuntimeException(ex.getMessage(), ex);
}
}
}
/**
* A {@link Function} that allows to invoke code that throws a checked exception.
*
* @author Stephane Nicoll
* @param <T> the type of the input to the function
* @param <R> the type of the result of the function
*/
@FunctionalInterface
public interface ThrowableFunction<T, R> extends Function<T, R> {
R applyWithException(T t) throws Exception;
@Override
default R apply(T t) {
try {
return applyWithException(t);
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw new RuntimeException(ex.getMessage(), ex);
}
}
}
/**
* A {@link Supplier} that allows to invoke code that throws a checked exception.
*
* @author Stephane Nicoll
* @param <T> the type of results supplied by this supplier
*/
public interface ThrowableSupplier<T> extends Supplier<T> {
T getWithException() throws Exception;
@Override
default T get() {
try {
return getWithException();
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw new RuntimeException(ex.getMessage(), ex);
}
}
}
}

View File

@@ -0,0 +1,175 @@
/*
* 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.beans.factory.generator.config;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.Function;
import java.util.function.Supplier;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.BeanDefinitionValueResolver;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.CollectionFactory;
import org.springframework.core.MethodParameter;
/**
* An {@link InjectedElementResolver} for an {@link Executable} that creates
* a bean instance.
*
* @author Stephane Nicoll
*/
class InjectedConstructionResolver implements InjectedElementResolver {
private final Executable executable;
private final Class<?> targetType;
private final String beanName;
private final Function<DefaultListableBeanFactory, BeanDefinition> beanDefinitionResolver;
InjectedConstructionResolver(Executable executable, Class<?> targetType, String beanName,
Function<DefaultListableBeanFactory, BeanDefinition> beanDefinitionResolver) {
this.executable = executable;
this.targetType = targetType;
this.beanName = beanName;
this.beanDefinitionResolver = beanDefinitionResolver;
}
Executable getExecutable() {
return this.executable;
}
@Override
public InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory, boolean required) {
int argumentCount = this.executable.getParameterCount();
List<Object> arguments = new ArrayList<>();
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
TypeConverter typeConverter = beanFactory.getTypeConverter();
ConstructorArgumentValues argumentValues = resolveArgumentValues(beanFactory);
for (int i = 0; i < argumentCount; i++) {
MethodParameter methodParam = createMethodParameter(i);
ValueHolder valueHolder = argumentValues.getIndexedArgumentValue(i, null);
if (valueHolder != null) {
if (valueHolder.isConverted()) {
arguments.add(valueHolder.getConvertedValue());
}
else {
Object userValue = beanFactory.getTypeConverter()
.convertIfNecessary(valueHolder.getValue(), methodParam.getParameterType());
arguments.add(userValue);
}
}
else {
DependencyDescriptor depDescriptor = new DependencyDescriptor(methodParam, true);
depDescriptor.setContainingClass(this.targetType);
try {
Object arg = resolveDependency(() -> beanFactory.resolveDependency(
depDescriptor, this.beanName, autowiredBeans, typeConverter), methodParam.getParameterType());
arguments.add(arg);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, this.beanName, new InjectionPoint(methodParam), ex);
}
}
}
return new InjectedElementAttributes(arguments);
}
private Object resolveDependency(Supplier<Object> resolvedDependency, Class<?> dependencyType) {
try {
return resolvedDependency.get();
}
catch (NoSuchBeanDefinitionException ex) {
// Single constructor or factory method -> let's return an empty array/collection
// for e.g. a vararg or a non-null List/Set/Map parameter.
if (dependencyType.isArray()) {
return Array.newInstance(dependencyType.getComponentType(), 0);
}
else if (CollectionFactory.isApproximableCollectionType(dependencyType)) {
return CollectionFactory.createCollection(dependencyType, 0);
}
else if (CollectionFactory.isApproximableMapType(dependencyType)) {
return CollectionFactory.createMap(dependencyType, 0);
}
throw ex;
}
}
private ConstructorArgumentValues resolveArgumentValues(DefaultListableBeanFactory beanFactory) {
ConstructorArgumentValues resolvedValues = new ConstructorArgumentValues();
BeanDefinition beanDefinition = this.beanDefinitionResolver.apply(beanFactory);
if (beanDefinition == null || !beanDefinition.hasConstructorArgumentValues()) {
return resolvedValues;
}
ConstructorArgumentValues argumentValues = beanDefinition.getConstructorArgumentValues();
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(beanFactory,
this.beanName, beanDefinition);
for (Map.Entry<Integer, ValueHolder> entry : argumentValues.getIndexedArgumentValues().entrySet()) {
int index = entry.getKey();
ValueHolder valueHolder = entry.getValue();
if (valueHolder.isConverted()) {
resolvedValues.addIndexedArgumentValue(index, valueHolder);
}
else {
Object resolvedValue =
valueResolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue());
ValueHolder resolvedValueHolder =
new ValueHolder(resolvedValue, valueHolder.getType(), valueHolder.getName());
resolvedValueHolder.setSource(valueHolder);
resolvedValues.addIndexedArgumentValue(index, resolvedValueHolder);
}
}
return resolvedValues;
}
private MethodParameter createMethodParameter(int index) {
if (this.executable instanceof Constructor) {
return new MethodParameter((Constructor<?>) this.executable, index);
}
else {
return new MethodParameter((Method) this.executable, index);
}
}
@Override
public String toString() {
return new StringJoiner(", ", InjectedConstructionResolver.class.getSimpleName() + "[", "]")
.add("executable=" + this.executable)
.toString();
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.beans.factory.generator.config;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Resolved attributes of an injected element.
*
* @author Stephane Nicoll
* @since 6.0
*/
public class InjectedElementAttributes {
@Nullable
private final List<Object> attributes;
InjectedElementAttributes(@Nullable List<Object> attributes) {
this.attributes = attributes;
}
/**
* Specify if the attributes have been resolved.
* @return the resolution of the injection
*/
public boolean isResolved() {
return (this.attributes != null);
}
/**
* Run the specified {@linkplain Runnable task} only if this instance is
* {@link #isResolved() resolved}.
* @param task the task to invoke if attributes are available
*/
public void ifResolved(Runnable task) {
if (isResolved()) {
task.run();
}
}
/**
* Invoke the specified {@link Consumer} with the resolved attributes.
* @param attributes the consumer to invoke if this instance is resolved
*/
public void ifResolved(BeanDefinitionRegistrar.ThrowableConsumer<InjectedElementAttributes> attributes) {
ifResolved(() -> attributes.accept(this));
}
/**
* Return the resolved attribute at the specified index.
* @param index the attribute index
* @param <T> the type of the attribute
* @return the attribute
*/
@SuppressWarnings("unchecked")
public <T> T get(int index) {
Assert.notNull(this.attributes, "Attributes must not be null");
return (T) this.attributes.get(index);
}
/**
* Return the resolved attribute at the specified index.
* @param index the attribute index
* @param type the attribute type
* @param <T> the type of the attribute
* @return the attribute
*/
@SuppressWarnings("unchecked")
public <T> T get(int index, Class<T> type) {
Assert.notNull(this.attributes, "Attributes must not be null");
return (T) this.attributes.get(index);
}
}

View File

@@ -0,0 +1,75 @@
/*
* 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.beans.factory.generator.config;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
/**
* Resolve the attributes of an injected element such as a {@code Constructor}
* or a factory {@code Method}.
*
* @author Stephane Nicoll
* @since 6.0
*/
public interface InjectedElementResolver {
/**
* Resolve the attributes using the specified bean factory.
* @param beanFactory the bean factory to use
* @return the resolved attributes
*/
default InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory) {
return resolve(beanFactory, true);
}
/**
* Resolve the attributes using the specified bean factory.
* @param beanFactory the bean factory to use
* @param required whether the injection point is mandatory
* @return the resolved attributes
*/
InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory, boolean required);
/**
* Invoke the specified consumer with the resolved
* {@link InjectedElementAttributes attributes}.
* @param beanFactory the bean factory to use to resolve the attributes
* @param attributes a consumer of the resolved attributes
*/
default void invoke(DefaultListableBeanFactory beanFactory,
BeanDefinitionRegistrar.ThrowableConsumer<InjectedElementAttributes> attributes) {
InjectedElementAttributes elements = resolve(beanFactory);
attributes.accept(elements);
}
/**
* Create an instance based on the resolved
* {@link InjectedElementAttributes attributes}.
* @param beanFactory the bean factory to use to resolve the attributes
* @param factory a factory to create the instance based on the resolved attributes
* @param <T> the type of the instance
* @return a new instance
*/
default <T> T create(DefaultListableBeanFactory beanFactory,
BeanDefinitionRegistrar.ThrowableFunction<InjectedElementAttributes, T> factory) {
InjectedElementAttributes attributes = resolve(beanFactory);
return factory.apply(attributes);
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.beans.factory.generator.config;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
/**
* An {@link InjectedElementResolver} for a {@link Field}.
*
* @author Stephane Nicoll
*/
class InjectedFieldResolver implements InjectedElementResolver {
private final Field field;
private final String beanName;
/**
* Create a new instance.
* @param field the field to handle
* @param beanName the name of the bean, or {@code null}
*/
InjectedFieldResolver(Field field, String beanName) {
this.field = field;
this.beanName = beanName;
}
@Override
public InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory, boolean required) {
DependencyDescriptor desc = new DependencyDescriptor(this.field, required);
desc.setContainingClass(this.field.getType());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
TypeConverter typeConverter = beanFactory.getTypeConverter();
try {
Object value = beanFactory.resolveDependency(desc, this.beanName, autowiredBeanNames, typeConverter);
if (value == null && !required) {
return new InjectedElementAttributes(null);
}
return new InjectedElementAttributes(Collections.singletonList(value));
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, this.beanName, new InjectionPoint(this.field), ex);
}
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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.beans.factory.generator.config;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.MethodParameter;
/**
* An {@link InjectedElementResolver} for a {@link Method}.
*
* @author Stephane Nicoll
*/
class InjectedMethodResolver implements InjectedElementResolver {
private final Method method;
private final Class<?> target;
private final String beanName;
/**
* Create a new instance.
* @param method the method to handle
* @param target the type on which the method is declared
* @param beanName the name of the bean, or {@code null}
*/
InjectedMethodResolver(Method method, Class<?> target, String beanName) {
this.method = method;
this.target = target;
this.beanName = beanName;
}
@Override
public InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory, boolean required) {
int argumentCount = this.method.getParameterCount();
List<Object> arguments = new ArrayList<>();
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
TypeConverter typeConverter = beanFactory.getTypeConverter();
for (int i = 0; i < argumentCount; i++) {
MethodParameter methodParam = new MethodParameter(this.method, i);
DependencyDescriptor depDescriptor = new DependencyDescriptor(methodParam, required);
depDescriptor.setContainingClass(this.target);
try {
Object arg = beanFactory.resolveDependency(depDescriptor, this.beanName, autowiredBeans, typeConverter);
if (arg == null && !required) {
arguments = null;
break;
}
arguments.add(arg);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, this.beanName, new InjectionPoint(methodParam), ex);
}
}
return new InjectedElementAttributes(arguments);
}
}

View File

@@ -0,0 +1,9 @@
/**
* Classes used in generated code to ease bean registration.
*/
@NonNullApi
@NonNullFields
package org.springframework.beans.factory.generator.config;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;