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,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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user