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:
parent
bfe9d4fc49
commit
c5e1a774a5
|
@ -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;
|
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* 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.io.StringWriter;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanReference;
|
||||
import org.springframework.beans.factory.support.ManagedList;
|
||||
import org.springframework.beans.factory.support.ManagedSet;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.javapoet.support.CodeSnippet;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link BeanParameterGenerator}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class BeanParameterGeneratorTests {
|
||||
|
||||
private final BeanParameterGenerator generator = new BeanParameterGenerator();
|
||||
|
||||
@Test
|
||||
void writeCharArray() {
|
||||
char[] value = new char[] { 'v', 'a', 'l', 'u', 'e' };
|
||||
assertThat(write(value, ResolvableType.forArrayComponent(ResolvableType.forClass(char.class))))
|
||||
.isEqualTo("new char[] { 'v', 'a', 'l', 'u', 'e' }");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeStringArray() {
|
||||
String[] value = new String[] { "a", "test" };
|
||||
assertThat(write(value, ResolvableType.forArrayComponent(ResolvableType.forClass(String.class))))
|
||||
.isEqualTo("new String[] { \"a\", \"test\" }");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeStringList() {
|
||||
List<String> value = List.of("a", "test");
|
||||
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(List.class, String.class));
|
||||
assertThat(code.getSnippet()).isEqualTo(
|
||||
"List.of(\"a\", \"test\")");
|
||||
assertThat(code.hasImport(List.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeStringManagedList() {
|
||||
ManagedList<String> value = ManagedList.of("a", "test");
|
||||
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(List.class, String.class));
|
||||
assertThat(code.getSnippet()).isEqualTo(
|
||||
"ManagedList.of(\"a\", \"test\")");
|
||||
assertThat(code.hasImport(ManagedList.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeEmptyList() {
|
||||
List<String> value = List.of();
|
||||
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(List.class, String.class));
|
||||
assertThat(code.getSnippet()).isEqualTo("Collections.emptyList()");
|
||||
assertThat(code.hasImport(Collections.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeStringSet() {
|
||||
Set<String> value = Set.of("a", "test");
|
||||
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(Set.class, String.class));
|
||||
assertThat(code.getSnippet()).startsWith("Set.of(").contains("a").contains("test");
|
||||
assertThat(code.hasImport(Set.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeStringManagedSet() {
|
||||
Set<String> value = ManagedSet.of("a", "test");
|
||||
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(Set.class, String.class));
|
||||
assertThat(code.getSnippet()).isEqualTo(
|
||||
"ManagedSet.of(\"a\", \"test\")");
|
||||
assertThat(code.hasImport(ManagedSet.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeEmptySet() {
|
||||
Set<String> value = Set.of();
|
||||
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(Set.class, String.class));
|
||||
assertThat(code.getSnippet()).isEqualTo("Collections.emptySet()");
|
||||
assertThat(code.hasImport(Collections.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeMap() {
|
||||
Map<String, Object> value = new LinkedHashMap<>();
|
||||
value.put("name", "Hello");
|
||||
value.put("counter", 42);
|
||||
assertThat(write(value)).isEqualTo("Map.of(\"name\", \"Hello\", \"counter\", 42)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeMapWithEnum() {
|
||||
Map<String, Object> value = new HashMap<>();
|
||||
value.put("unit", ChronoUnit.DAYS);
|
||||
assertThat(write(value)).isEqualTo("Map.of(\"unit\", ChronoUnit.DAYS)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeEmptyMap() {
|
||||
assertThat(write(Map.of())).isEqualTo("Map.of()");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeString() {
|
||||
assertThat(write("test", ResolvableType.forClass(String.class))).isEqualTo("\"test\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeCharEscapeBackslash() {
|
||||
assertThat(write('\\', ResolvableType.forType(char.class))).isEqualTo("'\\\\'");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("primitiveValues")
|
||||
void writePrimitiveValue(Object value, String parameter) {
|
||||
assertThat(write(value, ResolvableType.forClass(value.getClass()))).isEqualTo(parameter);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> primitiveValues() {
|
||||
return Stream.of(Arguments.of((short) 0, "0"), Arguments.of((1), "1"), Arguments.of(2L, "2"),
|
||||
Arguments.of(2.5d, "2.5"), Arguments.of(2.7f, "2.7"), Arguments.of('c', "'c'"),
|
||||
Arguments.of((byte) 1, "1"), Arguments.of(true, "true"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeEnum() {
|
||||
assertThat(write(ChronoUnit.DAYS, ResolvableType.forClass(ChronoUnit.class)))
|
||||
.isEqualTo("ChronoUnit.DAYS");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeClass() {
|
||||
assertThat(write(Integer.class, ResolvableType.forClass(Class.class)))
|
||||
.isEqualTo("Integer.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeResolvableType() {
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(Consumer.class, Integer.class);
|
||||
assertThat(write(type, type))
|
||||
.isEqualTo("ResolvableType.forClassWithGenerics(Consumer.class, Integer.class)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeExecutableParameterTypesWithConstructor() {
|
||||
Constructor<?> constructor = TestSample.class.getDeclaredConstructors()[0];
|
||||
assertThat(CodeSnippet.process(this.generator.writeExecutableParameterTypes(constructor)))
|
||||
.isEqualTo("String.class, ResourceLoader.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeExecutableParameterTypesWithNoArgConstructor() {
|
||||
Constructor<?> constructor = BeanParameterGeneratorTests.class.getDeclaredConstructors()[0];
|
||||
assertThat(CodeSnippet.process(this.generator.writeExecutableParameterTypes(constructor)))
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeExecutableParameterTypesWithMethod() {
|
||||
Method method = ReflectionUtils.findMethod(TestSample.class, "createBean", String.class, Integer.class);
|
||||
assertThat(CodeSnippet.process(this.generator.writeExecutableParameterTypes(method)))
|
||||
.isEqualTo("String.class, Integer.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeNull() {
|
||||
assertThat(write(null)).isEqualTo("null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeBeanReference() {
|
||||
BeanReference beanReference = mock(BeanReference.class);
|
||||
given(beanReference.getBeanName()).willReturn("testBean");
|
||||
assertThat(write(beanReference)).isEqualTo("new RuntimeBeanReference(\"testBean\")");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeBeanDefinitionCallsConsumer() {
|
||||
BeanParameterGenerator customGenerator = new BeanParameterGenerator(
|
||||
((beanDefinition, builder) -> builder.add("test")));
|
||||
assertThat(CodeSnippet.process(customGenerator.writeParameterValue(new RootBeanDefinition()))).isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeBeanDefinitionWithoutConsumerFails() {
|
||||
BeanParameterGenerator customGenerator = new BeanParameterGenerator();
|
||||
assertThatIllegalStateException().isThrownBy(() -> customGenerator
|
||||
.writeParameterValue(new RootBeanDefinition()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeUnsupportedParameter() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> write(new StringWriter()))
|
||||
.withMessageContaining(StringWriter.class.getName());
|
||||
}
|
||||
|
||||
private String write(Object value) {
|
||||
return CodeSnippet.process(this.generator.writeParameterValue(value));
|
||||
}
|
||||
|
||||
private String write(Object value, ResolvableType resolvableType) {
|
||||
return codeSnippet(value, resolvableType).getSnippet();
|
||||
}
|
||||
|
||||
private CodeSnippet codeSnippet(Object value, ResolvableType resolvableType) {
|
||||
return CodeSnippet.of(this.generator.writeParameterValue(value, () -> resolvableType));
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class TestSample {
|
||||
|
||||
public TestSample(String test, ResourceLoader resourceLoader) {
|
||||
}
|
||||
|
||||
String createBean(String name, Integer counter) {
|
||||
return "test";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
* 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.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generator.CodeContribution;
|
||||
import org.springframework.aot.hint.ExecutableHint;
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.TypeHint;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.EnvironmentAwareComponent;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.NoDependencyComponent;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.SimpleConfiguration;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.factory.NumberHolderFactoryBean;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.factory.SampleFactory;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.injection.InjectionComponent;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.visibility.ProtectedConstructorComponent;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.visibility.ProtectedFactoryMethod;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.support.CodeSnippet;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultBeanInstanceGenerator}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class DefaultBeanInstanceGeneratorTests {
|
||||
|
||||
@Test
|
||||
void generateUsingDefaultConstructorUsesMethodReference() {
|
||||
CodeContribution contribution = generate(SimpleConfiguration.class.getDeclaredConstructors()[0]);
|
||||
assertThat(code(contribution)).isEqualTo("SimpleConfiguration::new");
|
||||
assertThat(reflectionHints(contribution, SimpleConfiguration.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateUsingConstructorWithoutParameterAndMultipleCandidatesDoesNotUseMethodReference() throws NoSuchMethodException {
|
||||
CodeContribution contribution = generate(TestBean.class.getConstructor());
|
||||
assertThat(code(contribution)).isEqualTo("() -> new TestBean()");
|
||||
assertThat(reflectionHints(contribution, TestBean.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateUsingConstructorWithParameter() {
|
||||
Constructor<?> constructor = InjectionComponent.class.getDeclaredConstructors()[0];
|
||||
CodeContribution contribution = generate(constructor);
|
||||
assertThat(code(contribution).lines()).containsOnly(
|
||||
"(instanceContext) -> instanceContext.create(beanFactory, (attributes) -> "
|
||||
+ "new InjectionComponent(attributes.get(0)))");
|
||||
assertThat(reflectionHints(contribution, InjectionComponent.class))
|
||||
.satisfies(hasSingleQueryConstructor(constructor));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateUsingConstructorWithInnerClassAndNoExtraArg() {
|
||||
CodeContribution contribution = generate(NoDependencyComponent.class.getDeclaredConstructors()[0]);
|
||||
assertThat(code(contribution).lines()).containsOnly(
|
||||
"() -> beanFactory.getBean(InnerComponentConfiguration.class).new NoDependencyComponent()");
|
||||
assertThat(reflectionHints(contribution, NoDependencyComponent.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateUsingConstructorWithInnerClassAndExtraArg() {
|
||||
Constructor<?> constructor = EnvironmentAwareComponent.class.getDeclaredConstructors()[0];
|
||||
CodeContribution contribution = generate(constructor);
|
||||
assertThat(code(contribution).lines()).containsOnly(
|
||||
"(instanceContext) -> instanceContext.create(beanFactory, (attributes) -> "
|
||||
+ "beanFactory.getBean(InnerComponentConfiguration.class).new EnvironmentAwareComponent(attributes.get(1)))");
|
||||
assertThat(reflectionHints(contribution, EnvironmentAwareComponent.class))
|
||||
.satisfies(hasSingleQueryConstructor(constructor));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateUsingConstructorOfTypeWithGeneric() {
|
||||
CodeContribution contribution = generate(NumberHolderFactoryBean.class.getDeclaredConstructors()[0]);
|
||||
assertThat(code(contribution)).isEqualTo("NumberHolderFactoryBean::new");
|
||||
assertThat(reflectionHints(contribution, NumberHolderFactoryBean.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateUsingNoArgConstructorAndContributorsDoesNotUseMethodReference() {
|
||||
CodeContribution contribution = generate(SimpleConfiguration.class.getDeclaredConstructors()[0],
|
||||
contrib -> contrib.statements().add(CodeBlock.of("// hello\n")),
|
||||
BeanInstanceContributor.NO_OP);
|
||||
assertThat(code(contribution)).isEqualTo("""
|
||||
(instanceContext) -> {
|
||||
SimpleConfiguration bean = new SimpleConfiguration();
|
||||
// hello
|
||||
return bean;
|
||||
}""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateUsingContributorsRegisterHints() {
|
||||
CodeContribution contribution = generate(SimpleConfiguration.class.getDeclaredConstructors()[0],
|
||||
contrib -> {
|
||||
contrib.statements().add(CodeBlock.of("// hello\n"));
|
||||
contrib.runtimeHints().resources().registerPattern("com/example/*.properties");
|
||||
},
|
||||
contrib -> contrib.runtimeHints().reflection().registerType(TypeReference.of(String.class),
|
||||
hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)));
|
||||
assertThat(code(contribution)).isEqualTo("""
|
||||
(instanceContext) -> {
|
||||
SimpleConfiguration bean = new SimpleConfiguration();
|
||||
// hello
|
||||
return bean;
|
||||
}""");
|
||||
assertThat(contribution.runtimeHints().resources().resourcePatterns()).singleElement().satisfies(hint ->
|
||||
assertThat(hint.getIncludes()).containsOnly("com/example/*.properties"));
|
||||
assertThat(contribution.runtimeHints().reflection().getTypeHint(String.class)).satisfies(hint -> {
|
||||
assertThat(hint.getType()).isEqualTo(TypeReference.of(String.class));
|
||||
assertThat(hint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_PUBLIC_METHODS);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateUsingMethodWithNoArg() {
|
||||
Method method = method(SimpleConfiguration.class, "stringBean");
|
||||
CodeContribution contribution = generate(method);
|
||||
assertThat(code(contribution)).isEqualTo("() -> beanFactory.getBean(SimpleConfiguration.class).stringBean()");
|
||||
assertThat(reflectionHints(contribution, SimpleConfiguration.class))
|
||||
.satisfies(hasSingleQueryMethod(method));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateUsingStaticMethodWithNoArg() {
|
||||
Method method = method(SampleFactory.class, "integerBean");
|
||||
CodeContribution contribution = generate(method);
|
||||
assertThat(code(contribution)).isEqualTo("() -> SampleFactory.integerBean()");
|
||||
assertThat(reflectionHints(contribution, SampleFactory.class))
|
||||
.satisfies(hasSingleQueryMethod(method));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateUsingMethodWithArg() {
|
||||
Method method = method(SampleFactory.class, "create", Number.class, String.class);
|
||||
CodeContribution contribution = generate(method);
|
||||
assertThat(code(contribution)).isEqualTo("(instanceContext) -> instanceContext.create(beanFactory, (attributes) -> "
|
||||
+ "SampleFactory.create(attributes.get(0), attributes.get(1)))");
|
||||
assertThat(reflectionHints(contribution, SampleFactory.class))
|
||||
.satisfies(hasSingleQueryMethod(method));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateUsingMethodAndContributors() {
|
||||
CodeContribution contribution = generate(method(SimpleConfiguration.class, "stringBean"),
|
||||
contrib -> {
|
||||
contrib.statements().add(CodeBlock.of("// hello\n"));
|
||||
contrib.runtimeHints().resources().registerPattern("com/example/*.properties");
|
||||
},
|
||||
contrib -> contrib.runtimeHints().reflection().registerType(TypeReference.of(String.class),
|
||||
hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)));
|
||||
assertThat(code(contribution)).isEqualTo("""
|
||||
(instanceContext) -> {
|
||||
String bean = beanFactory.getBean(SimpleConfiguration.class).stringBean();
|
||||
// hello
|
||||
return bean;
|
||||
}""");
|
||||
assertThat(contribution.runtimeHints().resources().resourcePatterns()).singleElement().satisfies(hint ->
|
||||
assertThat(hint.getIncludes()).containsOnly("com/example/*.properties"));
|
||||
assertThat(contribution.runtimeHints().reflection().getTypeHint(String.class)).satisfies(hint -> {
|
||||
assertThat(hint.getType()).isEqualTo(TypeReference.of(String.class));
|
||||
assertThat(hint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_PUBLIC_METHODS);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateUsingProtectedConstructorRegistersProtectedAccess() {
|
||||
CodeContribution contribution = generate(ProtectedConstructorComponent.class.getDeclaredConstructors()[0]);
|
||||
assertThat(contribution.protectedAccess().isAccessible("com.example")).isFalse();
|
||||
assertThat(contribution.protectedAccess().getPrivilegedPackageName("com.example"))
|
||||
.isEqualTo(ProtectedConstructorComponent.class.getPackageName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateUsingProtectedMethodRegistersProtectedAccess() {
|
||||
CodeContribution contribution = generate(method(ProtectedFactoryMethod.class, "testBean", Integer.class));
|
||||
assertThat(contribution.protectedAccess().isAccessible("com.example")).isFalse();
|
||||
assertThat(contribution.protectedAccess().getPrivilegedPackageName("com.example"))
|
||||
.isEqualTo(ProtectedFactoryMethod.class.getPackageName());
|
||||
}
|
||||
|
||||
private String code(CodeContribution contribution) {
|
||||
return CodeSnippet.process(contribution.statements().toCodeBlock());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TypeHint reflectionHints(CodeContribution contribution, Class<?> type) {
|
||||
return contribution.runtimeHints().reflection().getTypeHint(type);
|
||||
}
|
||||
|
||||
private Consumer<TypeHint> hasSingleQueryConstructor(Constructor<?> constructor) {
|
||||
return typeHint -> assertThat(typeHint.constructors()).singleElement()
|
||||
.satisfies(match(constructor, "<init>", ExecutableMode.INTROSPECT));
|
||||
}
|
||||
|
||||
private Consumer<TypeHint> hasSingleQueryMethod(Method method) {
|
||||
return typeHint -> assertThat(typeHint.methods()).singleElement()
|
||||
.satisfies(match(method, method.getName(), ExecutableMode.INTROSPECT));
|
||||
}
|
||||
|
||||
private Consumer<ExecutableHint> match(Executable executable, String name, ExecutableMode... modes) {
|
||||
return hint -> {
|
||||
assertThat(hint.getName()).isEqualTo(name);
|
||||
assertThat(hint.getParameterTypes()).hasSameSizeAs(executable.getParameterTypes());
|
||||
for (int i = 0; i < hint.getParameterTypes().size(); i++) {
|
||||
assertThat(hint.getParameterTypes().get(i))
|
||||
.isEqualTo(TypeReference.of(executable.getParameterTypes()[i]));
|
||||
}
|
||||
assertThat(hint.getModes()).containsOnly(modes);
|
||||
};
|
||||
}
|
||||
|
||||
private CodeContribution generate(Executable executable,
|
||||
BeanInstanceContributor... beanInstanceContributors) {
|
||||
DefaultBeanInstanceGenerator generator = new DefaultBeanInstanceGenerator(executable,
|
||||
Arrays.asList(beanInstanceContributors));
|
||||
return generator.generateBeanInstance(new RuntimeHints());
|
||||
}
|
||||
|
||||
private static Method method(Class<?> type, String methodName, Class<?>... parameterTypes) {
|
||||
Method method = ReflectionUtils.findMethod(type, methodName, parameterTypes);
|
||||
assertThat(method).isNotNull();
|
||||
return method;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
* 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 org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.generator.InjectionGeneratorTests.SimpleConstructorBean.InnerClass;
|
||||
import org.springframework.javapoet.support.CodeSnippet;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link InjectionGenerator}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class InjectionGeneratorTests {
|
||||
|
||||
@Test
|
||||
void writeInstantiationForConstructorWithNoArgUseShortcut() {
|
||||
Constructor<?> constructor = SimpleBean.class.getDeclaredConstructors()[0];
|
||||
assertThat(writeInstantiation(constructor).lines())
|
||||
.containsExactly("new InjectionGeneratorTests.SimpleBean()");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeInstantiationForConstructorWithNonGenericParameter() {
|
||||
Constructor<?> constructor = SimpleConstructorBean.class.getDeclaredConstructors()[0];
|
||||
assertThat(writeInstantiation(constructor).lines()).containsExactly(
|
||||
"instanceContext.create(beanFactory, (attributes) -> new InjectionGeneratorTests.SimpleConstructorBean(attributes.get(0), attributes.get(1)))");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeInstantiationForConstructorWithGenericParameter() {
|
||||
Constructor<?> constructor = GenericConstructorBean.class.getDeclaredConstructors()[0];
|
||||
assertThat(writeInstantiation(constructor).lines()).containsExactly(
|
||||
"instanceContext.create(beanFactory, (attributes) -> new InjectionGeneratorTests.GenericConstructorBean(attributes.get(0)))");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeInstantiationForAmbiguousConstructor() throws Exception {
|
||||
Constructor<?> constructor = AmbiguousConstructorBean.class.getDeclaredConstructor(String.class, Number.class);
|
||||
assertThat(writeInstantiation(constructor).lines()).containsExactly(
|
||||
"instanceContext.create(beanFactory, (attributes) -> new InjectionGeneratorTests.AmbiguousConstructorBean(attributes.get(0, String.class), attributes.get(1, Number.class)))");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeInstantiationForConstructorInInnerClass() {
|
||||
Constructor<?> constructor = InnerClass.class.getDeclaredConstructors()[0];
|
||||
assertThat(writeInstantiation(constructor).lines()).containsExactly(
|
||||
"beanFactory.getBean(InjectionGeneratorTests.SimpleConstructorBean.class).new InnerClass()");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeInstantiationForMethodWithNoArgUseShortcut() {
|
||||
assertThat(writeInstantiation(method(SimpleBean.class, "name")).lines()).containsExactly(
|
||||
"beanFactory.getBean(InjectionGeneratorTests.SimpleBean.class).name()");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeInstantiationForStaticMethodWithNoArgUseShortcut() {
|
||||
assertThat(writeInstantiation(method(SimpleBean.class, "number")).lines()).containsExactly(
|
||||
"InjectionGeneratorTests.SimpleBean.number()");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeInstantiationForMethodWithNonGenericParameter() {
|
||||
assertThat(writeInstantiation(method(SampleBean.class, "source", Integer.class)).lines()).containsExactly(
|
||||
"instanceContext.create(beanFactory, (attributes) -> beanFactory.getBean(InjectionGeneratorTests.SampleBean.class).source(attributes.get(0)))");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeInstantiationForStaticMethodWithNonGenericParameter() {
|
||||
assertThat(writeInstantiation(method(SampleBean.class, "staticSource", Integer.class)).lines()).containsExactly(
|
||||
"instanceContext.create(beanFactory, (attributes) -> InjectionGeneratorTests.SampleBean.staticSource(attributes.get(0)))");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeInstantiationForMethodWithGenericParameters() {
|
||||
assertThat(writeInstantiation(method(SampleBean.class, "source", ObjectProvider.class)).lines()).containsExactly(
|
||||
"instanceContext.create(beanFactory, (attributes) -> beanFactory.getBean(InjectionGeneratorTests.SampleBean.class).source(attributes.get(0)))");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeInjectionForUnsupportedMember() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> writeInjection(mock(Member.class), false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeInjectionForNonRequiredMethodWithNonGenericParameters() {
|
||||
Method method = method(SampleBean.class, "sourceAndCounter", String.class, Integer.class);
|
||||
assertThat(writeInjection(method, false)).isEqualTo("""
|
||||
instanceContext.method("sourceAndCounter", String.class, Integer.class)
|
||||
.resolve(beanFactory, false).ifResolved((attributes) -> bean.sourceAndCounter(attributes.get(0), attributes.get(1)))""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeInjectionForRequiredMethodWithGenericParameter() {
|
||||
Method method = method(SampleBean.class, "nameAndCounter", String.class, ObjectProvider.class);
|
||||
assertThat(writeInjection(method, true)).isEqualTo("""
|
||||
instanceContext.method("nameAndCounter", String.class, ObjectProvider.class)
|
||||
.invoke(beanFactory, (attributes) -> bean.nameAndCounter(attributes.get(0), attributes.get(1)))""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeInjectionForNonRequiredMethodWithGenericParameter() {
|
||||
Method method = method(SampleBean.class, "nameAndCounter", String.class, ObjectProvider.class);
|
||||
assertThat(writeInjection(method, false)).isEqualTo("""
|
||||
instanceContext.method("nameAndCounter", String.class, ObjectProvider.class)
|
||||
.resolve(beanFactory, false).ifResolved((attributes) -> bean.nameAndCounter(attributes.get(0), attributes.get(1)))""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeInjectionForRequiredField() {
|
||||
Field field = field(SampleBean.class, "counter");
|
||||
assertThat(writeInjection(field, true)).isEqualTo("""
|
||||
instanceContext.field("counter", Integer.class)
|
||||
.invoke(beanFactory, (attributes) -> bean.counter = attributes.get(0))""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeInjectionForNonRequiredField() {
|
||||
Field field = field(SampleBean.class, "counter");
|
||||
assertThat(writeInjection(field, false)).isEqualTo("""
|
||||
instanceContext.field("counter", Integer.class)
|
||||
.resolve(beanFactory, false).ifResolved((attributes) -> bean.counter = attributes.get(0))""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeInjectionForRequiredPrivateField() {
|
||||
Field field = field(SampleBean.class, "source");
|
||||
assertThat(writeInjection(field, true)).isEqualTo("""
|
||||
instanceContext.field("source", String.class)
|
||||
.invoke(beanFactory, (attributes) -> {
|
||||
Field sourceField = ReflectionUtils.findField(InjectionGeneratorTests.SampleBean.class, "source", String.class);
|
||||
ReflectionUtils.makeAccessible(sourceField);
|
||||
ReflectionUtils.setField(sourceField, bean, attributes.get(0));
|
||||
})""");
|
||||
}
|
||||
|
||||
|
||||
private Method method(Class<?> type, String name, Class<?>... parameterTypes) {
|
||||
Method method = ReflectionUtils.findMethod(type, name, parameterTypes);
|
||||
assertThat(method).isNotNull();
|
||||
return method;
|
||||
}
|
||||
|
||||
private Field field(Class<?> type, String name) {
|
||||
Field field = ReflectionUtils.findField(type, name);
|
||||
assertThat(field).isNotNull();
|
||||
return field;
|
||||
}
|
||||
|
||||
private String writeInstantiation(Executable creator) {
|
||||
return CodeSnippet.process(code -> code.add(new InjectionGenerator().writeInstantiation(creator)));
|
||||
}
|
||||
|
||||
private String writeInjection(Member member, boolean required) {
|
||||
return CodeSnippet.process(code -> code.add(new InjectionGenerator().writeInjection(member, required)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class SampleBean {
|
||||
|
||||
private String source;
|
||||
|
||||
Integer counter;
|
||||
|
||||
|
||||
void sourceAndCounter(String source, Integer counter) {
|
||||
|
||||
}
|
||||
|
||||
void nameAndCounter(String name, ObjectProvider<Integer> counter) {
|
||||
|
||||
}
|
||||
|
||||
String source(Integer counter) {
|
||||
return "source" + counter;
|
||||
}
|
||||
|
||||
String source(ObjectProvider<Integer> counter) {
|
||||
return "source" + counter.getIfAvailable(() -> 0);
|
||||
}
|
||||
|
||||
static String staticSource(Integer counter) {
|
||||
return counter + "source";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class SimpleBean {
|
||||
|
||||
String name() {
|
||||
return "test";
|
||||
}
|
||||
|
||||
static Integer number() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class SimpleConstructorBean {
|
||||
|
||||
private final String source;
|
||||
|
||||
private final Integer counter;
|
||||
|
||||
public SimpleConstructorBean(String source, Integer counter) {
|
||||
this.source = source;
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
class InnerClass {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class GenericConstructorBean {
|
||||
|
||||
private final ObjectProvider<Integer> counter;
|
||||
|
||||
GenericConstructorBean(ObjectProvider<Integer> counter) {
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class AmbiguousConstructorBean {
|
||||
|
||||
AmbiguousConstructorBean(String first, String second) {
|
||||
|
||||
}
|
||||
|
||||
AmbiguousConstructorBean(String first, Number second) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,458 @@
|
|||
/*
|
||||
* 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.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import org.springframework.beans.FatalBeanException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.beans.factory.generator.config.BeanDefinitionRegistrar.BeanInstanceContext;
|
||||
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.ResolvableType;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link BeanDefinitionRegistrar}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class BeanDefinitionRegistrarTests {
|
||||
|
||||
@Test
|
||||
void beanDefinitionWithBeanClassDoesNotSetTargetType() {
|
||||
RootBeanDefinition beanDefinition = BeanDefinitionRegistrar.of("test", String.class).toBeanDefinition();
|
||||
assertThat(beanDefinition.getBeanClass()).isEqualTo(String.class);
|
||||
assertThat(beanDefinition.getTargetType()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void beanDefinitionWithResolvableTypeSetsTargetType() {
|
||||
ResolvableType targetType = ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class);
|
||||
RootBeanDefinition beanDefinition = BeanDefinitionRegistrar.of("test", targetType).toBeanDefinition();
|
||||
assertThat(beanDefinition.getTargetType()).isNotNull().isEqualTo(NumberHolder.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithSimpleInstanceSupplier() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
BeanDefinitionRegistrar.of("test", InjectionSample.class)
|
||||
.instanceSupplier(InjectionSample::new).register(beanFactory);
|
||||
assertBeanFactory(beanFactory, () -> {
|
||||
assertThat(beanFactory.containsBean("test")).isTrue();
|
||||
assertThat(beanFactory.getBean(InjectionSample.class)).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithSimpleInstanceSupplierThatThrowsRuntimeException() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
Exception exception = new IllegalArgumentException("test exception");
|
||||
BeanDefinitionRegistrar.of("testBean", InjectionSample.class)
|
||||
.instanceSupplier(() -> {
|
||||
throw exception;
|
||||
}).register(beanFactory);
|
||||
assertThatThrownBy(() -> beanFactory.getBean("testBean")).isInstanceOf(BeanCreationException.class)
|
||||
.getRootCause().isEqualTo(exception);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithSimpleInstanceSupplierThatThrowsCheckedException() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
Exception exception = new IOException("test exception");
|
||||
BeanDefinitionRegistrar.of("testBean", InjectionSample.class)
|
||||
.instanceSupplier(() -> {
|
||||
throw exception;
|
||||
}).register(beanFactory);
|
||||
assertThatThrownBy(() -> beanFactory.getBean("testBean")).isInstanceOf(BeanCreationException.class)
|
||||
.getRootCause().isEqualTo(exception);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithoutBeanNameFails() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
BeanDefinitionRegistrar registrar = BeanDefinitionRegistrar.inner(InjectionSample.class)
|
||||
.instanceSupplier(InjectionSample::new);
|
||||
assertThatIllegalStateException().isThrownBy(() -> registrar.register(beanFactory))
|
||||
.withMessageContaining("Bean name not set.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void registerWithCustomizer() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
BeanDefinitionRegistrar.ThrowableConsumer<RootBeanDefinition> first = mock(BeanDefinitionRegistrar.ThrowableConsumer.class);
|
||||
BeanDefinitionRegistrar.ThrowableConsumer<RootBeanDefinition> second = mock(BeanDefinitionRegistrar.ThrowableConsumer.class);
|
||||
BeanDefinitionRegistrar.of("test", InjectionSample.class)
|
||||
.instanceSupplier(InjectionSample::new).customize(first).customize(second).register(beanFactory);
|
||||
assertBeanFactory(beanFactory, () -> {
|
||||
assertThat(beanFactory.containsBean("test")).isTrue();
|
||||
InOrder ordered = inOrder(first, second);
|
||||
ordered.verify(first).accept(any(RootBeanDefinition.class));
|
||||
ordered.verify(second).accept(any(RootBeanDefinition.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithCustomizerThatThrowsRuntimeException() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
Exception exception = new RuntimeException("test exception");
|
||||
BeanDefinitionRegistrar registrar = BeanDefinitionRegistrar.of("test", InjectionSample.class)
|
||||
.instanceSupplier(InjectionSample::new).customize(bd -> {
|
||||
throw exception;
|
||||
});
|
||||
assertThatThrownBy(() -> registrar.register(beanFactory)).isInstanceOf(FatalBeanException.class)
|
||||
.hasMessageContaining("Failed to create bean definition for bean with name 'test'")
|
||||
.hasMessageContaining("test exception")
|
||||
.hasCause(exception);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithCustomizerThatThrowsCheckedException() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
Exception exception = new IOException("test exception");
|
||||
BeanDefinitionRegistrar registrar = BeanDefinitionRegistrar.of("test", InjectionSample.class)
|
||||
.instanceSupplier(InjectionSample::new).customize(bd -> {
|
||||
throw exception;
|
||||
});
|
||||
assertThatThrownBy(() -> registrar.register(beanFactory)).isInstanceOf(FatalBeanException.class)
|
||||
.hasMessageContaining("Failed to create bean definition for bean with name 'test'")
|
||||
.hasMessageContaining("test exception");
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithConstructorInstantiation() {
|
||||
ResourceLoader resourceLoader = new DefaultResourceLoader();
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerResolvableDependency(ResourceLoader.class, resourceLoader);
|
||||
BeanDefinitionRegistrar.of("test", ConstructorSample.class).withConstructor(ResourceLoader.class)
|
||||
.instanceSupplier(instanceContext -> instanceContext.create(beanFactory, attributes ->
|
||||
new ConstructorSample(attributes.get(0)))).register(beanFactory);
|
||||
assertBeanFactory(beanFactory, () -> {
|
||||
assertThat(beanFactory.containsBean("test")).isTrue();
|
||||
assertThat(beanFactory.getBean(ConstructorSample.class).resourceLoader).isEqualTo(resourceLoader);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithConstructorInstantiationThatThrowsRuntimeException() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
Exception exception = new RuntimeException("test exception");
|
||||
BeanDefinitionRegistrar.of("test", ConstructorSample.class).withConstructor(ResourceLoader.class)
|
||||
.instanceSupplier(instanceContext -> {
|
||||
throw exception;
|
||||
}).register(beanFactory);
|
||||
assertThatThrownBy(() -> beanFactory.getBean("test")).isInstanceOf(BeanCreationException.class)
|
||||
.getRootCause().isEqualTo(exception);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithConstructorInstantiationThatThrowsCheckedException() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
Exception exception = new IOException("test exception");
|
||||
BeanDefinitionRegistrar.of("test", ConstructorSample.class).withConstructor(ResourceLoader.class)
|
||||
.instanceSupplier(instanceContext -> {
|
||||
throw exception;
|
||||
}).register(beanFactory);
|
||||
assertThatThrownBy(() -> beanFactory.getBean("test")).isInstanceOf(BeanCreationException.class)
|
||||
.getRootCause().isEqualTo(exception);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithConstructorOnInnerClass() {
|
||||
Environment environment = mock(Environment.class);
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerSingleton("environment", environment);
|
||||
beanFactory.registerBeanDefinition("sample", BeanDefinitionBuilder.rootBeanDefinition(InnerClassSample.class).getBeanDefinition());
|
||||
BeanDefinitionRegistrar.of("test", InnerClassSample.Inner.class).withConstructor(InnerClassSample.class, Environment.class)
|
||||
.instanceSupplier(instanceContext -> instanceContext.create(beanFactory, attributes ->
|
||||
beanFactory.getBean(InnerClassSample.class).new Inner(attributes.get(1))))
|
||||
.register(beanFactory);
|
||||
assertBeanFactory(beanFactory, () -> {
|
||||
assertThat(beanFactory.containsBean("test")).isTrue();
|
||||
InnerClassSample.Inner bean = beanFactory.getBean(InnerClassSample.Inner.class);
|
||||
assertThat(bean.environment).isEqualTo(environment);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithInvalidConstructor() {
|
||||
assertThatThrownBy(() -> BeanDefinitionRegistrar.of("test", ConstructorSample.class).withConstructor(Object.class))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("No constructor with type(s) [java.lang.Object] found on")
|
||||
.hasMessageContaining(ConstructorSample.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithFactoryMethod() {
|
||||
ResourceLoader resourceLoader = new DefaultResourceLoader();
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerResolvableDependency(ResourceLoader.class, resourceLoader);
|
||||
BeanDefinitionRegistrar.of("configuration", ConfigurationSample.class).instanceSupplier(ConfigurationSample::new)
|
||||
.register(beanFactory);
|
||||
BeanDefinitionRegistrar.of("test", ConstructorSample.class)
|
||||
.withFactoryMethod(ConfigurationSample.class, "sampleBean", ResourceLoader.class)
|
||||
.instanceSupplier(instanceContext -> instanceContext.create(beanFactory, attributes ->
|
||||
beanFactory.getBean(ConfigurationSample.class).sampleBean(attributes.get(0))))
|
||||
.register(beanFactory);
|
||||
assertBeanFactory(beanFactory, () -> {
|
||||
assertThat(beanFactory.containsBean("configuration")).isTrue();
|
||||
assertThat(beanFactory.containsBean("test")).isTrue();
|
||||
assertThat(beanFactory.getBean(ConstructorSample.class).resourceLoader).isEqualTo(resourceLoader);
|
||||
RootBeanDefinition bd = (RootBeanDefinition) beanFactory.getBeanDefinition("test");
|
||||
assertThat(bd.getResolvedFactoryMethod()).isNotNull().isEqualTo(
|
||||
ReflectionUtils.findMethod(ConfigurationSample.class, "sampleBean", ResourceLoader.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithCreateShortcutWithoutFactoryMethod() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
BeanDefinitionRegistrar.of("configuration", ConfigurationSample.class).instanceSupplier(ConfigurationSample::new)
|
||||
.register(beanFactory);
|
||||
BeanDefinitionRegistrar.of("test", ConstructorSample.class)
|
||||
.instanceSupplier(instanceContext -> instanceContext.create(beanFactory, attributes ->
|
||||
beanFactory.getBean(ConfigurationSample.class).sampleBean(attributes.get(0))))
|
||||
.register(beanFactory);
|
||||
assertThatThrownBy(() -> beanFactory.getBean("test")).isInstanceOf(BeanCreationException.class)
|
||||
.hasMessageContaining("No factory method or constructor is set");
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithInjectedField() {
|
||||
Environment environment = mock(Environment.class);
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerSingleton("environment", environment);
|
||||
BeanDefinitionRegistrar.of("test", InjectionSample.class).instanceSupplier(instanceContext -> {
|
||||
InjectionSample bean = new InjectionSample();
|
||||
instanceContext.field("environment", Environment.class).invoke(beanFactory,
|
||||
attributes -> bean.environment = (attributes.get(0)));
|
||||
return bean;
|
||||
}).register(beanFactory);
|
||||
assertBeanFactory(beanFactory, () -> {
|
||||
assertThat(beanFactory.containsBean("test")).isTrue();
|
||||
assertThat(beanFactory.getBean(InjectionSample.class).environment).isEqualTo(environment);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithInvalidField() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
BeanDefinitionRegistrar.of("test", InjectionSample.class).instanceSupplier(instanceContext ->
|
||||
instanceContext.field("doesNotExist", Object.class).resolve(beanFactory)).register(beanFactory);
|
||||
assertThatThrownBy(() -> beanFactory.getBean(InjectionSample.class)
|
||||
).isInstanceOf(BeanCreationException.class)
|
||||
.hasMessageContaining("No field '%s' with type %s found", "doesNotExist", Object.class.getName())
|
||||
.hasMessageContaining(InjectionSample.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithInjectedMethod() {
|
||||
Environment environment = mock(Environment.class);
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerSingleton("environment", environment);
|
||||
BeanDefinitionRegistrar.of("test", InjectionSample.class).instanceSupplier(instanceContext -> {
|
||||
InjectionSample bean = new InjectionSample();
|
||||
instanceContext.method("setEnvironment", Environment.class).invoke(beanFactory,
|
||||
attributes -> bean.setEnvironment(attributes.get(0)));
|
||||
return bean;
|
||||
}).register(beanFactory);
|
||||
assertBeanFactory(beanFactory, () -> {
|
||||
assertThat(beanFactory.containsBean("test")).isTrue();
|
||||
assertThat(beanFactory.getBean(InjectionSample.class).environment).isEqualTo(environment);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithInvalidMethod() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
assertThatThrownBy(() -> {
|
||||
BeanDefinitionRegistrar.of("test", InjectionSample.class).instanceSupplier(instanceContext ->
|
||||
instanceContext.method("setEnvironment", Object.class).resolve(beanFactory)).register(beanFactory);
|
||||
beanFactory.getBean(InjectionSample.class);
|
||||
}
|
||||
).isInstanceOf(BeanCreationException.class)
|
||||
.hasMessageContaining("No method '%s' with type(s) [%s] found", "setEnvironment", Object.class.getName())
|
||||
.hasMessageContaining(InjectionSample.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void innerBeanDefinitionWithClass() {
|
||||
RootBeanDefinition beanDefinition = BeanDefinitionRegistrar.inner(ConfigurationSample.class)
|
||||
.customize(bd -> bd.setSynthetic(true)).toBeanDefinition();
|
||||
assertThat(beanDefinition).isNotNull();
|
||||
assertThat(beanDefinition.getResolvableType().resolve()).isEqualTo(ConfigurationSample.class);
|
||||
assertThat(beanDefinition.isSynthetic()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void innerBeanDefinitionWithResolvableType() {
|
||||
RootBeanDefinition beanDefinition = BeanDefinitionRegistrar.inner(ResolvableType.forClass(ConfigurationSample.class))
|
||||
.customize(bd -> bd.setDescription("test")).toBeanDefinition();
|
||||
assertThat(beanDefinition).isNotNull();
|
||||
assertThat(beanDefinition.getResolvableType().resolve()).isEqualTo(ConfigurationSample.class);
|
||||
assertThat(beanDefinition.getDescription()).isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void innerBeanDefinitionHasInnerBeanNameInInstanceSupplier() {
|
||||
RootBeanDefinition beanDefinition = BeanDefinitionRegistrar.inner(String.class)
|
||||
.instanceSupplier(instanceContext -> {
|
||||
Field field = ReflectionUtils.findField(BeanInstanceContext.class, "beanName", String.class);
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
return ReflectionUtils.getField(field, instanceContext);
|
||||
}).toBeanDefinition();
|
||||
assertThat(beanDefinition).isNotNull();
|
||||
String beanName = (String) beanDefinition.getInstanceSupplier().get();
|
||||
assertThat(beanName).isNotNull().startsWith("(inner bean)#");
|
||||
}
|
||||
|
||||
|
||||
private void assertBeanFactory(DefaultListableBeanFactory beanFactory, Runnable assertions) {
|
||||
assertions.run();
|
||||
}
|
||||
|
||||
|
||||
static class ConfigurationSample {
|
||||
|
||||
ConstructorSample sampleBean(ResourceLoader resourceLoader) {
|
||||
return new ConstructorSample(resourceLoader);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ConstructorSample {
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
ConstructorSample(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
}
|
||||
|
||||
static class MultiArgConstructorSample {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final Integer counter;
|
||||
|
||||
public MultiArgConstructorSample(String name, Integer counter) {
|
||||
this.name = name;
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class InjectionSample {
|
||||
|
||||
private Environment environment;
|
||||
|
||||
private String name;
|
||||
|
||||
private Integer counter;
|
||||
|
||||
void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
void setNameAndCounter(@Value("${test.name:test}") String name, @Value("${test.counter:42}") Integer counter) {
|
||||
this.name = name;
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class InnerClassSample {
|
||||
|
||||
class Inner {
|
||||
|
||||
private Environment environment;
|
||||
|
||||
Inner(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class GenericFactoryBeanConfiguration {
|
||||
|
||||
FactoryBean<NumberHolder<?>> integerHolderFactory() {
|
||||
return new GenericFactoryBean<>(integerHolder());
|
||||
}
|
||||
|
||||
NumberHolder<?> integerHolder() {
|
||||
return new NumberHolder<>(42);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class GenericFactoryBean<T> implements FactoryBean<T> {
|
||||
|
||||
private final T value;
|
||||
|
||||
public GenericFactoryBean(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getObject() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return this.value.getClass();
|
||||
}
|
||||
}
|
||||
|
||||
static class NumberHolder<N extends Number> {
|
||||
|
||||
private final N number;
|
||||
|
||||
public NumberHolder(N number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class NumberHolderSample {
|
||||
|
||||
@Autowired
|
||||
private NumberHolder<Integer> numberHolder;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,476 @@
|
|||
/*
|
||||
* 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.Method;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.assertj.core.util.Arrays;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
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.env.Environment;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link InjectedConstructionResolver}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class InjectedConstructionResolverTests {
|
||||
|
||||
@Test
|
||||
void resolveNoArgConstructor() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
InjectedElementAttributes attributes = createResolverForConstructor(
|
||||
InjectedConstructionResolverTests.class).resolve(beanFactory);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("singleArgConstruction")
|
||||
void resolveSingleArgConstructor(InjectedConstructionResolver resolver) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerSingleton("one", "1");
|
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
assertThat((String) attributes.get(0)).isEqualTo("1");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("singleArgConstruction")
|
||||
void resolveRequiredDependencyNotPresentThrowsUnsatisfiedDependencyException(InjectedConstructionResolver resolver) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
assertThatThrownBy(() -> resolver.resolve(beanFactory))
|
||||
.isInstanceOfSatisfying(UnsatisfiedDependencyException.class, ex -> {
|
||||
assertThat(ex.getBeanName()).isEqualTo("test");
|
||||
assertThat(ex.getInjectionPoint()).isNotNull();
|
||||
assertThat(ex.getInjectionPoint().getMember()).isEqualTo(resolver.getExecutable());
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("arrayOfBeansConstruction")
|
||||
void resolveArrayOfBeans(InjectedConstructionResolver resolver) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerSingleton("one", "1");
|
||||
beanFactory.registerSingleton("two", "2");
|
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
Object attribute = attributes.get(0);
|
||||
assertThat(Arrays.isArray(attribute)).isTrue();
|
||||
assertThat((Object[]) attribute).containsExactly("1", "2");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("arrayOfBeansConstruction")
|
||||
void resolveRequiredArrayOfBeansInjectEmptyArray(InjectedConstructionResolver resolver) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
Object attribute = attributes.get(0);
|
||||
assertThat(Arrays.isArray(attribute)).isTrue();
|
||||
assertThat((Object[]) attribute).isEmpty();
|
||||
|
||||
}
|
||||
|
||||
static Stream<Arguments> arrayOfBeansConstruction() {
|
||||
return Stream.of(Arguments.of(createResolverForConstructor(BeansCollectionConstructor.class, String[].class)),
|
||||
Arguments.of(createResolverForFactoryMethod(BeansCollectionFactory.class, "array", String[].class)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("listOfBeansConstruction")
|
||||
void resolveListOfBeans(InjectedConstructionResolver resolver) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerSingleton("one", "1");
|
||||
beanFactory.registerSingleton("two", "2");
|
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
Object attribute = attributes.get(0);
|
||||
assertThat(attribute).isInstanceOf(List.class).asList().containsExactly("1", "2");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("listOfBeansConstruction")
|
||||
void resolveRequiredListOfBeansInjectEmptyList(InjectedConstructionResolver resolver) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
Object attribute = attributes.get(0);
|
||||
assertThat(attribute).isInstanceOf(List.class);
|
||||
assertThat((List<?>) attribute).isEmpty();
|
||||
}
|
||||
|
||||
static Stream<Arguments> listOfBeansConstruction() {
|
||||
return Stream.of(Arguments.of(createResolverForConstructor(BeansCollectionConstructor.class, List.class)),
|
||||
Arguments.of(createResolverForFactoryMethod(BeansCollectionFactory.class, "list", List.class)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("setOfBeansConstruction")
|
||||
@SuppressWarnings("unchecked")
|
||||
void resolveSetOfBeans(InjectedConstructionResolver resolver) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerSingleton("one", "1");
|
||||
beanFactory.registerSingleton("two", "2");
|
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
Object attribute = attributes.get(0);
|
||||
assertThat(attribute).isInstanceOf(Set.class);
|
||||
assertThat((Set<String>) attribute).containsExactly("1", "2");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("setOfBeansConstruction")
|
||||
void resolveRequiredSetOfBeansInjectEmptySet(InjectedConstructionResolver resolver) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
Object attribute = attributes.get(0);
|
||||
assertThat(attribute).isInstanceOf(Set.class);
|
||||
assertThat((Set<?>) attribute).isEmpty();
|
||||
}
|
||||
|
||||
static Stream<Arguments> setOfBeansConstruction() {
|
||||
return Stream.of(Arguments.of(createResolverForConstructor(BeansCollectionConstructor.class, Set.class)),
|
||||
Arguments.of(createResolverForFactoryMethod(BeansCollectionFactory.class, "set", Set.class)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("mapOfBeansConstruction")
|
||||
@SuppressWarnings("unchecked")
|
||||
void resolveMapOfBeans(InjectedConstructionResolver resolver) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerSingleton("one", "1");
|
||||
beanFactory.registerSingleton("two", "2");
|
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
Object attribute = attributes.get(0);
|
||||
assertThat(attribute).isInstanceOf(Map.class);
|
||||
assertThat((Map<String, String>) attribute).containsExactly(entry("one", "1"), entry("two", "2"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("mapOfBeansConstruction")
|
||||
void resolveRequiredMapOfBeansInjectEmptySet(InjectedConstructionResolver resolver) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
Object attribute = attributes.get(0);
|
||||
assertThat(attribute).isInstanceOf(Map.class);
|
||||
assertThat((Map<?, ?>) attribute).isEmpty();
|
||||
}
|
||||
|
||||
static Stream<Arguments> mapOfBeansConstruction() {
|
||||
return Stream.of(Arguments.of(createResolverForConstructor(BeansCollectionConstructor.class, Map.class)),
|
||||
Arguments.of(createResolverForFactoryMethod(BeansCollectionFactory.class, "map", Map.class)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("multiArgsConstruction")
|
||||
void resolveMultiArgsConstructor(InjectedConstructionResolver resolver) {
|
||||
ResourceLoader resourceLoader = new DefaultResourceLoader();
|
||||
Environment environment = mock(Environment.class);
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerResolvableDependency(ResourceLoader.class, resourceLoader);
|
||||
beanFactory.registerSingleton("environment", environment);
|
||||
beanFactory.registerSingleton("one", "1");
|
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
assertThat((ResourceLoader) attributes.get(0)).isEqualTo(resourceLoader);
|
||||
assertThat((Environment) attributes.get(1)).isEqualTo(environment);
|
||||
ObjectProvider<String> provider = attributes.get(2);
|
||||
assertThat(provider.getIfAvailable()).isEqualTo("1");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("mixedArgsConstruction")
|
||||
void resolveMixedArgsConstructorWithUserValue(InjectedConstructionResolver resolver) {
|
||||
ResourceLoader resourceLoader = new DefaultResourceLoader();
|
||||
Environment environment = mock(Environment.class);
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerResolvableDependency(ResourceLoader.class, resourceLoader);
|
||||
beanFactory.registerSingleton("environment", environment);
|
||||
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(MixedArgsConstructor.class)
|
||||
.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR).getBeanDefinition();
|
||||
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1, "user-value");
|
||||
beanFactory.registerBeanDefinition("test", beanDefinition);
|
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
assertThat((ResourceLoader) attributes.get(0)).isEqualTo(resourceLoader);
|
||||
assertThat((String) attributes.get(1)).isEqualTo("user-value");
|
||||
assertThat((Environment) attributes.get(2)).isEqualTo(environment);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("mixedArgsConstruction")
|
||||
void resolveMixedArgsConstructorWithUserBeanReference(InjectedConstructionResolver resolver) {
|
||||
ResourceLoader resourceLoader = new DefaultResourceLoader();
|
||||
Environment environment = mock(Environment.class);
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerResolvableDependency(ResourceLoader.class, resourceLoader);
|
||||
beanFactory.registerSingleton("environment", environment);
|
||||
beanFactory.registerSingleton("one", "1");
|
||||
beanFactory.registerSingleton("two", "2");
|
||||
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(MixedArgsConstructor.class)
|
||||
.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR).getBeanDefinition();
|
||||
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1, new RuntimeBeanReference("two"));
|
||||
beanFactory.registerBeanDefinition("test", beanDefinition);
|
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
assertThat((ResourceLoader) attributes.get(0)).isEqualTo(resourceLoader);
|
||||
assertThat((String) attributes.get(1)).isEqualTo("2");
|
||||
assertThat((Environment) attributes.get(2)).isEqualTo(environment);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveUserValueWithTypeConversionRequired() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(CharDependency.class)
|
||||
.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR).getBeanDefinition();
|
||||
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, "\\");
|
||||
beanFactory.registerBeanDefinition("test", beanDefinition);
|
||||
InjectedElementAttributes attributes = createResolverForConstructor(CharDependency.class, char.class).resolve(beanFactory);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
Object attribute = attributes.get(0);
|
||||
assertThat(attribute).isInstanceOf(Character.class);
|
||||
assertThat((Character) attribute).isEqualTo('\\');
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("singleArgConstruction")
|
||||
void resolveUserValueWithBeanReference(InjectedConstructionResolver resolver) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerSingleton("stringBean", "string");
|
||||
beanFactory.registerBeanDefinition("test", BeanDefinitionBuilder.rootBeanDefinition(SingleArgConstructor.class)
|
||||
.addConstructorArgReference("stringBean").getBeanDefinition());
|
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
Object attribute = attributes.get(0);
|
||||
assertThat(attribute).isEqualTo("string");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("singleArgConstruction")
|
||||
void resolveUserValueWithBeanDefinition(InjectedConstructionResolver resolver) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
AbstractBeanDefinition userValue = BeanDefinitionBuilder.rootBeanDefinition(String.class, () -> "string").getBeanDefinition();
|
||||
beanFactory.registerBeanDefinition("test", BeanDefinitionBuilder.rootBeanDefinition(SingleArgConstructor.class)
|
||||
.addConstructorArgValue(userValue).getBeanDefinition());
|
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
Object attribute = attributes.get(0);
|
||||
assertThat(attribute).isEqualTo("string");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("singleArgConstruction")
|
||||
void resolveUserValueThatIsAlreadyResolved(InjectedConstructionResolver resolver) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(SingleArgConstructor.class).getBeanDefinition();
|
||||
ValueHolder valueHolder = new ValueHolder('a');
|
||||
valueHolder.setConvertedValue("this is an a");
|
||||
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, valueHolder);
|
||||
beanFactory.registerBeanDefinition("test", beanDefinition);
|
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
Object attribute = attributes.get(0);
|
||||
assertThat(attribute).isEqualTo("this is an a");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("singleArgConstruction")
|
||||
void createInvokeFactory(InjectedConstructionResolver resolver) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerSingleton("one", "1");
|
||||
String instance = resolver.create(beanFactory, attributes -> attributes.get(0));
|
||||
assertThat(instance).isEqualTo("1");
|
||||
}
|
||||
|
||||
private static InjectedConstructionResolver createResolverForConstructor(Class<?> beanType, Class<?>... parameterTypes) {
|
||||
try {
|
||||
Constructor<?> executable = beanType.getDeclaredConstructor(parameterTypes);
|
||||
return new InjectedConstructionResolver(executable, beanType, "test",
|
||||
InjectedConstructionResolverTests::safeGetBeanDefinition);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static InjectedConstructionResolver createResolverForFactoryMethod(Class<?> targetType,
|
||||
String methodName, Class<?>... parameterTypes) {
|
||||
Method executable = ReflectionUtils.findMethod(targetType, methodName, parameterTypes);
|
||||
return new InjectedConstructionResolver(executable, targetType, "test",
|
||||
InjectedConstructionResolverTests::safeGetBeanDefinition);
|
||||
}
|
||||
|
||||
private static BeanDefinition safeGetBeanDefinition(DefaultListableBeanFactory beanFactory) {
|
||||
try {
|
||||
return beanFactory.getBeanDefinition("test");
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Stream<Arguments> singleArgConstruction() {
|
||||
return Stream.of(Arguments.of(createResolverForConstructor(SingleArgConstructor.class, String.class)),
|
||||
Arguments.of(createResolverForFactoryMethod(SingleArgFactory.class, "single", String.class)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class SingleArgConstructor {
|
||||
|
||||
public SingleArgConstructor(String s) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class SingleArgFactory {
|
||||
|
||||
String single(String s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class BeansCollectionConstructor {
|
||||
|
||||
public BeansCollectionConstructor(String[] beans) {
|
||||
|
||||
}
|
||||
|
||||
public BeansCollectionConstructor(List<String> beans) {
|
||||
|
||||
}
|
||||
|
||||
public BeansCollectionConstructor(Set<String> beans) {
|
||||
|
||||
}
|
||||
|
||||
public BeansCollectionConstructor(Map<String, String> beans) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class BeansCollectionFactory {
|
||||
|
||||
public String array(String[] beans) {
|
||||
return "test";
|
||||
}
|
||||
|
||||
public String list(List<String> beans) {
|
||||
return "test";
|
||||
}
|
||||
|
||||
public String set(Set<String> beans) {
|
||||
return "test";
|
||||
}
|
||||
|
||||
public String map(Map<String, String> beans) {
|
||||
return "test";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static Stream<Arguments> multiArgsConstruction() {
|
||||
return Stream.of(
|
||||
Arguments.of(createResolverForConstructor(MultiArgsConstructor.class, ResourceLoader.class,
|
||||
Environment.class, ObjectProvider.class)),
|
||||
Arguments.of(createResolverForFactoryMethod(MultiArgsFactory.class, "multiArgs", ResourceLoader.class,
|
||||
Environment.class, ObjectProvider.class)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class MultiArgsConstructor {
|
||||
|
||||
public MultiArgsConstructor(ResourceLoader resourceLoader, Environment environment, ObjectProvider<String> provider) {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class MultiArgsFactory {
|
||||
|
||||
String multiArgs(ResourceLoader resourceLoader, Environment environment, ObjectProvider<String> provider) {
|
||||
return "test";
|
||||
}
|
||||
}
|
||||
|
||||
static Stream<Arguments> mixedArgsConstruction() {
|
||||
return Stream.of(
|
||||
Arguments.of(createResolverForConstructor(MixedArgsConstructor.class, ResourceLoader.class,
|
||||
String.class, Environment.class)),
|
||||
Arguments.of(createResolverForFactoryMethod(MixedArgsFactory.class, "mixedArgs", ResourceLoader.class,
|
||||
String.class, Environment.class)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class MixedArgsConstructor {
|
||||
|
||||
public MixedArgsConstructor(ResourceLoader resourceLoader, String test, Environment environment) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class MixedArgsFactory {
|
||||
|
||||
String mixedArgs(ResourceLoader resourceLoader, String test, Environment environment) {
|
||||
return "test";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class CharDependency {
|
||||
|
||||
CharDependency(char escapeChar) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link InjectedElementAttributes}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class InjectedElementAttributesTests {
|
||||
|
||||
private static final InjectedElementAttributes unresolved = new InjectedElementAttributes(null);
|
||||
|
||||
private static final InjectedElementAttributes resolved = new InjectedElementAttributes(Collections.singletonList("test"));
|
||||
|
||||
@Test
|
||||
void isResolvedWithUnresolvedAttributes() {
|
||||
assertThat(unresolved.isResolved()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isResolvedWithResoledAttributes() {
|
||||
assertThat(resolved.isResolved()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ifResolvedWithUnresolvedAttributesDoesNotInvokeRunnable() {
|
||||
Runnable runnable = mock(Runnable.class);
|
||||
unresolved.ifResolved(runnable);
|
||||
verifyNoInteractions(runnable);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ifResolvedWithResolvedAttributesInvokesRunnable() {
|
||||
Runnable runnable = mock(Runnable.class);
|
||||
resolved.ifResolved(runnable);
|
||||
verify(runnable).run();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void ifResolvedWithUnresolvedAttributesDoesNotInvokeConsumer() {
|
||||
BeanDefinitionRegistrar.ThrowableConsumer<InjectedElementAttributes> consumer = mock(BeanDefinitionRegistrar.ThrowableConsumer.class);
|
||||
unresolved.ifResolved(consumer);
|
||||
verifyNoInteractions(consumer);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void ifResolvedWithResolvedAttributesInvokesConsumer() {
|
||||
BeanDefinitionRegistrar.ThrowableConsumer<InjectedElementAttributes> consumer = mock(BeanDefinitionRegistrar.ThrowableConsumer.class);
|
||||
resolved.ifResolved(consumer);
|
||||
verify(consumer).accept(resolved);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithAvailableAttribute() {
|
||||
InjectedElementAttributes attributes = new InjectedElementAttributes(Collections.singletonList("test"));
|
||||
assertThat((String) attributes.get(0)).isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithTypeAndAvailableAttribute() {
|
||||
InjectedElementAttributes attributes = new InjectedElementAttributes(Collections.singletonList("test"));
|
||||
assertThat(attributes.get(0, String.class)).isEqualTo("test");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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 org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link InjectedFieldResolver}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class InjectedFieldResolverTests {
|
||||
|
||||
@Test
|
||||
void resolveDependency() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerSingleton("one", "1");
|
||||
InjectedElementAttributes attributes = createResolver(TestBean.class, "string",
|
||||
String.class).resolve(beanFactory, true);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
assertThat((String) attributes.get(0)).isEqualTo("1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRequiredDependencyNotPresentThrowsUnsatisfiedDependencyException() {
|
||||
Field field = ReflectionUtils.findField(TestBean.class, "string", String.class);
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
assertThatThrownBy(() -> createResolver(TestBean.class, "string", String.class).resolve(beanFactory))
|
||||
.isInstanceOfSatisfying(UnsatisfiedDependencyException.class, ex -> {
|
||||
assertThat(ex.getBeanName()).isEqualTo("test");
|
||||
assertThat(ex.getInjectionPoint()).isNotNull();
|
||||
assertThat(ex.getInjectionPoint().getField()).isEqualTo(field);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveNonRequiredDependency() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
InjectedElementAttributes attributes = createResolver(TestBean.class, "string", String.class).resolve(beanFactory, false);
|
||||
assertThat(attributes.isResolved()).isFalse();
|
||||
}
|
||||
|
||||
private InjectedFieldResolver createResolver(Class<?> beanType, String fieldName, Class<?> fieldType) {
|
||||
Field field = ReflectionUtils.findField(beanType, fieldName, fieldType);
|
||||
assertThat(field).isNotNull();
|
||||
return new InjectedFieldResolver(field, "test");
|
||||
}
|
||||
|
||||
static class TestBean {
|
||||
|
||||
private String string;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* 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 org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link InjectedMethodResolver}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class InjectedMethodResolverTests {
|
||||
|
||||
@Test
|
||||
void resolveSingleDependency() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerSingleton("test", "testValue");
|
||||
InjectedElementAttributes attributes = createResolver(TestBean.class, "injectString", String.class)
|
||||
.resolve(beanFactory, true);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
assertThat((String) attributes.get(0)).isEqualTo("testValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRequiredDependencyNotPresentThrowsUnsatisfiedDependencyException() {
|
||||
Method method = ReflectionUtils.findMethod(TestBean.class, "injectString", String.class);
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
assertThatThrownBy(() -> createResolver(TestBean.class, "injectString", String.class)
|
||||
.resolve(beanFactory)).isInstanceOfSatisfying(UnsatisfiedDependencyException.class, ex -> {
|
||||
assertThat(ex.getBeanName()).isEqualTo("test");
|
||||
assertThat(ex.getInjectionPoint()).isNotNull();
|
||||
assertThat(ex.getInjectionPoint().getMember()).isEqualTo(method);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveNonRequiredDependency() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
InjectedElementAttributes attributes = createResolver(TestBean.class, "injectString", String.class)
|
||||
.resolve(beanFactory, false);
|
||||
assertThat(attributes.isResolved()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveDependencyAndEnvironment() {
|
||||
Environment environment = mock(Environment.class);
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerSingleton("environment", environment);
|
||||
beanFactory.registerSingleton("test", "testValue");
|
||||
InjectedElementAttributes attributes = createResolver(TestBean.class, "injectStringAndEnvironment",
|
||||
String.class, Environment.class).resolve(beanFactory, true);
|
||||
assertThat(attributes.isResolved()).isTrue();
|
||||
String string = attributes.get(0);
|
||||
assertThat(string).isEqualTo("testValue");
|
||||
assertThat((Environment) attributes.get(1)).isEqualTo(environment);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void createWithUnresolvedAttributesDoesNotInvokeCallback() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
BeanDefinitionRegistrar.ThrowableFunction<InjectedElementAttributes, ?> callback = mock(BeanDefinitionRegistrar.ThrowableFunction.class);
|
||||
assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() ->
|
||||
createResolver(TestBean.class, "injectString", String.class).create(beanFactory, callback));
|
||||
verifyNoInteractions(callback);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void invokeWithUnresolvedAttributesDoesNotInvokeCallback() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
BeanDefinitionRegistrar.ThrowableConsumer<InjectedElementAttributes> callback = mock(BeanDefinitionRegistrar.ThrowableConsumer.class);
|
||||
assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() ->
|
||||
createResolver(TestBean.class, "injectString", String.class).invoke(beanFactory, callback));
|
||||
verifyNoInteractions(callback);
|
||||
}
|
||||
|
||||
private InjectedMethodResolver createResolver(Class<?> beanType, String methodName, Class<?>... parameterTypes) {
|
||||
Method method = ReflectionUtils.findMethod(beanType, methodName, parameterTypes);
|
||||
assertThat(method).isNotNull();
|
||||
return new InjectedMethodResolver(method, beanType, "test");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class TestBean {
|
||||
|
||||
public void injectString(String string) {
|
||||
|
||||
}
|
||||
|
||||
public void injectStringAndEnvironment(String string, Environment environment) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.testfixture.beans.factory.generator;
|
||||
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
public class InnerComponentConfiguration {
|
||||
|
||||
public class NoDependencyComponent {
|
||||
|
||||
public NoDependencyComponent() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class EnvironmentAwareComponent {
|
||||
|
||||
public EnvironmentAwareComponent(Environment environment) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.testfixture.beans.factory.generator;
|
||||
|
||||
public class SimpleConfiguration {
|
||||
|
||||
public SimpleConfiguration() {
|
||||
}
|
||||
|
||||
public String stringBean() {
|
||||
return "Hello";
|
||||
}
|
||||
|
||||
public Integer integerBean() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.testfixture.beans.factory.generator.factory;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* A sample object with a generic type.
|
||||
*
|
||||
* @param <T> the number type
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class NumberHolder<T extends Number> implements Serializable {
|
||||
|
||||
private final T number;
|
||||
|
||||
public NumberHolder(T number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.testfixture.beans.factory.generator.factory;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
|
||||
/**
|
||||
* A sample factory bean with a generic type.
|
||||
*
|
||||
* @param <T> the type of the number generated by this factory bean
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class NumberHolderFactoryBean<T extends Number> implements FactoryBean<NumberHolder<T>> {
|
||||
|
||||
private T number;
|
||||
|
||||
public void setNumber(T number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberHolder<T> getObject() {
|
||||
return new NumberHolder<>(this.number);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return NumberHolder.class;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.testfixture.beans.factory.generator.factory;
|
||||
|
||||
public abstract class SampleFactory {
|
||||
|
||||
public static String create(String testBean) {
|
||||
return testBean;
|
||||
}
|
||||
|
||||
public static String create(char character) {
|
||||
return String.valueOf(character);
|
||||
}
|
||||
|
||||
public static String create(Number number, String test) {
|
||||
return number + test;
|
||||
}
|
||||
|
||||
public static String create(Class<?> type) {
|
||||
return type.getName();
|
||||
}
|
||||
|
||||
public static Integer integerBean() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.testfixture.beans.factory.generator.injection;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class InjectionComponent {
|
||||
|
||||
private final String bean;
|
||||
|
||||
private Integer counter;
|
||||
|
||||
public InjectionComponent(String bean) {
|
||||
this.bean = bean;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setCounter(Integer counter) {
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.testfixture.beans.factory.generator.property;
|
||||
|
||||
public class ConfigurableBean {
|
||||
|
||||
private String name;
|
||||
|
||||
private Integer counter;
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setCounter(Integer counter) {
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.testfixture.beans.factory.generator.visibility;
|
||||
|
||||
public class ProtectedConstructorComponent {
|
||||
|
||||
ProtectedConstructorComponent() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.testfixture.beans.factory.generator.visibility;
|
||||
|
||||
public class ProtectedFactoryMethod {
|
||||
|
||||
String testBean(Integer number) {
|
||||
return "test-" + number;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aot.generator;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.support.MultiCodeBlock;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Code generator for {@link ResolvableType}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class ResolvableTypeGenerator {
|
||||
|
||||
/**
|
||||
* Generate a type signature for the specified {@link ResolvableType}.
|
||||
* @param target the type to generate
|
||||
* @return the representation of that type
|
||||
*/
|
||||
public CodeBlock generateTypeFor(ResolvableType target) {
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
generate(code, target, false);
|
||||
return code.build();
|
||||
}
|
||||
|
||||
private void generate(CodeBlock.Builder code, ResolvableType target, boolean forceResolvableType) {
|
||||
Class<?> type = ClassUtils.getUserClass(target.toClass());
|
||||
if (!target.hasGenerics()) {
|
||||
if (forceResolvableType) {
|
||||
code.add("$T.forClass($T.class)", ResolvableType.class, type);
|
||||
}
|
||||
else {
|
||||
code.add("$T.class", type);
|
||||
}
|
||||
}
|
||||
else {
|
||||
code.add("$T.forClassWithGenerics($T.class, ", ResolvableType.class, type);
|
||||
ResolvableType[] generics = target.getGenerics();
|
||||
boolean hasGenericParameter = Arrays.stream(generics).anyMatch(ResolvableType::hasGenerics);
|
||||
MultiCodeBlock multi = new MultiCodeBlock();
|
||||
for (int i = 0; i < generics.length; i++) {
|
||||
ResolvableType parameter = target.getGeneric(i);
|
||||
multi.add(parameterCode -> generate(parameterCode, parameter, hasGenericParameter));
|
||||
}
|
||||
code.add(multi.join(", ")).add(")");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -29,6 +29,7 @@ import java.util.stream.Collectors;
|
|||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.aot.hint.TypeHint.Builder;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Gather the need for reflection at runtime.
|
||||
|
@ -49,6 +50,28 @@ public class ReflectionHints {
|
|||
return this.types.values().stream().map(TypeHint.Builder::build);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the reflection hints for the type defined by the specified
|
||||
* {@link TypeReference}.
|
||||
* @param type the type to inspect
|
||||
* @return the reflection hints for this type, or {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
public TypeHint getTypeHint(TypeReference type) {
|
||||
Builder typeHintBuilder = this.types.get(type);
|
||||
return (typeHintBuilder != null ? typeHintBuilder.build() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the reflection hints for the specified type.
|
||||
* @param type the type to inspect
|
||||
* @return the reflection hints for this type, or {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
public TypeHint getTypeHint(Class<?> type) {
|
||||
return getTypeHint(TypeReference.of(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register or customize reflection hints for the type defined by the
|
||||
* specified {@link TypeReference}.
|
||||
|
|
|
@ -47,41 +47,48 @@ public final class MultiStatement {
|
|||
/**
|
||||
* Add the specified {@link CodeBlock codeblock} rendered as-is.
|
||||
* @param codeBlock the code block to add
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @see #addStatement(CodeBlock) to add a code block that represents
|
||||
* a statement
|
||||
*/
|
||||
public void add(CodeBlock codeBlock) {
|
||||
public MultiStatement add(CodeBlock codeBlock) {
|
||||
this.statements.add(Statement.of(codeBlock));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link CodeBlock} rendered as-is using the specified callback.
|
||||
* @param code the callback to use
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @see #addStatement(CodeBlock) to add a code block that represents
|
||||
* a statement
|
||||
*/
|
||||
public void add(Consumer<Builder> code) {
|
||||
public MultiStatement add(Consumer<Builder> code) {
|
||||
CodeBlock.Builder builder = CodeBlock.builder();
|
||||
code.accept(builder);
|
||||
add(builder.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a statement.
|
||||
* @param statement the statement to add
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public void addStatement(CodeBlock statement) {
|
||||
public MultiStatement addStatement(CodeBlock statement) {
|
||||
this.statements.add(Statement.ofStatement(statement));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a statement using the specified callback.
|
||||
* @param code the callback to use
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public void addStatement(Consumer<Builder> code) {
|
||||
public MultiStatement addStatement(Consumer<Builder> code) {
|
||||
CodeBlock.Builder builder = CodeBlock.builder();
|
||||
code.accept(builder);
|
||||
addStatement(builder.build());
|
||||
return addStatement(builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,10 +96,11 @@ public final class MultiStatement {
|
|||
* arguments.
|
||||
* @param code the code of the statement
|
||||
* @param args the arguments for placeholders
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @see CodeBlock#of(String, Object...)
|
||||
*/
|
||||
public void addStatement(String code, Object... args) {
|
||||
addStatement(CodeBlock.of(code, args));
|
||||
public MultiStatement addStatement(String code, Object... args) {
|
||||
return addStatement(CodeBlock.of(code, args));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,9 +109,11 @@ public final class MultiStatement {
|
|||
* @param items the items to handle, each item is represented as a statement
|
||||
* @param itemGenerator the item generator
|
||||
* @param <T> the type of the item
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public <T> void addAll(Iterable<T> items, Function<T, CodeBlock> itemGenerator) {
|
||||
public <T> MultiStatement addAll(Iterable<T> items, Function<T, CodeBlock> itemGenerator) {
|
||||
items.forEach(element -> addStatement(itemGenerator.apply(element)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aot.generator;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.javapoet.support.CodeSnippet;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ResolvableTypeGenerator}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ResolvableTypeGeneratorTests {
|
||||
|
||||
@Test
|
||||
void generateTypeForResolvableTypeWithGenericParameter() {
|
||||
assertThat(generateTypeFor(
|
||||
ResolvableType.forClassWithGenerics(Function.class,
|
||||
ResolvableType.forClassWithGenerics(Supplier.class, String.class),
|
||||
ResolvableType.forClassWithGenerics(Supplier.class, Integer.class))))
|
||||
.isEqualTo("ResolvableType.forClassWithGenerics(Function.class, "
|
||||
+ "ResolvableType.forClassWithGenerics(Supplier.class, String.class), "
|
||||
+ "ResolvableType.forClassWithGenerics(Supplier.class, Integer.class))");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateTypeForResolvableTypeWithMixedParameter() {
|
||||
assertThat(generateTypeFor(
|
||||
ResolvableType.forClassWithGenerics(Function.class,
|
||||
ResolvableType.forClassWithGenerics(Supplier.class, String.class),
|
||||
ResolvableType.forClass(Integer.class))))
|
||||
.isEqualTo("ResolvableType.forClassWithGenerics(Function.class, "
|
||||
+ "ResolvableType.forClassWithGenerics(Supplier.class, String.class), "
|
||||
+ "ResolvableType.forClass(Integer.class))");
|
||||
}
|
||||
|
||||
private String generateTypeFor(ResolvableType type) {
|
||||
return CodeSnippet.process(new ResolvableTypeGenerator().generateTypeFor(type));
|
||||
}
|
||||
|
||||
}
|
|
@ -16,10 +16,13 @@
|
|||
|
||||
package org.springframework.aot.hint;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -41,11 +44,34 @@ class ReflectionHintsTests {
|
|||
typeWithMemberCategories(String.class, MemberCategory.DECLARED_FIELDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTypeUsingType() {
|
||||
this.reflectionHints.registerType(TypeReference.of(String.class),
|
||||
hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS));
|
||||
assertThat(this.reflectionHints.getTypeHint(String.class)).satisfies(
|
||||
typeWithMemberCategories(String.class, MemberCategory.DECLARED_FIELDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTypeUsingTypeReference() {
|
||||
this.reflectionHints.registerType(String.class,
|
||||
hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS));
|
||||
assertThat(this.reflectionHints.getTypeHint(TypeReference.of(String.class))).satisfies(
|
||||
typeWithMemberCategories(String.class, MemberCategory.DECLARED_FIELDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTypeForNonExistingType() {
|
||||
assertThat(this.reflectionHints.getTypeHint(String.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerTypeReuseBuilder() {
|
||||
this.reflectionHints.registerType(TypeReference.of(String.class),
|
||||
typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
|
||||
this.reflectionHints.registerField(ReflectionUtils.findField(String.class, "value"));
|
||||
Field field = ReflectionUtils.findField(String.class, "value");
|
||||
assertThat(field).isNotNull();
|
||||
this.reflectionHints.registerField(field);
|
||||
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(String.class.getCanonicalName());
|
||||
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint -> assertThat(fieldHint.getName()).isEqualTo("value"));
|
||||
|
@ -63,7 +89,9 @@ class ReflectionHintsTests {
|
|||
|
||||
@Test
|
||||
void registerField() {
|
||||
this.reflectionHints.registerField(ReflectionUtils.findField(TestType.class, "field"));
|
||||
Field field = ReflectionUtils.findField(TestType.class, "field");
|
||||
assertThat(field).isNotNull();
|
||||
this.reflectionHints.registerField(field);
|
||||
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName());
|
||||
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint ->
|
||||
|
@ -92,7 +120,9 @@ class ReflectionHintsTests {
|
|||
|
||||
@Test
|
||||
void registerMethod() {
|
||||
this.reflectionHints.registerMethod(ReflectionUtils.findMethod(TestType.class, "setName", String.class));
|
||||
Method method = ReflectionUtils.findMethod(TestType.class, "setName", String.class);
|
||||
assertThat(method).isNotNull();
|
||||
this.reflectionHints.registerMethod(method);
|
||||
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName());
|
||||
assertThat(typeHint.fields()).isEmpty();
|
||||
|
@ -119,6 +149,7 @@ class ReflectionHintsTests {
|
|||
@SuppressWarnings("unused")
|
||||
static class TestType {
|
||||
|
||||
@Nullable
|
||||
private String field;
|
||||
|
||||
void setName(String name) {
|
||||
|
|
Loading…
Reference in New Issue