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;
+ }
+
+}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/BeanDefinitionRegistrar.java b/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/BeanDefinitionRegistrar.java
new file mode 100644
index 0000000000..149ddcc906
--- /dev/null
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/BeanDefinitionRegistrar.java
@@ -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.
+ *
+ * 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> 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 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 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 the type of the bean
+ * @return the bean instance
+ */
+ public T create(DefaultListableBeanFactory beanFactory, ThrowableFunction 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 the type of the input to the operation
+ */
+ @FunctionalInterface
+ public interface ThrowableConsumer extends Consumer {
+
+ 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 the type of the input to the function
+ * @param the type of the result of the function
+ */
+ @FunctionalInterface
+ public interface ThrowableFunction extends Function {
+
+ 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 the type of results supplied by this supplier
+ */
+ public interface ThrowableSupplier extends Supplier {
+
+ 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);
+ }
+ }
+ }
+
+}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/InjectedConstructionResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/InjectedConstructionResolver.java
new file mode 100644
index 0000000000..d1257a7eac
--- /dev/null
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/InjectedConstructionResolver.java
@@ -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 beanDefinitionResolver;
+
+ InjectedConstructionResolver(Executable executable, Class> targetType, String beanName,
+ Function 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 arguments = new ArrayList<>();
+ Set 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 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 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();
+ }
+
+}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/InjectedElementAttributes.java b/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/InjectedElementAttributes.java
new file mode 100644
index 0000000000..a8677f2ee1
--- /dev/null
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/InjectedElementAttributes.java
@@ -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 attributes;
+
+
+ InjectedElementAttributes(@Nullable List 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 attributes) {
+ ifResolved(() -> attributes.accept(this));
+ }
+
+ /**
+ * Return the resolved attribute at the specified index.
+ * @param index the attribute index
+ * @param the type of the attribute
+ * @return the attribute
+ */
+ @SuppressWarnings("unchecked")
+ public 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 the type of the attribute
+ * @return the attribute
+ */
+ @SuppressWarnings("unchecked")
+ public T get(int index, Class type) {
+ Assert.notNull(this.attributes, "Attributes must not be null");
+ return (T) this.attributes.get(index);
+ }
+}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/InjectedElementResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/InjectedElementResolver.java
new file mode 100644
index 0000000000..5e87e10a28
--- /dev/null
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/InjectedElementResolver.java
@@ -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 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 the type of the instance
+ * @return a new instance
+ */
+ default T create(DefaultListableBeanFactory beanFactory,
+ BeanDefinitionRegistrar.ThrowableFunction factory) {
+
+ InjectedElementAttributes attributes = resolve(beanFactory);
+ return factory.apply(attributes);
+ }
+
+}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/InjectedFieldResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/InjectedFieldResolver.java
new file mode 100644
index 0000000000..a7d35c2ff0
--- /dev/null
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/InjectedFieldResolver.java
@@ -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 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);
+ }
+ }
+
+}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/InjectedMethodResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/InjectedMethodResolver.java
new file mode 100644
index 0000000000..c58d616734
--- /dev/null
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/InjectedMethodResolver.java
@@ -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 arguments = new ArrayList<>();
+ Set 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);
+ }
+
+}
+
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/package-info.java b/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/package-info.java
new file mode 100644
index 0000000000..821c57180f
--- /dev/null
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/generator/config/package-info.java
@@ -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;
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/generator/BeanParameterGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/generator/BeanParameterGeneratorTests.java
new file mode 100644
index 0000000000..8e85c3cdf0
--- /dev/null
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/generator/BeanParameterGeneratorTests.java
@@ -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 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 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 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 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 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 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 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 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 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";
+ }
+ }
+
+}
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/generator/DefaultBeanInstanceGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/generator/DefaultBeanInstanceGeneratorTests.java
new file mode 100644
index 0000000000..d780d86814
--- /dev/null
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/generator/DefaultBeanInstanceGeneratorTests.java
@@ -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 hasSingleQueryConstructor(Constructor> constructor) {
+ return typeHint -> assertThat(typeHint.constructors()).singleElement()
+ .satisfies(match(constructor, "", ExecutableMode.INTROSPECT));
+ }
+
+ private Consumer hasSingleQueryMethod(Method method) {
+ return typeHint -> assertThat(typeHint.methods()).singleElement()
+ .satisfies(match(method, method.getName(), ExecutableMode.INTROSPECT));
+ }
+
+ private Consumer 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;
+ }
+
+}
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/generator/InjectionGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/generator/InjectionGeneratorTests.java
new file mode 100644
index 0000000000..910e66b6db
--- /dev/null
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/generator/InjectionGeneratorTests.java
@@ -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 counter) {
+
+ }
+
+ String source(Integer counter) {
+ return "source" + counter;
+ }
+
+ String source(ObjectProvider 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 counter;
+
+ GenericConstructorBean(ObjectProvider counter) {
+ this.counter = counter;
+ }
+
+ }
+
+ static class AmbiguousConstructorBean {
+
+ AmbiguousConstructorBean(String first, String second) {
+
+ }
+
+ AmbiguousConstructorBean(String first, Number second) {
+
+ }
+
+ }
+}
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/generator/config/BeanDefinitionRegistrarTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/generator/config/BeanDefinitionRegistrarTests.java
new file mode 100644
index 0000000000..8c97229f6d
--- /dev/null
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/generator/config/BeanDefinitionRegistrarTests.java
@@ -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 first = mock(BeanDefinitionRegistrar.ThrowableConsumer.class);
+ BeanDefinitionRegistrar.ThrowableConsumer 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> integerHolderFactory() {
+ return new GenericFactoryBean<>(integerHolder());
+ }
+
+ NumberHolder> integerHolder() {
+ return new NumberHolder<>(42);
+ }
+
+ }
+
+ static class GenericFactoryBean implements FactoryBean {
+
+ 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 {
+
+ private final N number;
+
+ public NumberHolder(N number) {
+ this.number = number;
+ }
+
+ }
+
+ static class NumberHolderSample {
+
+ @Autowired
+ private NumberHolder numberHolder;
+
+ }
+
+}
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/generator/config/InjectedConstructionResolverTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/generator/config/InjectedConstructionResolverTests.java
new file mode 100644
index 0000000000..ff599eb4ff
--- /dev/null
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/generator/config/InjectedConstructionResolverTests.java
@@ -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 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 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) 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 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) 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 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 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 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 beans) {
+
+ }
+
+ public BeansCollectionConstructor(Set beans) {
+
+ }
+
+ public BeansCollectionConstructor(Map beans) {
+
+ }
+
+ }
+
+ @SuppressWarnings("unused")
+ static class BeansCollectionFactory {
+
+ public String array(String[] beans) {
+ return "test";
+ }
+
+ public String list(List beans) {
+ return "test";
+ }
+
+ public String set(Set beans) {
+ return "test";
+ }
+
+ public String map(Map beans) {
+ return "test";
+ }
+
+ }
+
+ static Stream 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 provider) {
+ }
+ }
+
+ @SuppressWarnings("unused")
+ static class MultiArgsFactory {
+
+ String multiArgs(ResourceLoader resourceLoader, Environment environment, ObjectProvider provider) {
+ return "test";
+ }
+ }
+
+ static Stream 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) {
+ }
+
+ }
+
+}
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/generator/config/InjectedElementAttributesTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/generator/config/InjectedElementAttributesTests.java
new file mode 100644
index 0000000000..f2f66811b9
--- /dev/null
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/generator/config/InjectedElementAttributesTests.java
@@ -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 consumer = mock(BeanDefinitionRegistrar.ThrowableConsumer.class);
+ unresolved.ifResolved(consumer);
+ verifyNoInteractions(consumer);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void ifResolvedWithResolvedAttributesInvokesConsumer() {
+ BeanDefinitionRegistrar.ThrowableConsumer 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");
+ }
+
+}
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/generator/config/InjectedFieldResolverTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/generator/config/InjectedFieldResolverTests.java
new file mode 100644
index 0000000000..39f90cca7f
--- /dev/null
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/generator/config/InjectedFieldResolverTests.java
@@ -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;
+
+ }
+
+}
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/generator/config/InjectedMethodResolverTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/generator/config/InjectedMethodResolverTests.java
new file mode 100644
index 0000000000..d9b9e2aee2
--- /dev/null
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/generator/config/InjectedMethodResolverTests.java
@@ -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 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 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) {
+
+ }
+
+ }
+
+}
diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/InnerComponentConfiguration.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/InnerComponentConfiguration.java
new file mode 100644
index 0000000000..28188b2a52
--- /dev/null
+++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/InnerComponentConfiguration.java
@@ -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) {
+
+ }
+ }
+}
diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/SimpleConfiguration.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/SimpleConfiguration.java
new file mode 100644
index 0000000000..0f5ba99ad0
--- /dev/null
+++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/SimpleConfiguration.java
@@ -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;
+ }
+
+}
diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/factory/NumberHolder.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/factory/NumberHolder.java
new file mode 100644
index 0000000000..d49475656e
--- /dev/null
+++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/factory/NumberHolder.java
@@ -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 the number type
+ * @author Stephane Nicoll
+ */
+@SuppressWarnings("serial")
+public class NumberHolder implements Serializable {
+
+ private final T number;
+
+ public NumberHolder(T number) {
+ this.number = number;
+ }
+
+}
diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/factory/NumberHolderFactoryBean.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/factory/NumberHolderFactoryBean.java
new file mode 100644
index 0000000000..a837a8dcaf
--- /dev/null
+++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/factory/NumberHolderFactoryBean.java
@@ -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 the type of the number generated by this factory bean
+ * @author Stephane Nicoll
+ */
+public class NumberHolderFactoryBean implements FactoryBean> {
+
+ private T number;
+
+ public void setNumber(T number) {
+ this.number = number;
+ }
+
+ @Override
+ public NumberHolder getObject() {
+ return new NumberHolder<>(this.number);
+ }
+
+ @Override
+ public Class> getObjectType() {
+ return NumberHolder.class;
+ }
+
+}
diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/factory/SampleFactory.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/factory/SampleFactory.java
new file mode 100644
index 0000000000..eca5e0d7bf
--- /dev/null
+++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/factory/SampleFactory.java
@@ -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;
+ }
+
+}
diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/injection/InjectionComponent.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/injection/InjectionComponent.java
new file mode 100644
index 0000000000..9d772ade5a
--- /dev/null
+++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/injection/InjectionComponent.java
@@ -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;
+ }
+
+}
diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/property/ConfigurableBean.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/property/ConfigurableBean.java
new file mode 100644
index 0000000000..0a8319223b
--- /dev/null
+++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/property/ConfigurableBean.java
@@ -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;
+ }
+
+}
diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/visibility/ProtectedConstructorComponent.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/visibility/ProtectedConstructorComponent.java
new file mode 100644
index 0000000000..5b27a375cd
--- /dev/null
+++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/visibility/ProtectedConstructorComponent.java
@@ -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() {
+
+ }
+}
diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/visibility/ProtectedFactoryMethod.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/visibility/ProtectedFactoryMethod.java
new file mode 100644
index 0000000000..7d2fd2b557
--- /dev/null
+++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/factory/generator/visibility/ProtectedFactoryMethod.java
@@ -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;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/aot/generator/ResolvableTypeGenerator.java b/spring-core/src/main/java/org/springframework/aot/generator/ResolvableTypeGenerator.java
new file mode 100644
index 0000000000..339a993bf9
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/aot/generator/ResolvableTypeGenerator.java
@@ -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(")");
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java
index f459c6a258..1f7be98f33 100644
--- a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java
+++ b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java
@@ -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}.
diff --git a/spring-core/src/main/java/org/springframework/javapoet/support/MultiStatement.java b/spring-core/src/main/java/org/springframework/javapoet/support/MultiStatement.java
index 04bd02071e..2442b85850 100644
--- a/spring-core/src/main/java/org/springframework/javapoet/support/MultiStatement.java
+++ b/spring-core/src/main/java/org/springframework/javapoet/support/MultiStatement.java
@@ -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 code) {
+ public MultiStatement add(Consumer 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 code) {
+ public MultiStatement addStatement(Consumer 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 the type of the item
+ * @return {@code this}, to facilitate method chaining
*/
- public void addAll(Iterable items, Function itemGenerator) {
+ public MultiStatement addAll(Iterable items, Function itemGenerator) {
items.forEach(element -> addStatement(itemGenerator.apply(element)));
+ return this;
}
/**
diff --git a/spring-core/src/test/java/org/springframework/aot/generator/ResolvableTypeGeneratorTests.java b/spring-core/src/test/java/org/springframework/aot/generator/ResolvableTypeGeneratorTests.java
new file mode 100644
index 0000000000..34ab0f8671
--- /dev/null
+++ b/spring-core/src/test/java/org/springframework/aot/generator/ResolvableTypeGeneratorTests.java
@@ -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));
+ }
+
+}
diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java
index 6b0f57d85c..8d96f72190 100644
--- a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java
+++ b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java
@@ -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) {