Browse Source
This commit provides the necessary infrastructure to let components contribute statements that are used to fully instantiate a bean instance. To ease code generation, a dedicated infrastructure to register bean definition is provided in the o.s.beans.factory.generator package. BeanDefinitionRegistrar offers a builder style API that provides a way to hide how injected elements are resolved at runtime and let contributors provide code that may throw a checked exception. BeanInstanceContributor is the interface that components can implement to contribute to a bean instance setup. DefaultBeanInstanceGenerator generates, for a particular bean definition, the necessary statements to instantiate a bean. Closes gh-28047pull/28037/head
33 changed files with 4119 additions and 11 deletions
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator; |
||||
|
||||
import org.springframework.aot.generator.CodeContribution; |
||||
|
||||
/** |
||||
* Strategy interface to be implemented by components that participates in a |
||||
* bean instance setup so that the generated code provides an equivalent |
||||
* setup. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 6.0 |
||||
*/ |
||||
@FunctionalInterface |
||||
public interface BeanInstanceContributor { |
||||
|
||||
/** |
||||
* A {@link BeanInstanceContributor} that does not contribute anything |
||||
* to the {@link CodeContribution}. |
||||
*/ |
||||
BeanInstanceContributor NO_OP = contribution -> { }; |
||||
|
||||
/** |
||||
* Contribute to the specified {@link CodeContribution}. |
||||
* <p>Implementation of this interface can assume the following variables |
||||
* to be accessible: |
||||
* <ul> |
||||
* <li>{@code beanFactory}: the general {@code DefaultListableBeanFactory}</li> |
||||
* <li>{@code instanceContext}: the {@code BeanInstanceContext} callback</li> |
||||
* <li>{@code bean}: the variable that refers to the bean instance</li> |
||||
* </ul> |
||||
* @param contribution the {@link CodeContribution} to use |
||||
*/ |
||||
void contribute(CodeContribution contribution); |
||||
|
||||
} |
||||
@ -0,0 +1,218 @@
@@ -0,0 +1,218 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator; |
||||
|
||||
import java.lang.reflect.Executable; |
||||
import java.lang.reflect.Parameter; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.function.BiConsumer; |
||||
import java.util.function.Function; |
||||
import java.util.function.Supplier; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import org.springframework.aot.generator.ResolvableTypeGenerator; |
||||
import org.springframework.beans.factory.config.BeanDefinition; |
||||
import org.springframework.beans.factory.config.BeanReference; |
||||
import org.springframework.beans.factory.config.RuntimeBeanReference; |
||||
import org.springframework.beans.factory.support.ManagedList; |
||||
import org.springframework.beans.factory.support.ManagedSet; |
||||
import org.springframework.core.ResolvableType; |
||||
import org.springframework.javapoet.CodeBlock; |
||||
import org.springframework.javapoet.CodeBlock.Builder; |
||||
import org.springframework.javapoet.support.MultiCodeBlock; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.ObjectUtils; |
||||
|
||||
/** |
||||
* Support for writing parameters. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 6.0 |
||||
*/ |
||||
public final class BeanParameterGenerator { |
||||
|
||||
private final ResolvableTypeGenerator typeGenerator = new ResolvableTypeGenerator(); |
||||
|
||||
private final BiConsumer<BeanDefinition, Builder> innerBeanDefinitionWriter; |
||||
|
||||
|
||||
/** |
||||
* Create an instance with the callback to use to write an inner bean |
||||
* definition. |
||||
* @param innerBeanDefinitionWriter the inner bean definition writer |
||||
*/ |
||||
public BeanParameterGenerator(BiConsumer<BeanDefinition, Builder> innerBeanDefinitionWriter) { |
||||
this.innerBeanDefinitionWriter = innerBeanDefinitionWriter; |
||||
} |
||||
|
||||
/** |
||||
* Create an instance with no support for inner bean definitions. |
||||
*/ |
||||
public BeanParameterGenerator() { |
||||
this((beanDefinition, builder) -> { |
||||
throw new IllegalStateException("Inner bean definition is not supported by this instance"); |
||||
}); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Write the specified parameter {@code value}. |
||||
* @param value the value of the parameter |
||||
* @return the value of the parameter |
||||
*/ |
||||
public CodeBlock writeParameterValue(@Nullable Object value) { |
||||
return writeParameterValue(value, () -> ResolvableType.forInstance(value)); |
||||
} |
||||
|
||||
/** |
||||
* Write the specified parameter {@code value}. |
||||
* @param value the value of the parameter |
||||
* @param parameterType the type of the parameter |
||||
* @return the value of the parameter |
||||
*/ |
||||
public CodeBlock writeParameterValue(@Nullable Object value, Supplier<ResolvableType> parameterType) { |
||||
Builder code = CodeBlock.builder(); |
||||
writeParameterValue(code, value, parameterType); |
||||
return code.build(); |
||||
} |
||||
|
||||
/** |
||||
* Write the parameter types of the specified {@link Executable}. |
||||
* @param executable the executable |
||||
* @return the parameter types of the executable as a comma separated list |
||||
*/ |
||||
public CodeBlock writeExecutableParameterTypes(Executable executable) { |
||||
Class<?>[] parameterTypes = Arrays.stream(executable.getParameters()) |
||||
.map(Parameter::getType).toArray(Class<?>[]::new); |
||||
return CodeBlock.of(Arrays.stream(parameterTypes).map(d -> "$T.class") |
||||
.collect(Collectors.joining(", ")), (Object[]) parameterTypes); |
||||
} |
||||
|
||||
private void writeParameterValue(Builder code, @Nullable Object value, Supplier<ResolvableType> parameterTypeSupplier) { |
||||
if (value == null) { |
||||
code.add("null"); |
||||
return; |
||||
} |
||||
ResolvableType parameterType = parameterTypeSupplier.get(); |
||||
if (parameterType.isArray()) { |
||||
code.add("new $T { ", parameterType.toClass()); |
||||
code.add(writeAll(Arrays.asList(ObjectUtils.toObjectArray(value)), |
||||
item -> parameterType.getComponentType())); |
||||
code.add(" }"); |
||||
} |
||||
else if (value instanceof List<?> list) { |
||||
if (list.isEmpty()) { |
||||
code.add("$T.emptyList()", Collections.class); |
||||
} |
||||
else { |
||||
Class<?> listType = (value instanceof ManagedList ? ManagedList.class : List.class); |
||||
code.add("$T.of(", listType); |
||||
ResolvableType collectionType = parameterType.as(List.class).getGenerics()[0]; |
||||
code.add(writeAll(list, item -> collectionType)); |
||||
code.add(")"); |
||||
} |
||||
} |
||||
else if (value instanceof Set<?> set) { |
||||
if (set.isEmpty()) { |
||||
code.add("$T.emptySet()", Collections.class); |
||||
} |
||||
else { |
||||
Class<?> setType = (value instanceof ManagedSet ? ManagedSet.class : Set.class); |
||||
code.add("$T.of(", setType); |
||||
ResolvableType collectionType = parameterType.as(Set.class).getGenerics()[0]; |
||||
code.add(writeAll(set, item -> collectionType)); |
||||
code.add(")"); |
||||
} |
||||
} |
||||
else if (value instanceof Map<?, ?> map) { |
||||
if (map.size() <= 10) { |
||||
code.add("$T.of(", Map.class); |
||||
List<Object> parameters = new ArrayList<>(); |
||||
map.forEach((mapKey, mapValue) -> { |
||||
parameters.add(mapKey); |
||||
parameters.add(mapValue); |
||||
}); |
||||
code.add(writeAll(parameters, ResolvableType::forInstance)); |
||||
code.add(")"); |
||||
} |
||||
} |
||||
else if (value instanceof Character character) { |
||||
String result = '\'' + characterLiteralWithoutSingleQuotes(character) + '\''; |
||||
code.add(result); |
||||
} |
||||
else if (isPrimitiveOrWrapper(value)) { |
||||
code.add("$L", value); |
||||
} |
||||
else if (value instanceof String) { |
||||
code.add("$S", value); |
||||
} |
||||
else if (value instanceof Enum<?> enumValue) { |
||||
code.add("$T.$N", enumValue.getClass(), enumValue.name()); |
||||
} |
||||
else if (value instanceof Class) { |
||||
code.add("$T.class", value); |
||||
} |
||||
else if (value instanceof ResolvableType) { |
||||
code.add(this.typeGenerator.generateTypeFor((ResolvableType) value)); |
||||
} |
||||
else if (value instanceof BeanDefinition) { |
||||
this.innerBeanDefinitionWriter.accept((BeanDefinition) value, code); |
||||
} |
||||
else if (value instanceof BeanReference) { |
||||
code.add("new $T($S)", RuntimeBeanReference.class, ((BeanReference) value).getBeanName()); |
||||
} |
||||
else { |
||||
throw new IllegalArgumentException("Parameter of type " + parameterType + " is not supported"); |
||||
} |
||||
} |
||||
|
||||
private <T> CodeBlock writeAll(Iterable<T> items, Function<T, ResolvableType> elementType) { |
||||
MultiCodeBlock multi = new MultiCodeBlock(); |
||||
items.forEach(item -> multi.add(code -> |
||||
writeParameterValue(code, item, () -> elementType.apply(item)))); |
||||
return multi.join(", "); |
||||
} |
||||
|
||||
private boolean isPrimitiveOrWrapper(Object value) { |
||||
Class<?> valueType = value.getClass(); |
||||
return (valueType.isPrimitive() || valueType == Double.class || valueType == Float.class |
||||
|| valueType == Long.class || valueType == Integer.class || valueType == Short.class |
||||
|| valueType == Character.class || valueType == Byte.class || valueType == Boolean.class); |
||||
} |
||||
|
||||
// Copied from com.squareup.javapoet.Util
|
||||
private static String characterLiteralWithoutSingleQuotes(char c) { |
||||
// see https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6
|
||||
return switch (c) { |
||||
case '\b' -> "\\b"; /* \u0008: backspace (BS) */ |
||||
case '\t' -> "\\t"; /* \u0009: horizontal tab (HT) */ |
||||
case '\n' -> "\\n"; /* \u000a: linefeed (LF) */ |
||||
case '\f' -> "\\f"; /* \u000c: form feed (FF) */ |
||||
case '\r' -> "\\r"; /* \u000d: carriage return (CR) */ |
||||
case '\"' -> "\""; /* \u0022: double quote (") */ |
||||
case '\'' -> "\\'"; /* \u0027: single quote (') */ |
||||
case '\\' -> "\\\\"; /* \u005c: backslash (\) */ |
||||
default -> Character.isISOControl(c) ? String.format("\\u%04x", (int) c) : Character.toString(c); |
||||
}; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,165 @@
@@ -0,0 +1,165 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator; |
||||
|
||||
import java.lang.reflect.Constructor; |
||||
import java.lang.reflect.Executable; |
||||
import java.lang.reflect.Method; |
||||
import java.lang.reflect.Modifier; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
|
||||
import org.springframework.aot.generator.CodeContribution; |
||||
import org.springframework.aot.generator.DefaultCodeContribution; |
||||
import org.springframework.aot.generator.ProtectedAccess.Options; |
||||
import org.springframework.aot.hint.ExecutableMode; |
||||
import org.springframework.aot.hint.RuntimeHints; |
||||
import org.springframework.javapoet.CodeBlock; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
/** |
||||
* Write the necessary statements to instantiate a bean. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class DefaultBeanInstanceGenerator { |
||||
|
||||
private static final Options BEAN_INSTANCE_OPTIONS = new Options(false, true); |
||||
|
||||
private final Executable instanceCreator; |
||||
|
||||
private final List<BeanInstanceContributor> contributors; |
||||
|
||||
private final InjectionGenerator injectionGenerator; |
||||
|
||||
|
||||
DefaultBeanInstanceGenerator(Executable instanceCreator, List<BeanInstanceContributor> contributors) { |
||||
this.instanceCreator = instanceCreator; |
||||
this.contributors = List.copyOf(contributors); |
||||
this.injectionGenerator = new InjectionGenerator(); |
||||
} |
||||
|
||||
/** |
||||
* Return the necessary code to instantiate and post-process the bean |
||||
* handled by this instance. |
||||
* @param runtimeHints the runtime hints instance to use |
||||
* @return a code contribution that provides an initialized bean instance |
||||
*/ |
||||
public CodeContribution generateBeanInstance(RuntimeHints runtimeHints) { |
||||
DefaultCodeContribution contribution = new DefaultCodeContribution(runtimeHints); |
||||
contribution.protectedAccess().analyze(this.instanceCreator, BEAN_INSTANCE_OPTIONS); |
||||
if (this.instanceCreator instanceof Constructor<?> constructor) { |
||||
writeBeanInstantiation(contribution, constructor); |
||||
} |
||||
else if (this.instanceCreator instanceof Method method) { |
||||
writeBeanInstantiation(contribution, method); |
||||
} |
||||
return contribution; |
||||
} |
||||
|
||||
private void writeBeanInstantiation(CodeContribution contribution, Constructor<?> constructor) { |
||||
Class<?> declaringType = ClassUtils.getUserClass(constructor.getDeclaringClass()); |
||||
boolean innerClass = isInnerClass(declaringType); |
||||
boolean multiStatements = !this.contributors.isEmpty(); |
||||
int minArgs = isInnerClass(declaringType) ? 2 : 1; |
||||
CodeBlock.Builder code = CodeBlock.builder(); |
||||
// Shortcut for common case
|
||||
if (!multiStatements && constructor.getParameterTypes().length < minArgs) { |
||||
if (innerClass) { |
||||
code.add("() -> beanFactory.getBean($T.class).new $L()", |
||||
declaringType.getEnclosingClass(), declaringType.getSimpleName()); |
||||
} |
||||
else { |
||||
// Only apply the shortcut if there's one candidate
|
||||
if (declaringType.getDeclaredConstructors().length > 1) { |
||||
code.add("() -> new $T()", declaringType); |
||||
} |
||||
else { |
||||
code.add("$T::new", declaringType); |
||||
} |
||||
} |
||||
contribution.statements().addStatement(code.build()); |
||||
return; |
||||
} |
||||
contribution.runtimeHints().reflection().registerConstructor(constructor, |
||||
hint -> hint.withMode(ExecutableMode.INTROSPECT)); |
||||
code.add("(instanceContext) ->"); |
||||
branch(multiStatements, () -> code.beginControlFlow(""), () -> code.add(" ")); |
||||
if (multiStatements) { |
||||
code.add("$T bean = ", declaringType); |
||||
} |
||||
code.add(this.injectionGenerator.writeInstantiation(constructor)); |
||||
contribution.statements().addStatement(code.build()); |
||||
|
||||
if (multiStatements) { |
||||
for (BeanInstanceContributor contributor : this.contributors) { |
||||
contributor.contribute(contribution); |
||||
} |
||||
contribution.statements().addStatement("return bean") |
||||
.add(codeBlock -> codeBlock.unindent().add("}")); |
||||
} |
||||
} |
||||
|
||||
private static boolean isInnerClass(Class<?> type) { |
||||
return type.isMemberClass() && !Modifier.isStatic(type.getModifiers()); |
||||
} |
||||
|
||||
private void writeBeanInstantiation(CodeContribution contribution, Method method) { |
||||
// Factory method can be introspected
|
||||
contribution.runtimeHints().reflection().registerMethod(method, |
||||
hint -> hint.withMode(ExecutableMode.INTROSPECT)); |
||||
List<Class<?>> parameterTypes = new ArrayList<>(Arrays.asList(method.getParameterTypes())); |
||||
boolean multiStatements = !this.contributors.isEmpty(); |
||||
Class<?> declaringType = method.getDeclaringClass(); |
||||
CodeBlock.Builder code = CodeBlock.builder(); |
||||
// Shortcut for common case
|
||||
if (!multiStatements && parameterTypes.isEmpty()) { |
||||
code.add("() -> "); |
||||
branch(Modifier.isStatic(method.getModifiers()), |
||||
() -> code.add("$T", declaringType), |
||||
() -> code.add("beanFactory.getBean($T.class)", declaringType)); |
||||
code.add(".$L()", method.getName()); |
||||
contribution.statements().addStatement(code.build()); |
||||
return; |
||||
} |
||||
code.add("(instanceContext) ->"); |
||||
branch(multiStatements, () -> code.beginControlFlow(""), () -> code.add(" ")); |
||||
if (multiStatements) { |
||||
code.add("$T bean = ", method.getReturnType()); |
||||
} |
||||
code.add(this.injectionGenerator.writeInstantiation(method)); |
||||
contribution.statements().addStatement(code.build()); |
||||
if (multiStatements) { |
||||
for (BeanInstanceContributor contributor : this.contributors) { |
||||
contributor.contribute(contribution); |
||||
} |
||||
contribution.statements().addStatement("return bean") |
||||
.add(codeBlock -> codeBlock.unindent().add("}")); |
||||
} |
||||
} |
||||
|
||||
private static void branch(boolean condition, Runnable ifTrue, Runnable ifFalse) { |
||||
if (condition) { |
||||
ifTrue.run(); |
||||
} |
||||
else { |
||||
ifFalse.run(); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,239 @@
@@ -0,0 +1,239 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator; |
||||
|
||||
import java.lang.reflect.Constructor; |
||||
import java.lang.reflect.Executable; |
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.Member; |
||||
import java.lang.reflect.Method; |
||||
import java.lang.reflect.Modifier; |
||||
import java.lang.reflect.Parameter; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.function.Consumer; |
||||
|
||||
import org.springframework.beans.factory.generator.config.BeanDefinitionRegistrar.BeanInstanceContext; |
||||
import org.springframework.javapoet.CodeBlock; |
||||
import org.springframework.javapoet.CodeBlock.Builder; |
||||
import org.springframework.util.ClassUtils; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
/** |
||||
* Generate the necessary code to {@link #writeInstantiation(Executable) |
||||
* create a bean instance} or {@link #writeInjection(Member, boolean) |
||||
* inject dependencies}. |
||||
* <p/> |
||||
* The generator assumes a number of variables to be accessible: |
||||
* <ul> |
||||
* <li>{@code beanFactory}: the general {@code DefaultListableBeanFactory}</li> |
||||
* <li>{@code instanceContext}: the {@link BeanInstanceContext} callback</li> |
||||
* <li>{@code bean}: the variable that refers to the bean instance</li> |
||||
* </ul> |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @author Brian Clozel |
||||
*/ |
||||
public class InjectionGenerator { |
||||
|
||||
private final BeanParameterGenerator parameterGenerator = new BeanParameterGenerator(); |
||||
|
||||
|
||||
/** |
||||
* Write the necessary code to instantiate an object using the specified |
||||
* {@link Executable}. The code is suitable to be assigned to a variable |
||||
* or used as a {@literal return} statement. |
||||
* @param creator the executable to invoke to create an instance of the |
||||
* requested object |
||||
* @return the code to instantiate an object using the specified executable |
||||
*/ |
||||
public CodeBlock writeInstantiation(Executable creator) { |
||||
if (creator instanceof Constructor<?> constructor) { |
||||
return write(constructor); |
||||
} |
||||
if (creator instanceof Method method) { |
||||
return writeMethodInstantiation(method); |
||||
} |
||||
throw new IllegalArgumentException("Could not handle creator " + creator); |
||||
} |
||||
|
||||
/** |
||||
* Write the code to inject a value resolved by {@link BeanInstanceContext} |
||||
* in the specified {@link Member}. |
||||
* @param member the field or method to inject |
||||
* @param required whether the value is required |
||||
* @return a statement that injects a value to the specified membmer |
||||
*/ |
||||
public CodeBlock writeInjection(Member member, boolean required) { |
||||
if (member instanceof Method method) { |
||||
return writeMethodInjection(method, required); |
||||
} |
||||
if (member instanceof Field field) { |
||||
return writeFieldInjection(field, required); |
||||
} |
||||
throw new IllegalArgumentException("Could not handle member " + member); |
||||
} |
||||
|
||||
private CodeBlock write(Constructor<?> creator) { |
||||
Builder code = CodeBlock.builder(); |
||||
Class<?> declaringType = ClassUtils.getUserClass(creator.getDeclaringClass()); |
||||
boolean innerClass = isInnerClass(declaringType); |
||||
Class<?>[] parameterTypes = Arrays.stream(creator.getParameters()).map(Parameter::getType) |
||||
.toArray(Class<?>[]::new); |
||||
// Shortcut for common case
|
||||
if (innerClass && parameterTypes.length == 1) { |
||||
code.add("beanFactory.getBean($T.class).new $L()", declaringType.getEnclosingClass(), |
||||
declaringType.getSimpleName()); |
||||
return code.build(); |
||||
} |
||||
if (parameterTypes.length == 0) { |
||||
code.add("new $T()", declaringType); |
||||
return code.build(); |
||||
} |
||||
boolean isAmbiguous = Arrays.stream(creator.getDeclaringClass().getDeclaredConstructors()) |
||||
.filter(constructor -> constructor.getParameterCount() == parameterTypes.length).count() > 1; |
||||
code.add("instanceContext.create(beanFactory, (attributes) ->"); |
||||
List<CodeBlock> parameters = resolveParameters(creator.getParameters(), isAmbiguous); |
||||
if (innerClass) { // Remove the implicit argument
|
||||
parameters.remove(0); |
||||
} |
||||
|
||||
code.add(" "); |
||||
if (innerClass) { |
||||
code.add("beanFactory.getBean($T.class).new $L(", declaringType.getEnclosingClass(), |
||||
declaringType.getSimpleName()); |
||||
} |
||||
else { |
||||
code.add("new $T(", declaringType); |
||||
} |
||||
for (int i = 0; i < parameters.size(); i++) { |
||||
code.add(parameters.get(i)); |
||||
if (i < parameters.size() - 1) { |
||||
code.add(", "); |
||||
} |
||||
} |
||||
code.add(")"); |
||||
code.add(")"); |
||||
return code.build(); |
||||
} |
||||
|
||||
private static boolean isInnerClass(Class<?> type) { |
||||
return type.isMemberClass() && !Modifier.isStatic(type.getModifiers()); |
||||
} |
||||
|
||||
private CodeBlock writeMethodInstantiation(Method injectionPoint) { |
||||
if (injectionPoint.getParameterCount() == 0) { |
||||
Builder code = CodeBlock.builder(); |
||||
Class<?> declaringType = injectionPoint.getDeclaringClass(); |
||||
if (Modifier.isStatic(injectionPoint.getModifiers())) { |
||||
code.add("$T", declaringType); |
||||
} |
||||
else { |
||||
code.add("beanFactory.getBean($T.class)", declaringType); |
||||
} |
||||
code.add(".$L()", injectionPoint.getName()); |
||||
return code.build(); |
||||
} |
||||
return write(injectionPoint, code -> code.add(".create(beanFactory, (attributes) ->"), true); |
||||
} |
||||
|
||||
private CodeBlock writeMethodInjection(Method injectionPoint, boolean required) { |
||||
Consumer<Builder> attributesResolver = code -> { |
||||
if (required) { |
||||
code.add(".invoke(beanFactory, (attributes) ->"); |
||||
} |
||||
else { |
||||
code.add(".resolve(beanFactory, false).ifResolved((attributes) ->"); |
||||
} |
||||
}; |
||||
return write(injectionPoint, attributesResolver, false); |
||||
} |
||||
|
||||
private CodeBlock write(Method injectionPoint, Consumer<Builder> attributesResolver, boolean instantiation) { |
||||
Builder code = CodeBlock.builder(); |
||||
code.add("instanceContext"); |
||||
if (!instantiation) { |
||||
code.add(".method($S, ", injectionPoint.getName()); |
||||
code.add(this.parameterGenerator.writeExecutableParameterTypes(injectionPoint)); |
||||
code.add(")\n").indent().indent(); |
||||
} |
||||
attributesResolver.accept(code); |
||||
List<CodeBlock> parameters = resolveParameters(injectionPoint.getParameters(), false); |
||||
code.add(" "); |
||||
if (instantiation) { |
||||
if (Modifier.isStatic(injectionPoint.getModifiers())) { |
||||
code.add("$T", injectionPoint.getDeclaringClass()); |
||||
} |
||||
else { |
||||
code.add("beanFactory.getBean($T.class)", injectionPoint.getDeclaringClass()); |
||||
} |
||||
} |
||||
else { |
||||
code.add("bean"); |
||||
} |
||||
code.add(".$L(", injectionPoint.getName()); |
||||
code.add(CodeBlock.join(parameters, ", ")); |
||||
code.add(")"); |
||||
code.add(")"); |
||||
if (!instantiation) { |
||||
code.unindent().unindent(); |
||||
} |
||||
return code.build(); |
||||
} |
||||
|
||||
CodeBlock writeFieldInjection(Field injectionPoint, boolean required) { |
||||
Builder code = CodeBlock.builder(); |
||||
code.add("instanceContext.field($S, $T.class", injectionPoint.getName(), injectionPoint.getType()); |
||||
code.add(")\n").indent().indent(); |
||||
if (required) { |
||||
code.add(".invoke(beanFactory, (attributes) ->"); |
||||
} |
||||
else { |
||||
code.add(".resolve(beanFactory, false).ifResolved((attributes) ->"); |
||||
} |
||||
boolean hasAssignment = Modifier.isPrivate(injectionPoint.getModifiers()); |
||||
if (hasAssignment) { |
||||
code.beginControlFlow(""); |
||||
String fieldName = String.format("%sField", injectionPoint.getName()); |
||||
code.addStatement("$T $L = $T.findField($T.class, $S, $T.class)", Field.class, fieldName, ReflectionUtils.class, |
||||
injectionPoint.getDeclaringClass(), injectionPoint.getName(), injectionPoint.getType()); |
||||
code.addStatement("$T.makeAccessible($L)", ReflectionUtils.class, fieldName); |
||||
code.addStatement("$T.setField($L, bean, attributes.get(0))", ReflectionUtils.class, fieldName); |
||||
code.unindent().add("}"); |
||||
} |
||||
else { |
||||
code.add(" bean.$L = attributes.get(0)", injectionPoint.getName()); |
||||
} |
||||
code.add(")").unindent().unindent(); |
||||
return code.build(); |
||||
} |
||||
|
||||
private List<CodeBlock> resolveParameters(Parameter[] parameters, boolean shouldCast) { |
||||
List<CodeBlock> parameterValues = new ArrayList<>(); |
||||
for (int i = 0; i < parameters.length; i++) { |
||||
if (shouldCast) { |
||||
parameterValues.add(CodeBlock.of("attributes.get($L, $T.class)", i, parameters[i].getType())); |
||||
} |
||||
else { |
||||
parameterValues.add(CodeBlock.of("attributes.get($L)", i)); |
||||
} |
||||
} |
||||
return parameterValues; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,408 @@
@@ -0,0 +1,408 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator.config; |
||||
|
||||
import java.lang.reflect.Constructor; |
||||
import java.lang.reflect.Executable; |
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.Member; |
||||
import java.lang.reflect.Method; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.function.Consumer; |
||||
import java.util.function.Function; |
||||
import java.util.function.Supplier; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
|
||||
import org.springframework.beans.FatalBeanException; |
||||
import org.springframework.beans.factory.BeanFactoryUtils; |
||||
import org.springframework.beans.factory.config.BeanDefinition; |
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder; |
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
||||
import org.springframework.beans.factory.support.RootBeanDefinition; |
||||
import org.springframework.core.MethodIntrospector; |
||||
import org.springframework.core.ResolvableType; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.ObjectUtils; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
/** |
||||
* {@link BeanDefinition} registration mechanism offering transparent |
||||
* dependency resolution, as well as exception management. |
||||
* |
||||
* <p>Used by code generators and for internal use within the framework |
||||
* only. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 6.0 |
||||
*/ |
||||
public final class BeanDefinitionRegistrar { |
||||
|
||||
private static final Log logger = LogFactory.getLog(BeanDefinitionRegistrar.class); |
||||
|
||||
@Nullable |
||||
private final String beanName; |
||||
|
||||
private final Class<?> beanClass; |
||||
|
||||
@Nullable |
||||
private final ResolvableType beanType; |
||||
|
||||
private final BeanDefinitionBuilder builder; |
||||
|
||||
private final List<Consumer<RootBeanDefinition>> customizers; |
||||
|
||||
@Nullable |
||||
private Executable instanceCreator; |
||||
|
||||
@Nullable |
||||
private RootBeanDefinition beanDefinition; |
||||
|
||||
|
||||
private BeanDefinitionRegistrar(@Nullable String beanName, Class<?> beanClass, @Nullable ResolvableType beanType) { |
||||
this.beanName = beanName; |
||||
this.beanClass = beanClass; |
||||
this.beanType = beanType; |
||||
this.builder = BeanDefinitionBuilder.rootBeanDefinition(beanClass); |
||||
this.customizers = new ArrayList<>(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Initialize the registration of a bean with the specified name and type. |
||||
* @param beanName the name of the bean |
||||
* @param beanType the type of the bean |
||||
* @return a registrar for the specified bean |
||||
*/ |
||||
public static BeanDefinitionRegistrar of(String beanName, ResolvableType beanType) { |
||||
return new BeanDefinitionRegistrar(beanName, beanType.toClass(), beanType); |
||||
} |
||||
|
||||
/** |
||||
* Initialize the registration of a bean with the specified name and type. |
||||
* @param beanName the name of the bean |
||||
* @param beanType the type of the bean |
||||
* @return a registrar for the specified bean |
||||
*/ |
||||
public static BeanDefinitionRegistrar of(String beanName, Class<?> beanType) { |
||||
return new BeanDefinitionRegistrar(beanName, beanType, null); |
||||
} |
||||
|
||||
/** |
||||
* Initialize the registration of an inner bean with the specified type. |
||||
* @param beanType the type of the inner bean |
||||
* @return a registrar for the specified inner bean |
||||
*/ |
||||
public static BeanDefinitionRegistrar inner(ResolvableType beanType) { |
||||
return new BeanDefinitionRegistrar(null, beanType.toClass(), beanType); |
||||
} |
||||
|
||||
/** |
||||
* Initialize the registration of an inner bean with the specified type. |
||||
* @param beanType the type of the inner bean |
||||
* @return a registrar for the specified inner bean |
||||
*/ |
||||
public static BeanDefinitionRegistrar inner(Class<?> beanType) { |
||||
return new BeanDefinitionRegistrar(null, beanType, null); |
||||
} |
||||
|
||||
/** |
||||
* Customize the {@link RootBeanDefinition} using the specified consumer. |
||||
* @param bd a consumer for the bean definition |
||||
* @return {@code this}, to facilitate method chaining |
||||
*/ |
||||
public BeanDefinitionRegistrar customize(ThrowableConsumer<RootBeanDefinition> bd) { |
||||
this.customizers.add(bd); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Specify the factory method to use to instantiate the bean. |
||||
* @param declaredType the {@link Method#getDeclaringClass() declared type} |
||||
* of the factory method. |
||||
* @param name the name of the method |
||||
* @param parameterTypes the parameter types of the method |
||||
* @return {@code this}, to facilitate method chaining |
||||
* @see RootBeanDefinition#getResolvedFactoryMethod() |
||||
*/ |
||||
public BeanDefinitionRegistrar withFactoryMethod(Class<?> declaredType, String name, Class<?>... parameterTypes) { |
||||
this.instanceCreator = getMethod(declaredType, name, parameterTypes); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Specify the constructor to use to instantiate the bean. |
||||
* @param parameterTypes the parameter types of the constructor |
||||
* @return {@code this}, to facilitate method chaining |
||||
*/ |
||||
public BeanDefinitionRegistrar withConstructor(Class<?>... parameterTypes) { |
||||
this.instanceCreator = getConstructor(this.beanClass, parameterTypes); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Specify how the bean instance should be created and initialized, using |
||||
* the {@link BeanInstanceContext} to resolve dependencies if necessary. |
||||
* @param instanceContext the {@link BeanInstanceContext} to use |
||||
* @return {@code this}, to facilitate method chaining |
||||
*/ |
||||
public BeanDefinitionRegistrar instanceSupplier(ThrowableFunction<BeanInstanceContext, ?> instanceContext) { |
||||
return customize(beanDefinition -> beanDefinition.setInstanceSupplier(() -> |
||||
instanceContext.apply(createBeanInstanceContext()))); |
||||
} |
||||
|
||||
/** |
||||
* Specify how the bean instance should be created and initialized. |
||||
* @return {@code this}, to facilitate method chaining |
||||
*/ |
||||
public BeanDefinitionRegistrar instanceSupplier(ThrowableSupplier<?> instanceSupplier) { |
||||
return customize(beanDefinition -> beanDefinition.setInstanceSupplier(instanceSupplier)); |
||||
} |
||||
|
||||
/** |
||||
* Register the {@link RootBeanDefinition} defined by this instance to |
||||
* the specified bean factory. |
||||
* @param beanFactory the bean factory to use |
||||
*/ |
||||
public void register(DefaultListableBeanFactory beanFactory) { |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug("Register bean definition with name '" + this.beanName + "'"); |
||||
} |
||||
BeanDefinition beanDefinition = toBeanDefinition(); |
||||
if (this.beanName == null) { |
||||
throw new IllegalStateException("Bean name not set. Could not register " + beanDefinition); |
||||
} |
||||
beanFactory.registerBeanDefinition(this.beanName, beanDefinition); |
||||
} |
||||
|
||||
/** |
||||
* Return the {@link RootBeanDefinition} defined by this instance. |
||||
* @return the bean definition |
||||
*/ |
||||
public RootBeanDefinition toBeanDefinition() { |
||||
try { |
||||
this.beanDefinition = createBeanDefinition(); |
||||
return this.beanDefinition; |
||||
} |
||||
catch (Exception ex) { |
||||
throw new FatalBeanException("Failed to create bean definition for bean with name '" + this.beanName + "'", ex); |
||||
} |
||||
} |
||||
|
||||
private RootBeanDefinition createBeanDefinition() { |
||||
RootBeanDefinition bd = (RootBeanDefinition) this.builder.getBeanDefinition(); |
||||
if (this.beanType != null) { |
||||
bd.setTargetType(this.beanType); |
||||
} |
||||
if (this.instanceCreator instanceof Method) { |
||||
bd.setResolvedFactoryMethod((Method) this.instanceCreator); |
||||
} |
||||
this.customizers.forEach(customizer -> customizer.accept(bd)); |
||||
return bd; |
||||
} |
||||
|
||||
private BeanInstanceContext createBeanInstanceContext() { |
||||
String resolvedBeanName = this.beanName != null ? this.beanName : createInnerBeanName(); |
||||
return new BeanInstanceContext(resolvedBeanName, this.beanClass); |
||||
} |
||||
|
||||
private String createInnerBeanName() { |
||||
return "(inner bean)" + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR + |
||||
(this.beanDefinition != null ? ObjectUtils.getIdentityHexString(this.beanDefinition) : 0); |
||||
} |
||||
|
||||
@Nullable |
||||
private BeanDefinition resolveBeanDefinition(DefaultListableBeanFactory beanFactory) { |
||||
return this.beanDefinition; |
||||
} |
||||
|
||||
private static Constructor<?> getConstructor(Class<?> beanType, Class<?>... parameterTypes) { |
||||
try { |
||||
return beanType.getDeclaredConstructor(parameterTypes); |
||||
} |
||||
catch (NoSuchMethodException ex) { |
||||
String message = String.format("No constructor with type(s) [%s] found on %s", |
||||
toCommaSeparatedNames(parameterTypes), beanType.getName()); |
||||
throw new IllegalArgumentException(message, ex); |
||||
} |
||||
} |
||||
|
||||
private static Method getMethod(Class<?> declaredType, String methodName, Class<?>... parameterTypes) { |
||||
Method method = ReflectionUtils.findMethod(declaredType, methodName, parameterTypes); |
||||
if (method == null) { |
||||
String message = String.format("No method '%s' with type(s) [%s] found on %s", methodName, |
||||
toCommaSeparatedNames(parameterTypes), declaredType.getName()); |
||||
throw new IllegalArgumentException(message); |
||||
} |
||||
return MethodIntrospector.selectInvocableMethod(method, declaredType); |
||||
} |
||||
|
||||
private static String toCommaSeparatedNames(Class<?>... parameterTypes) { |
||||
return Arrays.stream(parameterTypes).map(Class::getName).collect(Collectors.joining(", ")); |
||||
} |
||||
|
||||
/** |
||||
* Callback interface used by instance suppliers that need to resolve |
||||
* dependencies for the {@link Executable} used to create the instance |
||||
* as well as any {@link Member} that should be handled by the context. |
||||
*/ |
||||
public final class BeanInstanceContext { |
||||
|
||||
private final String beanName; |
||||
|
||||
private final Class<?> beanType; |
||||
|
||||
private BeanInstanceContext(String beanName, Class<?> beanType) { |
||||
this.beanName = beanName; |
||||
this.beanType = beanType; |
||||
} |
||||
|
||||
/** |
||||
* Return a bean instance using the specified {@code factory}. |
||||
* @param beanFactory the bean factory to use |
||||
* @param factory a function that returns a bean instance based on |
||||
* the resolved attributes required by its instance creator |
||||
* @param <T> the type of the bean |
||||
* @return the bean instance |
||||
*/ |
||||
public <T> T create(DefaultListableBeanFactory beanFactory, ThrowableFunction<InjectedElementAttributes, T> factory) { |
||||
return resolveInstanceCreator(BeanDefinitionRegistrar.this.instanceCreator).create(beanFactory, factory); |
||||
} |
||||
|
||||
private InjectedElementResolver resolveInstanceCreator(@Nullable Executable instanceCreator) { |
||||
if (instanceCreator instanceof Method) { |
||||
return new InjectedConstructionResolver(instanceCreator, instanceCreator.getDeclaringClass(), this.beanName, |
||||
BeanDefinitionRegistrar.this::resolveBeanDefinition); |
||||
} |
||||
if (instanceCreator instanceof Constructor) { |
||||
return new InjectedConstructionResolver(instanceCreator, this.beanType, this.beanName, |
||||
BeanDefinitionRegistrar.this::resolveBeanDefinition); |
||||
} |
||||
throw new IllegalStateException("No factory method or constructor is set"); |
||||
} |
||||
|
||||
/** |
||||
* Create an {@link InjectedElementResolver} for the specified field. |
||||
* @param name the name of the field |
||||
* @param type the type of the field |
||||
* @return a resolved for the specified field |
||||
*/ |
||||
public InjectedElementResolver field(String name, Class<?> type) { |
||||
return new InjectedFieldResolver(getField(name, type), this.beanName); |
||||
} |
||||
|
||||
/** |
||||
* Create an {@link InjectedElementResolver} for the specified bean method. |
||||
* @param name the name of the method on the target bean |
||||
* @param parameterTypes the method parameter types |
||||
* @return a resolved for the specified bean method |
||||
*/ |
||||
public InjectedElementResolver method(String name, Class<?>... parameterTypes) { |
||||
return new InjectedMethodResolver(getMethod(this.beanType, name, parameterTypes), this.beanType, this.beanName); |
||||
} |
||||
|
||||
private Field getField(String fieldName, Class<?> fieldType) { |
||||
Field field = ReflectionUtils.findField(this.beanType, fieldName, fieldType); |
||||
if (field == null) { |
||||
throw new IllegalArgumentException("No field '" + fieldName + "' with type " + fieldType.getName() + " found on " + this.beanType); |
||||
} |
||||
return field; |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* A {@link Consumer} that allows to invoke code that throws a checked exception. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @param <T> the type of the input to the operation |
||||
*/ |
||||
@FunctionalInterface |
||||
public interface ThrowableConsumer<T> extends Consumer<T> { |
||||
|
||||
void acceptWithException(T t) throws Exception; |
||||
|
||||
@Override |
||||
default void accept(T t) { |
||||
try { |
||||
acceptWithException(t); |
||||
} |
||||
catch (RuntimeException ex) { |
||||
throw ex; |
||||
} |
||||
catch (Exception ex) { |
||||
throw new RuntimeException(ex.getMessage(), ex); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* A {@link Function} that allows to invoke code that throws a checked exception. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @param <T> the type of the input to the function |
||||
* @param <R> the type of the result of the function |
||||
*/ |
||||
@FunctionalInterface |
||||
public interface ThrowableFunction<T, R> extends Function<T, R> { |
||||
|
||||
R applyWithException(T t) throws Exception; |
||||
|
||||
@Override |
||||
default R apply(T t) { |
||||
try { |
||||
return applyWithException(t); |
||||
} |
||||
catch (RuntimeException ex) { |
||||
throw ex; |
||||
} |
||||
catch (Exception ex) { |
||||
throw new RuntimeException(ex.getMessage(), ex); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* A {@link Supplier} that allows to invoke code that throws a checked exception. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @param <T> the type of results supplied by this supplier |
||||
*/ |
||||
public interface ThrowableSupplier<T> extends Supplier<T> { |
||||
|
||||
T getWithException() throws Exception; |
||||
|
||||
@Override |
||||
default T get() { |
||||
try { |
||||
return getWithException(); |
||||
} |
||||
catch (RuntimeException ex) { |
||||
throw ex; |
||||
} |
||||
catch (Exception ex) { |
||||
throw new RuntimeException(ex.getMessage(), ex); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,175 @@
@@ -0,0 +1,175 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator.config; |
||||
|
||||
import java.lang.reflect.Array; |
||||
import java.lang.reflect.Constructor; |
||||
import java.lang.reflect.Executable; |
||||
import java.lang.reflect.Method; |
||||
import java.util.ArrayList; |
||||
import java.util.LinkedHashSet; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.StringJoiner; |
||||
import java.util.function.Function; |
||||
import java.util.function.Supplier; |
||||
|
||||
import org.springframework.beans.BeansException; |
||||
import org.springframework.beans.TypeConverter; |
||||
import org.springframework.beans.factory.InjectionPoint; |
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException; |
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException; |
||||
import org.springframework.beans.factory.config.BeanDefinition; |
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues; |
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; |
||||
import org.springframework.beans.factory.config.DependencyDescriptor; |
||||
import org.springframework.beans.factory.support.BeanDefinitionValueResolver; |
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
||||
import org.springframework.core.CollectionFactory; |
||||
import org.springframework.core.MethodParameter; |
||||
|
||||
/** |
||||
* An {@link InjectedElementResolver} for an {@link Executable} that creates |
||||
* a bean instance. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
|
||||
class InjectedConstructionResolver implements InjectedElementResolver { |
||||
|
||||
private final Executable executable; |
||||
|
||||
private final Class<?> targetType; |
||||
|
||||
private final String beanName; |
||||
|
||||
private final Function<DefaultListableBeanFactory, BeanDefinition> beanDefinitionResolver; |
||||
|
||||
InjectedConstructionResolver(Executable executable, Class<?> targetType, String beanName, |
||||
Function<DefaultListableBeanFactory, BeanDefinition> beanDefinitionResolver) { |
||||
this.executable = executable; |
||||
this.targetType = targetType; |
||||
this.beanName = beanName; |
||||
this.beanDefinitionResolver = beanDefinitionResolver; |
||||
} |
||||
|
||||
|
||||
Executable getExecutable() { |
||||
return this.executable; |
||||
} |
||||
|
||||
@Override |
||||
public InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory, boolean required) { |
||||
int argumentCount = this.executable.getParameterCount(); |
||||
List<Object> arguments = new ArrayList<>(); |
||||
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount); |
||||
TypeConverter typeConverter = beanFactory.getTypeConverter(); |
||||
ConstructorArgumentValues argumentValues = resolveArgumentValues(beanFactory); |
||||
for (int i = 0; i < argumentCount; i++) { |
||||
MethodParameter methodParam = createMethodParameter(i); |
||||
ValueHolder valueHolder = argumentValues.getIndexedArgumentValue(i, null); |
||||
if (valueHolder != null) { |
||||
if (valueHolder.isConverted()) { |
||||
arguments.add(valueHolder.getConvertedValue()); |
||||
} |
||||
else { |
||||
Object userValue = beanFactory.getTypeConverter() |
||||
.convertIfNecessary(valueHolder.getValue(), methodParam.getParameterType()); |
||||
arguments.add(userValue); |
||||
} |
||||
} |
||||
else { |
||||
DependencyDescriptor depDescriptor = new DependencyDescriptor(methodParam, true); |
||||
depDescriptor.setContainingClass(this.targetType); |
||||
try { |
||||
Object arg = resolveDependency(() -> beanFactory.resolveDependency( |
||||
depDescriptor, this.beanName, autowiredBeans, typeConverter), methodParam.getParameterType()); |
||||
arguments.add(arg); |
||||
} |
||||
catch (BeansException ex) { |
||||
throw new UnsatisfiedDependencyException(null, this.beanName, new InjectionPoint(methodParam), ex); |
||||
} |
||||
} |
||||
} |
||||
return new InjectedElementAttributes(arguments); |
||||
} |
||||
|
||||
private Object resolveDependency(Supplier<Object> resolvedDependency, Class<?> dependencyType) { |
||||
try { |
||||
return resolvedDependency.get(); |
||||
} |
||||
catch (NoSuchBeanDefinitionException ex) { |
||||
// Single constructor or factory method -> let's return an empty array/collection
|
||||
// for e.g. a vararg or a non-null List/Set/Map parameter.
|
||||
if (dependencyType.isArray()) { |
||||
return Array.newInstance(dependencyType.getComponentType(), 0); |
||||
} |
||||
else if (CollectionFactory.isApproximableCollectionType(dependencyType)) { |
||||
return CollectionFactory.createCollection(dependencyType, 0); |
||||
} |
||||
else if (CollectionFactory.isApproximableMapType(dependencyType)) { |
||||
return CollectionFactory.createMap(dependencyType, 0); |
||||
} |
||||
throw ex; |
||||
} |
||||
} |
||||
|
||||
private ConstructorArgumentValues resolveArgumentValues(DefaultListableBeanFactory beanFactory) { |
||||
ConstructorArgumentValues resolvedValues = new ConstructorArgumentValues(); |
||||
BeanDefinition beanDefinition = this.beanDefinitionResolver.apply(beanFactory); |
||||
if (beanDefinition == null || !beanDefinition.hasConstructorArgumentValues()) { |
||||
return resolvedValues; |
||||
} |
||||
ConstructorArgumentValues argumentValues = beanDefinition.getConstructorArgumentValues(); |
||||
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(beanFactory, |
||||
this.beanName, beanDefinition); |
||||
for (Map.Entry<Integer, ValueHolder> entry : argumentValues.getIndexedArgumentValues().entrySet()) { |
||||
int index = entry.getKey(); |
||||
ValueHolder valueHolder = entry.getValue(); |
||||
if (valueHolder.isConverted()) { |
||||
resolvedValues.addIndexedArgumentValue(index, valueHolder); |
||||
} |
||||
else { |
||||
Object resolvedValue = |
||||
valueResolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue()); |
||||
ValueHolder resolvedValueHolder = |
||||
new ValueHolder(resolvedValue, valueHolder.getType(), valueHolder.getName()); |
||||
resolvedValueHolder.setSource(valueHolder); |
||||
resolvedValues.addIndexedArgumentValue(index, resolvedValueHolder); |
||||
} |
||||
} |
||||
return resolvedValues; |
||||
} |
||||
|
||||
private MethodParameter createMethodParameter(int index) { |
||||
if (this.executable instanceof Constructor) { |
||||
return new MethodParameter((Constructor<?>) this.executable, index); |
||||
} |
||||
else { |
||||
return new MethodParameter((Method) this.executable, index); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return new StringJoiner(", ", InjectedConstructionResolver.class.getSimpleName() + "[", "]") |
||||
.add("executable=" + this.executable) |
||||
.toString(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator.config; |
||||
|
||||
import java.util.List; |
||||
import java.util.function.Consumer; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Resolved attributes of an injected element. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 6.0 |
||||
*/ |
||||
public class InjectedElementAttributes { |
||||
|
||||
@Nullable |
||||
private final List<Object> attributes; |
||||
|
||||
|
||||
InjectedElementAttributes(@Nullable List<Object> attributes) { |
||||
this.attributes = attributes; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Specify if the attributes have been resolved. |
||||
* @return the resolution of the injection |
||||
*/ |
||||
public boolean isResolved() { |
||||
return (this.attributes != null); |
||||
} |
||||
|
||||
/** |
||||
* Run the specified {@linkplain Runnable task} only if this instance is |
||||
* {@link #isResolved() resolved}. |
||||
* @param task the task to invoke if attributes are available |
||||
*/ |
||||
public void ifResolved(Runnable task) { |
||||
if (isResolved()) { |
||||
task.run(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Invoke the specified {@link Consumer} with the resolved attributes. |
||||
* @param attributes the consumer to invoke if this instance is resolved |
||||
*/ |
||||
public void ifResolved(BeanDefinitionRegistrar.ThrowableConsumer<InjectedElementAttributes> attributes) { |
||||
ifResolved(() -> attributes.accept(this)); |
||||
} |
||||
|
||||
/** |
||||
* Return the resolved attribute at the specified index. |
||||
* @param index the attribute index |
||||
* @param <T> the type of the attribute |
||||
* @return the attribute |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public <T> T get(int index) { |
||||
Assert.notNull(this.attributes, "Attributes must not be null"); |
||||
return (T) this.attributes.get(index); |
||||
} |
||||
|
||||
/** |
||||
* Return the resolved attribute at the specified index. |
||||
* @param index the attribute index |
||||
* @param type the attribute type |
||||
* @param <T> the type of the attribute |
||||
* @return the attribute |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public <T> T get(int index, Class<T> type) { |
||||
Assert.notNull(this.attributes, "Attributes must not be null"); |
||||
return (T) this.attributes.get(index); |
||||
} |
||||
} |
||||
@ -0,0 +1,75 @@
@@ -0,0 +1,75 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator.config; |
||||
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
||||
|
||||
/** |
||||
* Resolve the attributes of an injected element such as a {@code Constructor} |
||||
* or a factory {@code Method}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 6.0 |
||||
*/ |
||||
public interface InjectedElementResolver { |
||||
|
||||
/** |
||||
* Resolve the attributes using the specified bean factory. |
||||
* @param beanFactory the bean factory to use |
||||
* @return the resolved attributes |
||||
*/ |
||||
default InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory) { |
||||
return resolve(beanFactory, true); |
||||
} |
||||
|
||||
/** |
||||
* Resolve the attributes using the specified bean factory. |
||||
* @param beanFactory the bean factory to use |
||||
* @param required whether the injection point is mandatory |
||||
* @return the resolved attributes |
||||
*/ |
||||
InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory, boolean required); |
||||
|
||||
/** |
||||
* Invoke the specified consumer with the resolved |
||||
* {@link InjectedElementAttributes attributes}. |
||||
* @param beanFactory the bean factory to use to resolve the attributes |
||||
* @param attributes a consumer of the resolved attributes |
||||
*/ |
||||
default void invoke(DefaultListableBeanFactory beanFactory, |
||||
BeanDefinitionRegistrar.ThrowableConsumer<InjectedElementAttributes> attributes) { |
||||
|
||||
InjectedElementAttributes elements = resolve(beanFactory); |
||||
attributes.accept(elements); |
||||
} |
||||
|
||||
/** |
||||
* Create an instance based on the resolved |
||||
* {@link InjectedElementAttributes attributes}. |
||||
* @param beanFactory the bean factory to use to resolve the attributes |
||||
* @param factory a factory to create the instance based on the resolved attributes |
||||
* @param <T> the type of the instance |
||||
* @return a new instance |
||||
*/ |
||||
default <T> T create(DefaultListableBeanFactory beanFactory, |
||||
BeanDefinitionRegistrar.ThrowableFunction<InjectedElementAttributes, T> factory) { |
||||
|
||||
InjectedElementAttributes attributes = resolve(beanFactory); |
||||
return factory.apply(attributes); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,71 @@
@@ -0,0 +1,71 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator.config; |
||||
|
||||
import java.lang.reflect.Field; |
||||
import java.util.Collections; |
||||
import java.util.LinkedHashSet; |
||||
import java.util.Set; |
||||
|
||||
import org.springframework.beans.BeansException; |
||||
import org.springframework.beans.TypeConverter; |
||||
import org.springframework.beans.factory.InjectionPoint; |
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException; |
||||
import org.springframework.beans.factory.config.DependencyDescriptor; |
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
||||
|
||||
/** |
||||
* An {@link InjectedElementResolver} for a {@link Field}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class InjectedFieldResolver implements InjectedElementResolver { |
||||
|
||||
private final Field field; |
||||
|
||||
private final String beanName; |
||||
|
||||
|
||||
/** |
||||
* Create a new instance. |
||||
* @param field the field to handle |
||||
* @param beanName the name of the bean, or {@code null} |
||||
*/ |
||||
InjectedFieldResolver(Field field, String beanName) { |
||||
this.field = field; |
||||
this.beanName = beanName; |
||||
} |
||||
|
||||
@Override |
||||
public InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory, boolean required) { |
||||
DependencyDescriptor desc = new DependencyDescriptor(this.field, required); |
||||
desc.setContainingClass(this.field.getType()); |
||||
Set<String> autowiredBeanNames = new LinkedHashSet<>(1); |
||||
TypeConverter typeConverter = beanFactory.getTypeConverter(); |
||||
try { |
||||
Object value = beanFactory.resolveDependency(desc, this.beanName, autowiredBeanNames, typeConverter); |
||||
if (value == null && !required) { |
||||
return new InjectedElementAttributes(null); |
||||
} |
||||
return new InjectedElementAttributes(Collections.singletonList(value)); |
||||
} |
||||
catch (BeansException ex) { |
||||
throw new UnsatisfiedDependencyException(null, this.beanName, new InjectionPoint(this.field), ex); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator.config; |
||||
|
||||
import java.lang.reflect.Method; |
||||
import java.util.ArrayList; |
||||
import java.util.LinkedHashSet; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
|
||||
import org.springframework.beans.BeansException; |
||||
import org.springframework.beans.TypeConverter; |
||||
import org.springframework.beans.factory.InjectionPoint; |
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException; |
||||
import org.springframework.beans.factory.config.DependencyDescriptor; |
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
||||
import org.springframework.core.MethodParameter; |
||||
|
||||
/** |
||||
* An {@link InjectedElementResolver} for a {@link Method}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class InjectedMethodResolver implements InjectedElementResolver { |
||||
|
||||
private final Method method; |
||||
|
||||
private final Class<?> target; |
||||
|
||||
private final String beanName; |
||||
|
||||
|
||||
/** |
||||
* Create a new instance. |
||||
* @param method the method to handle |
||||
* @param target the type on which the method is declared |
||||
* @param beanName the name of the bean, or {@code null} |
||||
*/ |
||||
InjectedMethodResolver(Method method, Class<?> target, String beanName) { |
||||
this.method = method; |
||||
this.target = target; |
||||
this.beanName = beanName; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory, boolean required) { |
||||
int argumentCount = this.method.getParameterCount(); |
||||
List<Object> arguments = new ArrayList<>(); |
||||
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount); |
||||
TypeConverter typeConverter = beanFactory.getTypeConverter(); |
||||
for (int i = 0; i < argumentCount; i++) { |
||||
MethodParameter methodParam = new MethodParameter(this.method, i); |
||||
DependencyDescriptor depDescriptor = new DependencyDescriptor(methodParam, required); |
||||
depDescriptor.setContainingClass(this.target); |
||||
try { |
||||
Object arg = beanFactory.resolveDependency(depDescriptor, this.beanName, autowiredBeans, typeConverter); |
||||
if (arg == null && !required) { |
||||
arguments = null; |
||||
break; |
||||
} |
||||
arguments.add(arg); |
||||
} |
||||
catch (BeansException ex) { |
||||
throw new UnsatisfiedDependencyException(null, this.beanName, new InjectionPoint(methodParam), ex); |
||||
} |
||||
} |
||||
return new InjectedElementAttributes(arguments); |
||||
} |
||||
|
||||
} |
||||
|
||||
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
/** |
||||
* Classes used in generated code to ease bean registration. |
||||
*/ |
||||
@NonNullApi |
||||
@NonNullFields |
||||
package org.springframework.beans.factory.generator.config; |
||||
|
||||
import org.springframework.lang.NonNullApi; |
||||
import org.springframework.lang.NonNullFields; |
||||
@ -0,0 +1,264 @@
@@ -0,0 +1,264 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator; |
||||
|
||||
import java.io.StringWriter; |
||||
import java.lang.reflect.Constructor; |
||||
import java.lang.reflect.Method; |
||||
import java.time.temporal.ChronoUnit; |
||||
import java.util.Collections; |
||||
import java.util.HashMap; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.function.Consumer; |
||||
import java.util.stream.Stream; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.params.ParameterizedTest; |
||||
import org.junit.jupiter.params.provider.Arguments; |
||||
import org.junit.jupiter.params.provider.MethodSource; |
||||
|
||||
import org.springframework.beans.factory.config.BeanReference; |
||||
import org.springframework.beans.factory.support.ManagedList; |
||||
import org.springframework.beans.factory.support.ManagedSet; |
||||
import org.springframework.beans.factory.support.RootBeanDefinition; |
||||
import org.springframework.core.ResolvableType; |
||||
import org.springframework.core.io.ResourceLoader; |
||||
import org.springframework.javapoet.support.CodeSnippet; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
/** |
||||
* Tests for {@link BeanParameterGenerator}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class BeanParameterGeneratorTests { |
||||
|
||||
private final BeanParameterGenerator generator = new BeanParameterGenerator(); |
||||
|
||||
@Test |
||||
void writeCharArray() { |
||||
char[] value = new char[] { 'v', 'a', 'l', 'u', 'e' }; |
||||
assertThat(write(value, ResolvableType.forArrayComponent(ResolvableType.forClass(char.class)))) |
||||
.isEqualTo("new char[] { 'v', 'a', 'l', 'u', 'e' }"); |
||||
} |
||||
|
||||
@Test |
||||
void writeStringArray() { |
||||
String[] value = new String[] { "a", "test" }; |
||||
assertThat(write(value, ResolvableType.forArrayComponent(ResolvableType.forClass(String.class)))) |
||||
.isEqualTo("new String[] { \"a\", \"test\" }"); |
||||
} |
||||
|
||||
@Test |
||||
void writeStringList() { |
||||
List<String> value = List.of("a", "test"); |
||||
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(List.class, String.class)); |
||||
assertThat(code.getSnippet()).isEqualTo( |
||||
"List.of(\"a\", \"test\")"); |
||||
assertThat(code.hasImport(List.class)).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void writeStringManagedList() { |
||||
ManagedList<String> value = ManagedList.of("a", "test"); |
||||
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(List.class, String.class)); |
||||
assertThat(code.getSnippet()).isEqualTo( |
||||
"ManagedList.of(\"a\", \"test\")"); |
||||
assertThat(code.hasImport(ManagedList.class)).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void writeEmptyList() { |
||||
List<String> value = List.of(); |
||||
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(List.class, String.class)); |
||||
assertThat(code.getSnippet()).isEqualTo("Collections.emptyList()"); |
||||
assertThat(code.hasImport(Collections.class)).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void writeStringSet() { |
||||
Set<String> value = Set.of("a", "test"); |
||||
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(Set.class, String.class)); |
||||
assertThat(code.getSnippet()).startsWith("Set.of(").contains("a").contains("test"); |
||||
assertThat(code.hasImport(Set.class)).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void writeStringManagedSet() { |
||||
Set<String> value = ManagedSet.of("a", "test"); |
||||
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(Set.class, String.class)); |
||||
assertThat(code.getSnippet()).isEqualTo( |
||||
"ManagedSet.of(\"a\", \"test\")"); |
||||
assertThat(code.hasImport(ManagedSet.class)).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void writeEmptySet() { |
||||
Set<String> value = Set.of(); |
||||
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(Set.class, String.class)); |
||||
assertThat(code.getSnippet()).isEqualTo("Collections.emptySet()"); |
||||
assertThat(code.hasImport(Collections.class)).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void writeMap() { |
||||
Map<String, Object> value = new LinkedHashMap<>(); |
||||
value.put("name", "Hello"); |
||||
value.put("counter", 42); |
||||
assertThat(write(value)).isEqualTo("Map.of(\"name\", \"Hello\", \"counter\", 42)"); |
||||
} |
||||
|
||||
@Test |
||||
void writeMapWithEnum() { |
||||
Map<String, Object> value = new HashMap<>(); |
||||
value.put("unit", ChronoUnit.DAYS); |
||||
assertThat(write(value)).isEqualTo("Map.of(\"unit\", ChronoUnit.DAYS)"); |
||||
} |
||||
|
||||
@Test |
||||
void writeEmptyMap() { |
||||
assertThat(write(Map.of())).isEqualTo("Map.of()"); |
||||
} |
||||
|
||||
@Test |
||||
void writeString() { |
||||
assertThat(write("test", ResolvableType.forClass(String.class))).isEqualTo("\"test\""); |
||||
} |
||||
|
||||
@Test |
||||
void writeCharEscapeBackslash() { |
||||
assertThat(write('\\', ResolvableType.forType(char.class))).isEqualTo("'\\\\'"); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("primitiveValues") |
||||
void writePrimitiveValue(Object value, String parameter) { |
||||
assertThat(write(value, ResolvableType.forClass(value.getClass()))).isEqualTo(parameter); |
||||
} |
||||
|
||||
private static Stream<Arguments> primitiveValues() { |
||||
return Stream.of(Arguments.of((short) 0, "0"), Arguments.of((1), "1"), Arguments.of(2L, "2"), |
||||
Arguments.of(2.5d, "2.5"), Arguments.of(2.7f, "2.7"), Arguments.of('c', "'c'"), |
||||
Arguments.of((byte) 1, "1"), Arguments.of(true, "true")); |
||||
} |
||||
|
||||
@Test |
||||
void writeEnum() { |
||||
assertThat(write(ChronoUnit.DAYS, ResolvableType.forClass(ChronoUnit.class))) |
||||
.isEqualTo("ChronoUnit.DAYS"); |
||||
} |
||||
|
||||
@Test |
||||
void writeClass() { |
||||
assertThat(write(Integer.class, ResolvableType.forClass(Class.class))) |
||||
.isEqualTo("Integer.class"); |
||||
} |
||||
|
||||
@Test |
||||
void writeResolvableType() { |
||||
ResolvableType type = ResolvableType.forClassWithGenerics(Consumer.class, Integer.class); |
||||
assertThat(write(type, type)) |
||||
.isEqualTo("ResolvableType.forClassWithGenerics(Consumer.class, Integer.class)"); |
||||
} |
||||
|
||||
@Test |
||||
void writeExecutableParameterTypesWithConstructor() { |
||||
Constructor<?> constructor = TestSample.class.getDeclaredConstructors()[0]; |
||||
assertThat(CodeSnippet.process(this.generator.writeExecutableParameterTypes(constructor))) |
||||
.isEqualTo("String.class, ResourceLoader.class"); |
||||
} |
||||
|
||||
@Test |
||||
void writeExecutableParameterTypesWithNoArgConstructor() { |
||||
Constructor<?> constructor = BeanParameterGeneratorTests.class.getDeclaredConstructors()[0]; |
||||
assertThat(CodeSnippet.process(this.generator.writeExecutableParameterTypes(constructor))) |
||||
.isEmpty(); |
||||
} |
||||
|
||||
@Test |
||||
void writeExecutableParameterTypesWithMethod() { |
||||
Method method = ReflectionUtils.findMethod(TestSample.class, "createBean", String.class, Integer.class); |
||||
assertThat(CodeSnippet.process(this.generator.writeExecutableParameterTypes(method))) |
||||
.isEqualTo("String.class, Integer.class"); |
||||
} |
||||
|
||||
@Test |
||||
void writeNull() { |
||||
assertThat(write(null)).isEqualTo("null"); |
||||
} |
||||
|
||||
@Test |
||||
void writeBeanReference() { |
||||
BeanReference beanReference = mock(BeanReference.class); |
||||
given(beanReference.getBeanName()).willReturn("testBean"); |
||||
assertThat(write(beanReference)).isEqualTo("new RuntimeBeanReference(\"testBean\")"); |
||||
} |
||||
|
||||
@Test |
||||
void writeBeanDefinitionCallsConsumer() { |
||||
BeanParameterGenerator customGenerator = new BeanParameterGenerator( |
||||
((beanDefinition, builder) -> builder.add("test"))); |
||||
assertThat(CodeSnippet.process(customGenerator.writeParameterValue(new RootBeanDefinition()))).isEqualTo("test"); |
||||
} |
||||
|
||||
@Test |
||||
void writeBeanDefinitionWithoutConsumerFails() { |
||||
BeanParameterGenerator customGenerator = new BeanParameterGenerator(); |
||||
assertThatIllegalStateException().isThrownBy(() -> customGenerator |
||||
.writeParameterValue(new RootBeanDefinition())); |
||||
} |
||||
|
||||
@Test |
||||
void writeUnsupportedParameter() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> write(new StringWriter())) |
||||
.withMessageContaining(StringWriter.class.getName()); |
||||
} |
||||
|
||||
private String write(Object value) { |
||||
return CodeSnippet.process(this.generator.writeParameterValue(value)); |
||||
} |
||||
|
||||
private String write(Object value, ResolvableType resolvableType) { |
||||
return codeSnippet(value, resolvableType).getSnippet(); |
||||
} |
||||
|
||||
private CodeSnippet codeSnippet(Object value, ResolvableType resolvableType) { |
||||
return CodeSnippet.of(this.generator.writeParameterValue(value, () -> resolvableType)); |
||||
} |
||||
|
||||
|
||||
@SuppressWarnings("unused") |
||||
static class TestSample { |
||||
|
||||
public TestSample(String test, ResourceLoader resourceLoader) { |
||||
} |
||||
|
||||
String createBean(String name, Integer counter) { |
||||
return "test"; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,255 @@
@@ -0,0 +1,255 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator; |
||||
|
||||
import java.lang.reflect.Constructor; |
||||
import java.lang.reflect.Executable; |
||||
import java.lang.reflect.Method; |
||||
import java.util.Arrays; |
||||
import java.util.function.Consumer; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.aot.generator.CodeContribution; |
||||
import org.springframework.aot.hint.ExecutableHint; |
||||
import org.springframework.aot.hint.ExecutableMode; |
||||
import org.springframework.aot.hint.MemberCategory; |
||||
import org.springframework.aot.hint.RuntimeHints; |
||||
import org.springframework.aot.hint.TypeHint; |
||||
import org.springframework.aot.hint.TypeReference; |
||||
import org.springframework.beans.testfixture.beans.TestBean; |
||||
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.EnvironmentAwareComponent; |
||||
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.NoDependencyComponent; |
||||
import org.springframework.beans.testfixture.beans.factory.generator.SimpleConfiguration; |
||||
import org.springframework.beans.testfixture.beans.factory.generator.factory.NumberHolderFactoryBean; |
||||
import org.springframework.beans.testfixture.beans.factory.generator.factory.SampleFactory; |
||||
import org.springframework.beans.testfixture.beans.factory.generator.injection.InjectionComponent; |
||||
import org.springframework.beans.testfixture.beans.factory.generator.visibility.ProtectedConstructorComponent; |
||||
import org.springframework.beans.testfixture.beans.factory.generator.visibility.ProtectedFactoryMethod; |
||||
import org.springframework.javapoet.CodeBlock; |
||||
import org.springframework.javapoet.support.CodeSnippet; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* Tests for {@link DefaultBeanInstanceGenerator}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class DefaultBeanInstanceGeneratorTests { |
||||
|
||||
@Test |
||||
void generateUsingDefaultConstructorUsesMethodReference() { |
||||
CodeContribution contribution = generate(SimpleConfiguration.class.getDeclaredConstructors()[0]); |
||||
assertThat(code(contribution)).isEqualTo("SimpleConfiguration::new"); |
||||
assertThat(reflectionHints(contribution, SimpleConfiguration.class)).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void generateUsingConstructorWithoutParameterAndMultipleCandidatesDoesNotUseMethodReference() throws NoSuchMethodException { |
||||
CodeContribution contribution = generate(TestBean.class.getConstructor()); |
||||
assertThat(code(contribution)).isEqualTo("() -> new TestBean()"); |
||||
assertThat(reflectionHints(contribution, TestBean.class)).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void generateUsingConstructorWithParameter() { |
||||
Constructor<?> constructor = InjectionComponent.class.getDeclaredConstructors()[0]; |
||||
CodeContribution contribution = generate(constructor); |
||||
assertThat(code(contribution).lines()).containsOnly( |
||||
"(instanceContext) -> instanceContext.create(beanFactory, (attributes) -> " |
||||
+ "new InjectionComponent(attributes.get(0)))"); |
||||
assertThat(reflectionHints(contribution, InjectionComponent.class)) |
||||
.satisfies(hasSingleQueryConstructor(constructor)); |
||||
} |
||||
|
||||
@Test |
||||
void generateUsingConstructorWithInnerClassAndNoExtraArg() { |
||||
CodeContribution contribution = generate(NoDependencyComponent.class.getDeclaredConstructors()[0]); |
||||
assertThat(code(contribution).lines()).containsOnly( |
||||
"() -> beanFactory.getBean(InnerComponentConfiguration.class).new NoDependencyComponent()"); |
||||
assertThat(reflectionHints(contribution, NoDependencyComponent.class)).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void generateUsingConstructorWithInnerClassAndExtraArg() { |
||||
Constructor<?> constructor = EnvironmentAwareComponent.class.getDeclaredConstructors()[0]; |
||||
CodeContribution contribution = generate(constructor); |
||||
assertThat(code(contribution).lines()).containsOnly( |
||||
"(instanceContext) -> instanceContext.create(beanFactory, (attributes) -> " |
||||
+ "beanFactory.getBean(InnerComponentConfiguration.class).new EnvironmentAwareComponent(attributes.get(1)))"); |
||||
assertThat(reflectionHints(contribution, EnvironmentAwareComponent.class)) |
||||
.satisfies(hasSingleQueryConstructor(constructor)); |
||||
} |
||||
|
||||
@Test |
||||
void generateUsingConstructorOfTypeWithGeneric() { |
||||
CodeContribution contribution = generate(NumberHolderFactoryBean.class.getDeclaredConstructors()[0]); |
||||
assertThat(code(contribution)).isEqualTo("NumberHolderFactoryBean::new"); |
||||
assertThat(reflectionHints(contribution, NumberHolderFactoryBean.class)).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void generateUsingNoArgConstructorAndContributorsDoesNotUseMethodReference() { |
||||
CodeContribution contribution = generate(SimpleConfiguration.class.getDeclaredConstructors()[0], |
||||
contrib -> contrib.statements().add(CodeBlock.of("// hello\n")), |
||||
BeanInstanceContributor.NO_OP); |
||||
assertThat(code(contribution)).isEqualTo(""" |
||||
(instanceContext) -> { |
||||
SimpleConfiguration bean = new SimpleConfiguration(); |
||||
// hello
|
||||
return bean; |
||||
}"""); |
||||
} |
||||
|
||||
@Test |
||||
void generateUsingContributorsRegisterHints() { |
||||
CodeContribution contribution = generate(SimpleConfiguration.class.getDeclaredConstructors()[0], |
||||
contrib -> { |
||||
contrib.statements().add(CodeBlock.of("// hello\n")); |
||||
contrib.runtimeHints().resources().registerPattern("com/example/*.properties"); |
||||
}, |
||||
contrib -> contrib.runtimeHints().reflection().registerType(TypeReference.of(String.class), |
||||
hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS))); |
||||
assertThat(code(contribution)).isEqualTo(""" |
||||
(instanceContext) -> { |
||||
SimpleConfiguration bean = new SimpleConfiguration(); |
||||
// hello
|
||||
return bean; |
||||
}"""); |
||||
assertThat(contribution.runtimeHints().resources().resourcePatterns()).singleElement().satisfies(hint -> |
||||
assertThat(hint.getIncludes()).containsOnly("com/example/*.properties")); |
||||
assertThat(contribution.runtimeHints().reflection().getTypeHint(String.class)).satisfies(hint -> { |
||||
assertThat(hint.getType()).isEqualTo(TypeReference.of(String.class)); |
||||
assertThat(hint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_PUBLIC_METHODS); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void generateUsingMethodWithNoArg() { |
||||
Method method = method(SimpleConfiguration.class, "stringBean"); |
||||
CodeContribution contribution = generate(method); |
||||
assertThat(code(contribution)).isEqualTo("() -> beanFactory.getBean(SimpleConfiguration.class).stringBean()"); |
||||
assertThat(reflectionHints(contribution, SimpleConfiguration.class)) |
||||
.satisfies(hasSingleQueryMethod(method)); |
||||
} |
||||
|
||||
@Test |
||||
void generateUsingStaticMethodWithNoArg() { |
||||
Method method = method(SampleFactory.class, "integerBean"); |
||||
CodeContribution contribution = generate(method); |
||||
assertThat(code(contribution)).isEqualTo("() -> SampleFactory.integerBean()"); |
||||
assertThat(reflectionHints(contribution, SampleFactory.class)) |
||||
.satisfies(hasSingleQueryMethod(method)); |
||||
} |
||||
|
||||
@Test |
||||
void generateUsingMethodWithArg() { |
||||
Method method = method(SampleFactory.class, "create", Number.class, String.class); |
||||
CodeContribution contribution = generate(method); |
||||
assertThat(code(contribution)).isEqualTo("(instanceContext) -> instanceContext.create(beanFactory, (attributes) -> " |
||||
+ "SampleFactory.create(attributes.get(0), attributes.get(1)))"); |
||||
assertThat(reflectionHints(contribution, SampleFactory.class)) |
||||
.satisfies(hasSingleQueryMethod(method)); |
||||
} |
||||
|
||||
@Test |
||||
void generateUsingMethodAndContributors() { |
||||
CodeContribution contribution = generate(method(SimpleConfiguration.class, "stringBean"), |
||||
contrib -> { |
||||
contrib.statements().add(CodeBlock.of("// hello\n")); |
||||
contrib.runtimeHints().resources().registerPattern("com/example/*.properties"); |
||||
}, |
||||
contrib -> contrib.runtimeHints().reflection().registerType(TypeReference.of(String.class), |
||||
hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS))); |
||||
assertThat(code(contribution)).isEqualTo(""" |
||||
(instanceContext) -> { |
||||
String bean = beanFactory.getBean(SimpleConfiguration.class).stringBean(); |
||||
// hello
|
||||
return bean; |
||||
}"""); |
||||
assertThat(contribution.runtimeHints().resources().resourcePatterns()).singleElement().satisfies(hint -> |
||||
assertThat(hint.getIncludes()).containsOnly("com/example/*.properties")); |
||||
assertThat(contribution.runtimeHints().reflection().getTypeHint(String.class)).satisfies(hint -> { |
||||
assertThat(hint.getType()).isEqualTo(TypeReference.of(String.class)); |
||||
assertThat(hint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_PUBLIC_METHODS); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void generateUsingProtectedConstructorRegistersProtectedAccess() { |
||||
CodeContribution contribution = generate(ProtectedConstructorComponent.class.getDeclaredConstructors()[0]); |
||||
assertThat(contribution.protectedAccess().isAccessible("com.example")).isFalse(); |
||||
assertThat(contribution.protectedAccess().getPrivilegedPackageName("com.example")) |
||||
.isEqualTo(ProtectedConstructorComponent.class.getPackageName()); |
||||
} |
||||
|
||||
@Test |
||||
void generateUsingProtectedMethodRegistersProtectedAccess() { |
||||
CodeContribution contribution = generate(method(ProtectedFactoryMethod.class, "testBean", Integer.class)); |
||||
assertThat(contribution.protectedAccess().isAccessible("com.example")).isFalse(); |
||||
assertThat(contribution.protectedAccess().getPrivilegedPackageName("com.example")) |
||||
.isEqualTo(ProtectedFactoryMethod.class.getPackageName()); |
||||
} |
||||
|
||||
private String code(CodeContribution contribution) { |
||||
return CodeSnippet.process(contribution.statements().toCodeBlock()); |
||||
} |
||||
|
||||
@Nullable |
||||
private TypeHint reflectionHints(CodeContribution contribution, Class<?> type) { |
||||
return contribution.runtimeHints().reflection().getTypeHint(type); |
||||
} |
||||
|
||||
private Consumer<TypeHint> hasSingleQueryConstructor(Constructor<?> constructor) { |
||||
return typeHint -> assertThat(typeHint.constructors()).singleElement() |
||||
.satisfies(match(constructor, "<init>", ExecutableMode.INTROSPECT)); |
||||
} |
||||
|
||||
private Consumer<TypeHint> hasSingleQueryMethod(Method method) { |
||||
return typeHint -> assertThat(typeHint.methods()).singleElement() |
||||
.satisfies(match(method, method.getName(), ExecutableMode.INTROSPECT)); |
||||
} |
||||
|
||||
private Consumer<ExecutableHint> match(Executable executable, String name, ExecutableMode... modes) { |
||||
return hint -> { |
||||
assertThat(hint.getName()).isEqualTo(name); |
||||
assertThat(hint.getParameterTypes()).hasSameSizeAs(executable.getParameterTypes()); |
||||
for (int i = 0; i < hint.getParameterTypes().size(); i++) { |
||||
assertThat(hint.getParameterTypes().get(i)) |
||||
.isEqualTo(TypeReference.of(executable.getParameterTypes()[i])); |
||||
} |
||||
assertThat(hint.getModes()).containsOnly(modes); |
||||
}; |
||||
} |
||||
|
||||
private CodeContribution generate(Executable executable, |
||||
BeanInstanceContributor... beanInstanceContributors) { |
||||
DefaultBeanInstanceGenerator generator = new DefaultBeanInstanceGenerator(executable, |
||||
Arrays.asList(beanInstanceContributors)); |
||||
return generator.generateBeanInstance(new RuntimeHints()); |
||||
} |
||||
|
||||
private static Method method(Class<?> type, String methodName, Class<?>... parameterTypes) { |
||||
Method method = ReflectionUtils.findMethod(type, methodName, parameterTypes); |
||||
assertThat(method).isNotNull(); |
||||
return method; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,269 @@
@@ -0,0 +1,269 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator; |
||||
|
||||
import java.lang.reflect.Constructor; |
||||
import java.lang.reflect.Executable; |
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.Member; |
||||
import java.lang.reflect.Method; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.beans.factory.ObjectProvider; |
||||
import org.springframework.beans.factory.generator.InjectionGeneratorTests.SimpleConstructorBean.InnerClass; |
||||
import org.springframework.javapoet.support.CodeSnippet; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
/** |
||||
* Tests for {@link InjectionGenerator}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class InjectionGeneratorTests { |
||||
|
||||
@Test |
||||
void writeInstantiationForConstructorWithNoArgUseShortcut() { |
||||
Constructor<?> constructor = SimpleBean.class.getDeclaredConstructors()[0]; |
||||
assertThat(writeInstantiation(constructor).lines()) |
||||
.containsExactly("new InjectionGeneratorTests.SimpleBean()"); |
||||
} |
||||
|
||||
@Test |
||||
void writeInstantiationForConstructorWithNonGenericParameter() { |
||||
Constructor<?> constructor = SimpleConstructorBean.class.getDeclaredConstructors()[0]; |
||||
assertThat(writeInstantiation(constructor).lines()).containsExactly( |
||||
"instanceContext.create(beanFactory, (attributes) -> new InjectionGeneratorTests.SimpleConstructorBean(attributes.get(0), attributes.get(1)))"); |
||||
} |
||||
|
||||
@Test |
||||
void writeInstantiationForConstructorWithGenericParameter() { |
||||
Constructor<?> constructor = GenericConstructorBean.class.getDeclaredConstructors()[0]; |
||||
assertThat(writeInstantiation(constructor).lines()).containsExactly( |
||||
"instanceContext.create(beanFactory, (attributes) -> new InjectionGeneratorTests.GenericConstructorBean(attributes.get(0)))"); |
||||
} |
||||
|
||||
@Test |
||||
void writeInstantiationForAmbiguousConstructor() throws Exception { |
||||
Constructor<?> constructor = AmbiguousConstructorBean.class.getDeclaredConstructor(String.class, Number.class); |
||||
assertThat(writeInstantiation(constructor).lines()).containsExactly( |
||||
"instanceContext.create(beanFactory, (attributes) -> new InjectionGeneratorTests.AmbiguousConstructorBean(attributes.get(0, String.class), attributes.get(1, Number.class)))"); |
||||
} |
||||
|
||||
@Test |
||||
void writeInstantiationForConstructorInInnerClass() { |
||||
Constructor<?> constructor = InnerClass.class.getDeclaredConstructors()[0]; |
||||
assertThat(writeInstantiation(constructor).lines()).containsExactly( |
||||
"beanFactory.getBean(InjectionGeneratorTests.SimpleConstructorBean.class).new InnerClass()"); |
||||
} |
||||
|
||||
@Test |
||||
void writeInstantiationForMethodWithNoArgUseShortcut() { |
||||
assertThat(writeInstantiation(method(SimpleBean.class, "name")).lines()).containsExactly( |
||||
"beanFactory.getBean(InjectionGeneratorTests.SimpleBean.class).name()"); |
||||
} |
||||
|
||||
@Test |
||||
void writeInstantiationForStaticMethodWithNoArgUseShortcut() { |
||||
assertThat(writeInstantiation(method(SimpleBean.class, "number")).lines()).containsExactly( |
||||
"InjectionGeneratorTests.SimpleBean.number()"); |
||||
} |
||||
|
||||
@Test |
||||
void writeInstantiationForMethodWithNonGenericParameter() { |
||||
assertThat(writeInstantiation(method(SampleBean.class, "source", Integer.class)).lines()).containsExactly( |
||||
"instanceContext.create(beanFactory, (attributes) -> beanFactory.getBean(InjectionGeneratorTests.SampleBean.class).source(attributes.get(0)))"); |
||||
} |
||||
|
||||
@Test |
||||
void writeInstantiationForStaticMethodWithNonGenericParameter() { |
||||
assertThat(writeInstantiation(method(SampleBean.class, "staticSource", Integer.class)).lines()).containsExactly( |
||||
"instanceContext.create(beanFactory, (attributes) -> InjectionGeneratorTests.SampleBean.staticSource(attributes.get(0)))"); |
||||
} |
||||
|
||||
@Test |
||||
void writeInstantiationForMethodWithGenericParameters() { |
||||
assertThat(writeInstantiation(method(SampleBean.class, "source", ObjectProvider.class)).lines()).containsExactly( |
||||
"instanceContext.create(beanFactory, (attributes) -> beanFactory.getBean(InjectionGeneratorTests.SampleBean.class).source(attributes.get(0)))"); |
||||
} |
||||
|
||||
@Test |
||||
void writeInjectionForUnsupportedMember() { |
||||
assertThatIllegalArgumentException().isThrownBy(() -> writeInjection(mock(Member.class), false)); |
||||
} |
||||
|
||||
@Test |
||||
void writeInjectionForNonRequiredMethodWithNonGenericParameters() { |
||||
Method method = method(SampleBean.class, "sourceAndCounter", String.class, Integer.class); |
||||
assertThat(writeInjection(method, false)).isEqualTo(""" |
||||
instanceContext.method("sourceAndCounter", String.class, Integer.class) |
||||
.resolve(beanFactory, false).ifResolved((attributes) -> bean.sourceAndCounter(attributes.get(0), attributes.get(1)))"""); |
||||
} |
||||
|
||||
@Test |
||||
void writeInjectionForRequiredMethodWithGenericParameter() { |
||||
Method method = method(SampleBean.class, "nameAndCounter", String.class, ObjectProvider.class); |
||||
assertThat(writeInjection(method, true)).isEqualTo(""" |
||||
instanceContext.method("nameAndCounter", String.class, ObjectProvider.class) |
||||
.invoke(beanFactory, (attributes) -> bean.nameAndCounter(attributes.get(0), attributes.get(1)))"""); |
||||
} |
||||
|
||||
@Test |
||||
void writeInjectionForNonRequiredMethodWithGenericParameter() { |
||||
Method method = method(SampleBean.class, "nameAndCounter", String.class, ObjectProvider.class); |
||||
assertThat(writeInjection(method, false)).isEqualTo(""" |
||||
instanceContext.method("nameAndCounter", String.class, ObjectProvider.class) |
||||
.resolve(beanFactory, false).ifResolved((attributes) -> bean.nameAndCounter(attributes.get(0), attributes.get(1)))"""); |
||||
} |
||||
|
||||
@Test |
||||
void writeInjectionForRequiredField() { |
||||
Field field = field(SampleBean.class, "counter"); |
||||
assertThat(writeInjection(field, true)).isEqualTo(""" |
||||
instanceContext.field("counter", Integer.class) |
||||
.invoke(beanFactory, (attributes) -> bean.counter = attributes.get(0))"""); |
||||
} |
||||
|
||||
@Test |
||||
void writeInjectionForNonRequiredField() { |
||||
Field field = field(SampleBean.class, "counter"); |
||||
assertThat(writeInjection(field, false)).isEqualTo(""" |
||||
instanceContext.field("counter", Integer.class) |
||||
.resolve(beanFactory, false).ifResolved((attributes) -> bean.counter = attributes.get(0))"""); |
||||
} |
||||
|
||||
@Test |
||||
void writeInjectionForRequiredPrivateField() { |
||||
Field field = field(SampleBean.class, "source"); |
||||
assertThat(writeInjection(field, true)).isEqualTo(""" |
||||
instanceContext.field("source", String.class) |
||||
.invoke(beanFactory, (attributes) -> { |
||||
Field sourceField = ReflectionUtils.findField(InjectionGeneratorTests.SampleBean.class, "source", String.class); |
||||
ReflectionUtils.makeAccessible(sourceField); |
||||
ReflectionUtils.setField(sourceField, bean, attributes.get(0)); |
||||
})"""); |
||||
} |
||||
|
||||
|
||||
private Method method(Class<?> type, String name, Class<?>... parameterTypes) { |
||||
Method method = ReflectionUtils.findMethod(type, name, parameterTypes); |
||||
assertThat(method).isNotNull(); |
||||
return method; |
||||
} |
||||
|
||||
private Field field(Class<?> type, String name) { |
||||
Field field = ReflectionUtils.findField(type, name); |
||||
assertThat(field).isNotNull(); |
||||
return field; |
||||
} |
||||
|
||||
private String writeInstantiation(Executable creator) { |
||||
return CodeSnippet.process(code -> code.add(new InjectionGenerator().writeInstantiation(creator))); |
||||
} |
||||
|
||||
private String writeInjection(Member member, boolean required) { |
||||
return CodeSnippet.process(code -> code.add(new InjectionGenerator().writeInjection(member, required))); |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
static class SampleBean { |
||||
|
||||
private String source; |
||||
|
||||
Integer counter; |
||||
|
||||
|
||||
void sourceAndCounter(String source, Integer counter) { |
||||
|
||||
} |
||||
|
||||
void nameAndCounter(String name, ObjectProvider<Integer> counter) { |
||||
|
||||
} |
||||
|
||||
String source(Integer counter) { |
||||
return "source" + counter; |
||||
} |
||||
|
||||
String source(ObjectProvider<Integer> counter) { |
||||
return "source" + counter.getIfAvailable(() -> 0); |
||||
} |
||||
|
||||
static String staticSource(Integer counter) { |
||||
return counter + "source"; |
||||
} |
||||
|
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
static class SimpleBean { |
||||
|
||||
String name() { |
||||
return "test"; |
||||
} |
||||
|
||||
static Integer number() { |
||||
return 42; |
||||
} |
||||
|
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
static class SimpleConstructorBean { |
||||
|
||||
private final String source; |
||||
|
||||
private final Integer counter; |
||||
|
||||
public SimpleConstructorBean(String source, Integer counter) { |
||||
this.source = source; |
||||
this.counter = counter; |
||||
} |
||||
|
||||
class InnerClass { |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
static class GenericConstructorBean { |
||||
|
||||
private final ObjectProvider<Integer> counter; |
||||
|
||||
GenericConstructorBean(ObjectProvider<Integer> counter) { |
||||
this.counter = counter; |
||||
} |
||||
|
||||
} |
||||
|
||||
static class AmbiguousConstructorBean { |
||||
|
||||
AmbiguousConstructorBean(String first, String second) { |
||||
|
||||
} |
||||
|
||||
AmbiguousConstructorBean(String first, Number second) { |
||||
|
||||
} |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,458 @@
@@ -0,0 +1,458 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator.config; |
||||
|
||||
import java.io.IOException; |
||||
import java.lang.reflect.Field; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.mockito.InOrder; |
||||
|
||||
import org.springframework.beans.FatalBeanException; |
||||
import org.springframework.beans.factory.BeanCreationException; |
||||
import org.springframework.beans.factory.FactoryBean; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.beans.factory.annotation.Value; |
||||
import org.springframework.beans.factory.generator.config.BeanDefinitionRegistrar.BeanInstanceContext; |
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder; |
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
||||
import org.springframework.beans.factory.support.RootBeanDefinition; |
||||
import org.springframework.core.ResolvableType; |
||||
import org.springframework.core.env.Environment; |
||||
import org.springframework.core.io.DefaultResourceLoader; |
||||
import org.springframework.core.io.ResourceLoader; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.Mockito.inOrder; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
/** |
||||
* Tests for {@link BeanDefinitionRegistrar}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class BeanDefinitionRegistrarTests { |
||||
|
||||
@Test |
||||
void beanDefinitionWithBeanClassDoesNotSetTargetType() { |
||||
RootBeanDefinition beanDefinition = BeanDefinitionRegistrar.of("test", String.class).toBeanDefinition(); |
||||
assertThat(beanDefinition.getBeanClass()).isEqualTo(String.class); |
||||
assertThat(beanDefinition.getTargetType()).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void beanDefinitionWithResolvableTypeSetsTargetType() { |
||||
ResolvableType targetType = ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class); |
||||
RootBeanDefinition beanDefinition = BeanDefinitionRegistrar.of("test", targetType).toBeanDefinition(); |
||||
assertThat(beanDefinition.getTargetType()).isNotNull().isEqualTo(NumberHolder.class); |
||||
} |
||||
|
||||
@Test |
||||
void registerWithSimpleInstanceSupplier() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
BeanDefinitionRegistrar.of("test", InjectionSample.class) |
||||
.instanceSupplier(InjectionSample::new).register(beanFactory); |
||||
assertBeanFactory(beanFactory, () -> { |
||||
assertThat(beanFactory.containsBean("test")).isTrue(); |
||||
assertThat(beanFactory.getBean(InjectionSample.class)).isNotNull(); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void registerWithSimpleInstanceSupplierThatThrowsRuntimeException() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
Exception exception = new IllegalArgumentException("test exception"); |
||||
BeanDefinitionRegistrar.of("testBean", InjectionSample.class) |
||||
.instanceSupplier(() -> { |
||||
throw exception; |
||||
}).register(beanFactory); |
||||
assertThatThrownBy(() -> beanFactory.getBean("testBean")).isInstanceOf(BeanCreationException.class) |
||||
.getRootCause().isEqualTo(exception); |
||||
} |
||||
|
||||
@Test |
||||
void registerWithSimpleInstanceSupplierThatThrowsCheckedException() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
Exception exception = new IOException("test exception"); |
||||
BeanDefinitionRegistrar.of("testBean", InjectionSample.class) |
||||
.instanceSupplier(() -> { |
||||
throw exception; |
||||
}).register(beanFactory); |
||||
assertThatThrownBy(() -> beanFactory.getBean("testBean")).isInstanceOf(BeanCreationException.class) |
||||
.getRootCause().isEqualTo(exception); |
||||
} |
||||
|
||||
@Test |
||||
void registerWithoutBeanNameFails() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
BeanDefinitionRegistrar registrar = BeanDefinitionRegistrar.inner(InjectionSample.class) |
||||
.instanceSupplier(InjectionSample::new); |
||||
assertThatIllegalStateException().isThrownBy(() -> registrar.register(beanFactory)) |
||||
.withMessageContaining("Bean name not set."); |
||||
} |
||||
|
||||
@Test |
||||
@SuppressWarnings("unchecked") |
||||
void registerWithCustomizer() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
BeanDefinitionRegistrar.ThrowableConsumer<RootBeanDefinition> first = mock(BeanDefinitionRegistrar.ThrowableConsumer.class); |
||||
BeanDefinitionRegistrar.ThrowableConsumer<RootBeanDefinition> second = mock(BeanDefinitionRegistrar.ThrowableConsumer.class); |
||||
BeanDefinitionRegistrar.of("test", InjectionSample.class) |
||||
.instanceSupplier(InjectionSample::new).customize(first).customize(second).register(beanFactory); |
||||
assertBeanFactory(beanFactory, () -> { |
||||
assertThat(beanFactory.containsBean("test")).isTrue(); |
||||
InOrder ordered = inOrder(first, second); |
||||
ordered.verify(first).accept(any(RootBeanDefinition.class)); |
||||
ordered.verify(second).accept(any(RootBeanDefinition.class)); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void registerWithCustomizerThatThrowsRuntimeException() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
Exception exception = new RuntimeException("test exception"); |
||||
BeanDefinitionRegistrar registrar = BeanDefinitionRegistrar.of("test", InjectionSample.class) |
||||
.instanceSupplier(InjectionSample::new).customize(bd -> { |
||||
throw exception; |
||||
}); |
||||
assertThatThrownBy(() -> registrar.register(beanFactory)).isInstanceOf(FatalBeanException.class) |
||||
.hasMessageContaining("Failed to create bean definition for bean with name 'test'") |
||||
.hasMessageContaining("test exception") |
||||
.hasCause(exception); |
||||
} |
||||
|
||||
@Test |
||||
void registerWithCustomizerThatThrowsCheckedException() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
Exception exception = new IOException("test exception"); |
||||
BeanDefinitionRegistrar registrar = BeanDefinitionRegistrar.of("test", InjectionSample.class) |
||||
.instanceSupplier(InjectionSample::new).customize(bd -> { |
||||
throw exception; |
||||
}); |
||||
assertThatThrownBy(() -> registrar.register(beanFactory)).isInstanceOf(FatalBeanException.class) |
||||
.hasMessageContaining("Failed to create bean definition for bean with name 'test'") |
||||
.hasMessageContaining("test exception"); |
||||
} |
||||
|
||||
@Test |
||||
void registerWithConstructorInstantiation() { |
||||
ResourceLoader resourceLoader = new DefaultResourceLoader(); |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerResolvableDependency(ResourceLoader.class, resourceLoader); |
||||
BeanDefinitionRegistrar.of("test", ConstructorSample.class).withConstructor(ResourceLoader.class) |
||||
.instanceSupplier(instanceContext -> instanceContext.create(beanFactory, attributes -> |
||||
new ConstructorSample(attributes.get(0)))).register(beanFactory); |
||||
assertBeanFactory(beanFactory, () -> { |
||||
assertThat(beanFactory.containsBean("test")).isTrue(); |
||||
assertThat(beanFactory.getBean(ConstructorSample.class).resourceLoader).isEqualTo(resourceLoader); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void registerWithConstructorInstantiationThatThrowsRuntimeException() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
Exception exception = new RuntimeException("test exception"); |
||||
BeanDefinitionRegistrar.of("test", ConstructorSample.class).withConstructor(ResourceLoader.class) |
||||
.instanceSupplier(instanceContext -> { |
||||
throw exception; |
||||
}).register(beanFactory); |
||||
assertThatThrownBy(() -> beanFactory.getBean("test")).isInstanceOf(BeanCreationException.class) |
||||
.getRootCause().isEqualTo(exception); |
||||
} |
||||
|
||||
@Test |
||||
void registerWithConstructorInstantiationThatThrowsCheckedException() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
Exception exception = new IOException("test exception"); |
||||
BeanDefinitionRegistrar.of("test", ConstructorSample.class).withConstructor(ResourceLoader.class) |
||||
.instanceSupplier(instanceContext -> { |
||||
throw exception; |
||||
}).register(beanFactory); |
||||
assertThatThrownBy(() -> beanFactory.getBean("test")).isInstanceOf(BeanCreationException.class) |
||||
.getRootCause().isEqualTo(exception); |
||||
} |
||||
|
||||
@Test |
||||
void registerWithConstructorOnInnerClass() { |
||||
Environment environment = mock(Environment.class); |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerSingleton("environment", environment); |
||||
beanFactory.registerBeanDefinition("sample", BeanDefinitionBuilder.rootBeanDefinition(InnerClassSample.class).getBeanDefinition()); |
||||
BeanDefinitionRegistrar.of("test", InnerClassSample.Inner.class).withConstructor(InnerClassSample.class, Environment.class) |
||||
.instanceSupplier(instanceContext -> instanceContext.create(beanFactory, attributes -> |
||||
beanFactory.getBean(InnerClassSample.class).new Inner(attributes.get(1)))) |
||||
.register(beanFactory); |
||||
assertBeanFactory(beanFactory, () -> { |
||||
assertThat(beanFactory.containsBean("test")).isTrue(); |
||||
InnerClassSample.Inner bean = beanFactory.getBean(InnerClassSample.Inner.class); |
||||
assertThat(bean.environment).isEqualTo(environment); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void registerWithInvalidConstructor() { |
||||
assertThatThrownBy(() -> BeanDefinitionRegistrar.of("test", ConstructorSample.class).withConstructor(Object.class)) |
||||
.isInstanceOf(IllegalArgumentException.class) |
||||
.hasMessageContaining("No constructor with type(s) [java.lang.Object] found on") |
||||
.hasMessageContaining(ConstructorSample.class.getName()); |
||||
} |
||||
|
||||
@Test |
||||
void registerWithFactoryMethod() { |
||||
ResourceLoader resourceLoader = new DefaultResourceLoader(); |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerResolvableDependency(ResourceLoader.class, resourceLoader); |
||||
BeanDefinitionRegistrar.of("configuration", ConfigurationSample.class).instanceSupplier(ConfigurationSample::new) |
||||
.register(beanFactory); |
||||
BeanDefinitionRegistrar.of("test", ConstructorSample.class) |
||||
.withFactoryMethod(ConfigurationSample.class, "sampleBean", ResourceLoader.class) |
||||
.instanceSupplier(instanceContext -> instanceContext.create(beanFactory, attributes -> |
||||
beanFactory.getBean(ConfigurationSample.class).sampleBean(attributes.get(0)))) |
||||
.register(beanFactory); |
||||
assertBeanFactory(beanFactory, () -> { |
||||
assertThat(beanFactory.containsBean("configuration")).isTrue(); |
||||
assertThat(beanFactory.containsBean("test")).isTrue(); |
||||
assertThat(beanFactory.getBean(ConstructorSample.class).resourceLoader).isEqualTo(resourceLoader); |
||||
RootBeanDefinition bd = (RootBeanDefinition) beanFactory.getBeanDefinition("test"); |
||||
assertThat(bd.getResolvedFactoryMethod()).isNotNull().isEqualTo( |
||||
ReflectionUtils.findMethod(ConfigurationSample.class, "sampleBean", ResourceLoader.class)); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void registerWithCreateShortcutWithoutFactoryMethod() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
BeanDefinitionRegistrar.of("configuration", ConfigurationSample.class).instanceSupplier(ConfigurationSample::new) |
||||
.register(beanFactory); |
||||
BeanDefinitionRegistrar.of("test", ConstructorSample.class) |
||||
.instanceSupplier(instanceContext -> instanceContext.create(beanFactory, attributes -> |
||||
beanFactory.getBean(ConfigurationSample.class).sampleBean(attributes.get(0)))) |
||||
.register(beanFactory); |
||||
assertThatThrownBy(() -> beanFactory.getBean("test")).isInstanceOf(BeanCreationException.class) |
||||
.hasMessageContaining("No factory method or constructor is set"); |
||||
} |
||||
|
||||
@Test |
||||
void registerWithInjectedField() { |
||||
Environment environment = mock(Environment.class); |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerSingleton("environment", environment); |
||||
BeanDefinitionRegistrar.of("test", InjectionSample.class).instanceSupplier(instanceContext -> { |
||||
InjectionSample bean = new InjectionSample(); |
||||
instanceContext.field("environment", Environment.class).invoke(beanFactory, |
||||
attributes -> bean.environment = (attributes.get(0))); |
||||
return bean; |
||||
}).register(beanFactory); |
||||
assertBeanFactory(beanFactory, () -> { |
||||
assertThat(beanFactory.containsBean("test")).isTrue(); |
||||
assertThat(beanFactory.getBean(InjectionSample.class).environment).isEqualTo(environment); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void registerWithInvalidField() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
BeanDefinitionRegistrar.of("test", InjectionSample.class).instanceSupplier(instanceContext -> |
||||
instanceContext.field("doesNotExist", Object.class).resolve(beanFactory)).register(beanFactory); |
||||
assertThatThrownBy(() -> beanFactory.getBean(InjectionSample.class) |
||||
).isInstanceOf(BeanCreationException.class) |
||||
.hasMessageContaining("No field '%s' with type %s found", "doesNotExist", Object.class.getName()) |
||||
.hasMessageContaining(InjectionSample.class.getName()); |
||||
} |
||||
|
||||
@Test |
||||
void registerWithInjectedMethod() { |
||||
Environment environment = mock(Environment.class); |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerSingleton("environment", environment); |
||||
BeanDefinitionRegistrar.of("test", InjectionSample.class).instanceSupplier(instanceContext -> { |
||||
InjectionSample bean = new InjectionSample(); |
||||
instanceContext.method("setEnvironment", Environment.class).invoke(beanFactory, |
||||
attributes -> bean.setEnvironment(attributes.get(0))); |
||||
return bean; |
||||
}).register(beanFactory); |
||||
assertBeanFactory(beanFactory, () -> { |
||||
assertThat(beanFactory.containsBean("test")).isTrue(); |
||||
assertThat(beanFactory.getBean(InjectionSample.class).environment).isEqualTo(environment); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void registerWithInvalidMethod() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
assertThatThrownBy(() -> { |
||||
BeanDefinitionRegistrar.of("test", InjectionSample.class).instanceSupplier(instanceContext -> |
||||
instanceContext.method("setEnvironment", Object.class).resolve(beanFactory)).register(beanFactory); |
||||
beanFactory.getBean(InjectionSample.class); |
||||
} |
||||
).isInstanceOf(BeanCreationException.class) |
||||
.hasMessageContaining("No method '%s' with type(s) [%s] found", "setEnvironment", Object.class.getName()) |
||||
.hasMessageContaining(InjectionSample.class.getName()); |
||||
} |
||||
|
||||
@Test |
||||
void innerBeanDefinitionWithClass() { |
||||
RootBeanDefinition beanDefinition = BeanDefinitionRegistrar.inner(ConfigurationSample.class) |
||||
.customize(bd -> bd.setSynthetic(true)).toBeanDefinition(); |
||||
assertThat(beanDefinition).isNotNull(); |
||||
assertThat(beanDefinition.getResolvableType().resolve()).isEqualTo(ConfigurationSample.class); |
||||
assertThat(beanDefinition.isSynthetic()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void innerBeanDefinitionWithResolvableType() { |
||||
RootBeanDefinition beanDefinition = BeanDefinitionRegistrar.inner(ResolvableType.forClass(ConfigurationSample.class)) |
||||
.customize(bd -> bd.setDescription("test")).toBeanDefinition(); |
||||
assertThat(beanDefinition).isNotNull(); |
||||
assertThat(beanDefinition.getResolvableType().resolve()).isEqualTo(ConfigurationSample.class); |
||||
assertThat(beanDefinition.getDescription()).isEqualTo("test"); |
||||
} |
||||
|
||||
@Test |
||||
void innerBeanDefinitionHasInnerBeanNameInInstanceSupplier() { |
||||
RootBeanDefinition beanDefinition = BeanDefinitionRegistrar.inner(String.class) |
||||
.instanceSupplier(instanceContext -> { |
||||
Field field = ReflectionUtils.findField(BeanInstanceContext.class, "beanName", String.class); |
||||
ReflectionUtils.makeAccessible(field); |
||||
return ReflectionUtils.getField(field, instanceContext); |
||||
}).toBeanDefinition(); |
||||
assertThat(beanDefinition).isNotNull(); |
||||
String beanName = (String) beanDefinition.getInstanceSupplier().get(); |
||||
assertThat(beanName).isNotNull().startsWith("(inner bean)#"); |
||||
} |
||||
|
||||
|
||||
private void assertBeanFactory(DefaultListableBeanFactory beanFactory, Runnable assertions) { |
||||
assertions.run(); |
||||
} |
||||
|
||||
|
||||
static class ConfigurationSample { |
||||
|
||||
ConstructorSample sampleBean(ResourceLoader resourceLoader) { |
||||
return new ConstructorSample(resourceLoader); |
||||
} |
||||
|
||||
} |
||||
|
||||
static class ConstructorSample { |
||||
private final ResourceLoader resourceLoader; |
||||
|
||||
ConstructorSample(ResourceLoader resourceLoader) { |
||||
this.resourceLoader = resourceLoader; |
||||
} |
||||
} |
||||
|
||||
static class MultiArgConstructorSample { |
||||
|
||||
private final String name; |
||||
|
||||
private final Integer counter; |
||||
|
||||
public MultiArgConstructorSample(String name, Integer counter) { |
||||
this.name = name; |
||||
this.counter = counter; |
||||
} |
||||
|
||||
} |
||||
|
||||
static class InjectionSample { |
||||
|
||||
private Environment environment; |
||||
|
||||
private String name; |
||||
|
||||
private Integer counter; |
||||
|
||||
void setEnvironment(Environment environment) { |
||||
this.environment = environment; |
||||
} |
||||
|
||||
void setNameAndCounter(@Value("${test.name:test}") String name, @Value("${test.counter:42}") Integer counter) { |
||||
this.name = name; |
||||
this.counter = counter; |
||||
} |
||||
|
||||
} |
||||
|
||||
static class InnerClassSample { |
||||
|
||||
class Inner { |
||||
|
||||
private Environment environment; |
||||
|
||||
Inner(Environment environment) { |
||||
this.environment = environment; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
static class GenericFactoryBeanConfiguration { |
||||
|
||||
FactoryBean<NumberHolder<?>> integerHolderFactory() { |
||||
return new GenericFactoryBean<>(integerHolder()); |
||||
} |
||||
|
||||
NumberHolder<?> integerHolder() { |
||||
return new NumberHolder<>(42); |
||||
} |
||||
|
||||
} |
||||
|
||||
static class GenericFactoryBean<T> implements FactoryBean<T> { |
||||
|
||||
private final T value; |
||||
|
||||
public GenericFactoryBean(T value) { |
||||
this.value = value; |
||||
} |
||||
|
||||
@Override |
||||
public T getObject() { |
||||
return this.value; |
||||
} |
||||
|
||||
@Override |
||||
public Class<?> getObjectType() { |
||||
return this.value.getClass(); |
||||
} |
||||
} |
||||
|
||||
static class NumberHolder<N extends Number> { |
||||
|
||||
private final N number; |
||||
|
||||
public NumberHolder(N number) { |
||||
this.number = number; |
||||
} |
||||
|
||||
} |
||||
|
||||
static class NumberHolderSample { |
||||
|
||||
@Autowired |
||||
private NumberHolder<Integer> numberHolder; |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,476 @@
@@ -0,0 +1,476 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator.config; |
||||
|
||||
import java.lang.reflect.Constructor; |
||||
import java.lang.reflect.Method; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.stream.Stream; |
||||
|
||||
import org.assertj.core.util.Arrays; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.params.ParameterizedTest; |
||||
import org.junit.jupiter.params.provider.Arguments; |
||||
import org.junit.jupiter.params.provider.MethodSource; |
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException; |
||||
import org.springframework.beans.factory.ObjectProvider; |
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException; |
||||
import org.springframework.beans.factory.config.BeanDefinition; |
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; |
||||
import org.springframework.beans.factory.config.RuntimeBeanReference; |
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition; |
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder; |
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
||||
import org.springframework.beans.factory.support.RootBeanDefinition; |
||||
import org.springframework.core.env.Environment; |
||||
import org.springframework.core.io.DefaultResourceLoader; |
||||
import org.springframework.core.io.ResourceLoader; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
||||
import static org.assertj.core.api.Assertions.entry; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
/** |
||||
* Tests for {@link InjectedConstructionResolver}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class InjectedConstructionResolverTests { |
||||
|
||||
@Test |
||||
void resolveNoArgConstructor() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
InjectedElementAttributes attributes = createResolverForConstructor( |
||||
InjectedConstructionResolverTests.class).resolve(beanFactory); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("singleArgConstruction") |
||||
void resolveSingleArgConstructor(InjectedConstructionResolver resolver) { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerSingleton("one", "1"); |
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
assertThat((String) attributes.get(0)).isEqualTo("1"); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("singleArgConstruction") |
||||
void resolveRequiredDependencyNotPresentThrowsUnsatisfiedDependencyException(InjectedConstructionResolver resolver) { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
assertThatThrownBy(() -> resolver.resolve(beanFactory)) |
||||
.isInstanceOfSatisfying(UnsatisfiedDependencyException.class, ex -> { |
||||
assertThat(ex.getBeanName()).isEqualTo("test"); |
||||
assertThat(ex.getInjectionPoint()).isNotNull(); |
||||
assertThat(ex.getInjectionPoint().getMember()).isEqualTo(resolver.getExecutable()); |
||||
}); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("arrayOfBeansConstruction") |
||||
void resolveArrayOfBeans(InjectedConstructionResolver resolver) { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerSingleton("one", "1"); |
||||
beanFactory.registerSingleton("two", "2"); |
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
Object attribute = attributes.get(0); |
||||
assertThat(Arrays.isArray(attribute)).isTrue(); |
||||
assertThat((Object[]) attribute).containsExactly("1", "2"); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("arrayOfBeansConstruction") |
||||
void resolveRequiredArrayOfBeansInjectEmptyArray(InjectedConstructionResolver resolver) { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
Object attribute = attributes.get(0); |
||||
assertThat(Arrays.isArray(attribute)).isTrue(); |
||||
assertThat((Object[]) attribute).isEmpty(); |
||||
|
||||
} |
||||
|
||||
static Stream<Arguments> arrayOfBeansConstruction() { |
||||
return Stream.of(Arguments.of(createResolverForConstructor(BeansCollectionConstructor.class, String[].class)), |
||||
Arguments.of(createResolverForFactoryMethod(BeansCollectionFactory.class, "array", String[].class))); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("listOfBeansConstruction") |
||||
void resolveListOfBeans(InjectedConstructionResolver resolver) { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerSingleton("one", "1"); |
||||
beanFactory.registerSingleton("two", "2"); |
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
Object attribute = attributes.get(0); |
||||
assertThat(attribute).isInstanceOf(List.class).asList().containsExactly("1", "2"); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("listOfBeansConstruction") |
||||
void resolveRequiredListOfBeansInjectEmptyList(InjectedConstructionResolver resolver) { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
Object attribute = attributes.get(0); |
||||
assertThat(attribute).isInstanceOf(List.class); |
||||
assertThat((List<?>) attribute).isEmpty(); |
||||
} |
||||
|
||||
static Stream<Arguments> listOfBeansConstruction() { |
||||
return Stream.of(Arguments.of(createResolverForConstructor(BeansCollectionConstructor.class, List.class)), |
||||
Arguments.of(createResolverForFactoryMethod(BeansCollectionFactory.class, "list", List.class))); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("setOfBeansConstruction") |
||||
@SuppressWarnings("unchecked") |
||||
void resolveSetOfBeans(InjectedConstructionResolver resolver) { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerSingleton("one", "1"); |
||||
beanFactory.registerSingleton("two", "2"); |
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
Object attribute = attributes.get(0); |
||||
assertThat(attribute).isInstanceOf(Set.class); |
||||
assertThat((Set<String>) attribute).containsExactly("1", "2"); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("setOfBeansConstruction") |
||||
void resolveRequiredSetOfBeansInjectEmptySet(InjectedConstructionResolver resolver) { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
Object attribute = attributes.get(0); |
||||
assertThat(attribute).isInstanceOf(Set.class); |
||||
assertThat((Set<?>) attribute).isEmpty(); |
||||
} |
||||
|
||||
static Stream<Arguments> setOfBeansConstruction() { |
||||
return Stream.of(Arguments.of(createResolverForConstructor(BeansCollectionConstructor.class, Set.class)), |
||||
Arguments.of(createResolverForFactoryMethod(BeansCollectionFactory.class, "set", Set.class))); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("mapOfBeansConstruction") |
||||
@SuppressWarnings("unchecked") |
||||
void resolveMapOfBeans(InjectedConstructionResolver resolver) { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerSingleton("one", "1"); |
||||
beanFactory.registerSingleton("two", "2"); |
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
Object attribute = attributes.get(0); |
||||
assertThat(attribute).isInstanceOf(Map.class); |
||||
assertThat((Map<String, String>) attribute).containsExactly(entry("one", "1"), entry("two", "2")); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("mapOfBeansConstruction") |
||||
void resolveRequiredMapOfBeansInjectEmptySet(InjectedConstructionResolver resolver) { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
Object attribute = attributes.get(0); |
||||
assertThat(attribute).isInstanceOf(Map.class); |
||||
assertThat((Map<?, ?>) attribute).isEmpty(); |
||||
} |
||||
|
||||
static Stream<Arguments> mapOfBeansConstruction() { |
||||
return Stream.of(Arguments.of(createResolverForConstructor(BeansCollectionConstructor.class, Map.class)), |
||||
Arguments.of(createResolverForFactoryMethod(BeansCollectionFactory.class, "map", Map.class))); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("multiArgsConstruction") |
||||
void resolveMultiArgsConstructor(InjectedConstructionResolver resolver) { |
||||
ResourceLoader resourceLoader = new DefaultResourceLoader(); |
||||
Environment environment = mock(Environment.class); |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerResolvableDependency(ResourceLoader.class, resourceLoader); |
||||
beanFactory.registerSingleton("environment", environment); |
||||
beanFactory.registerSingleton("one", "1"); |
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
assertThat((ResourceLoader) attributes.get(0)).isEqualTo(resourceLoader); |
||||
assertThat((Environment) attributes.get(1)).isEqualTo(environment); |
||||
ObjectProvider<String> provider = attributes.get(2); |
||||
assertThat(provider.getIfAvailable()).isEqualTo("1"); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("mixedArgsConstruction") |
||||
void resolveMixedArgsConstructorWithUserValue(InjectedConstructionResolver resolver) { |
||||
ResourceLoader resourceLoader = new DefaultResourceLoader(); |
||||
Environment environment = mock(Environment.class); |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerResolvableDependency(ResourceLoader.class, resourceLoader); |
||||
beanFactory.registerSingleton("environment", environment); |
||||
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(MixedArgsConstructor.class) |
||||
.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR).getBeanDefinition(); |
||||
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1, "user-value"); |
||||
beanFactory.registerBeanDefinition("test", beanDefinition); |
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
assertThat((ResourceLoader) attributes.get(0)).isEqualTo(resourceLoader); |
||||
assertThat((String) attributes.get(1)).isEqualTo("user-value"); |
||||
assertThat((Environment) attributes.get(2)).isEqualTo(environment); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("mixedArgsConstruction") |
||||
void resolveMixedArgsConstructorWithUserBeanReference(InjectedConstructionResolver resolver) { |
||||
ResourceLoader resourceLoader = new DefaultResourceLoader(); |
||||
Environment environment = mock(Environment.class); |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerResolvableDependency(ResourceLoader.class, resourceLoader); |
||||
beanFactory.registerSingleton("environment", environment); |
||||
beanFactory.registerSingleton("one", "1"); |
||||
beanFactory.registerSingleton("two", "2"); |
||||
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(MixedArgsConstructor.class) |
||||
.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR).getBeanDefinition(); |
||||
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1, new RuntimeBeanReference("two")); |
||||
beanFactory.registerBeanDefinition("test", beanDefinition); |
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
assertThat((ResourceLoader) attributes.get(0)).isEqualTo(resourceLoader); |
||||
assertThat((String) attributes.get(1)).isEqualTo("2"); |
||||
assertThat((Environment) attributes.get(2)).isEqualTo(environment); |
||||
} |
||||
|
||||
@Test |
||||
void resolveUserValueWithTypeConversionRequired() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(CharDependency.class) |
||||
.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR).getBeanDefinition(); |
||||
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, "\\"); |
||||
beanFactory.registerBeanDefinition("test", beanDefinition); |
||||
InjectedElementAttributes attributes = createResolverForConstructor(CharDependency.class, char.class).resolve(beanFactory); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
Object attribute = attributes.get(0); |
||||
assertThat(attribute).isInstanceOf(Character.class); |
||||
assertThat((Character) attribute).isEqualTo('\\'); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("singleArgConstruction") |
||||
void resolveUserValueWithBeanReference(InjectedConstructionResolver resolver) { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerSingleton("stringBean", "string"); |
||||
beanFactory.registerBeanDefinition("test", BeanDefinitionBuilder.rootBeanDefinition(SingleArgConstructor.class) |
||||
.addConstructorArgReference("stringBean").getBeanDefinition()); |
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
Object attribute = attributes.get(0); |
||||
assertThat(attribute).isEqualTo("string"); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("singleArgConstruction") |
||||
void resolveUserValueWithBeanDefinition(InjectedConstructionResolver resolver) { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
AbstractBeanDefinition userValue = BeanDefinitionBuilder.rootBeanDefinition(String.class, () -> "string").getBeanDefinition(); |
||||
beanFactory.registerBeanDefinition("test", BeanDefinitionBuilder.rootBeanDefinition(SingleArgConstructor.class) |
||||
.addConstructorArgValue(userValue).getBeanDefinition()); |
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
Object attribute = attributes.get(0); |
||||
assertThat(attribute).isEqualTo("string"); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("singleArgConstruction") |
||||
void resolveUserValueThatIsAlreadyResolved(InjectedConstructionResolver resolver) { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(SingleArgConstructor.class).getBeanDefinition(); |
||||
ValueHolder valueHolder = new ValueHolder('a'); |
||||
valueHolder.setConvertedValue("this is an a"); |
||||
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, valueHolder); |
||||
beanFactory.registerBeanDefinition("test", beanDefinition); |
||||
InjectedElementAttributes attributes = resolver.resolve(beanFactory); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
Object attribute = attributes.get(0); |
||||
assertThat(attribute).isEqualTo("this is an a"); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@MethodSource("singleArgConstruction") |
||||
void createInvokeFactory(InjectedConstructionResolver resolver) { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerSingleton("one", "1"); |
||||
String instance = resolver.create(beanFactory, attributes -> attributes.get(0)); |
||||
assertThat(instance).isEqualTo("1"); |
||||
} |
||||
|
||||
private static InjectedConstructionResolver createResolverForConstructor(Class<?> beanType, Class<?>... parameterTypes) { |
||||
try { |
||||
Constructor<?> executable = beanType.getDeclaredConstructor(parameterTypes); |
||||
return new InjectedConstructionResolver(executable, beanType, "test", |
||||
InjectedConstructionResolverTests::safeGetBeanDefinition); |
||||
} |
||||
catch (NoSuchMethodException ex) { |
||||
throw new IllegalStateException(ex); |
||||
} |
||||
} |
||||
|
||||
private static InjectedConstructionResolver createResolverForFactoryMethod(Class<?> targetType, |
||||
String methodName, Class<?>... parameterTypes) { |
||||
Method executable = ReflectionUtils.findMethod(targetType, methodName, parameterTypes); |
||||
return new InjectedConstructionResolver(executable, targetType, "test", |
||||
InjectedConstructionResolverTests::safeGetBeanDefinition); |
||||
} |
||||
|
||||
private static BeanDefinition safeGetBeanDefinition(DefaultListableBeanFactory beanFactory) { |
||||
try { |
||||
return beanFactory.getBeanDefinition("test"); |
||||
} |
||||
catch (NoSuchBeanDefinitionException ex) { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
static Stream<Arguments> singleArgConstruction() { |
||||
return Stream.of(Arguments.of(createResolverForConstructor(SingleArgConstructor.class, String.class)), |
||||
Arguments.of(createResolverForFactoryMethod(SingleArgFactory.class, "single", String.class))); |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
static class SingleArgConstructor { |
||||
|
||||
public SingleArgConstructor(String s) { |
||||
} |
||||
|
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
static class SingleArgFactory { |
||||
|
||||
String single(String s) { |
||||
return s; |
||||
} |
||||
|
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
static class BeansCollectionConstructor { |
||||
|
||||
public BeansCollectionConstructor(String[] beans) { |
||||
|
||||
} |
||||
|
||||
public BeansCollectionConstructor(List<String> beans) { |
||||
|
||||
} |
||||
|
||||
public BeansCollectionConstructor(Set<String> beans) { |
||||
|
||||
} |
||||
|
||||
public BeansCollectionConstructor(Map<String, String> beans) { |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
static class BeansCollectionFactory { |
||||
|
||||
public String array(String[] beans) { |
||||
return "test"; |
||||
} |
||||
|
||||
public String list(List<String> beans) { |
||||
return "test"; |
||||
} |
||||
|
||||
public String set(Set<String> beans) { |
||||
return "test"; |
||||
} |
||||
|
||||
public String map(Map<String, String> beans) { |
||||
return "test"; |
||||
} |
||||
|
||||
} |
||||
|
||||
static Stream<Arguments> multiArgsConstruction() { |
||||
return Stream.of( |
||||
Arguments.of(createResolverForConstructor(MultiArgsConstructor.class, ResourceLoader.class, |
||||
Environment.class, ObjectProvider.class)), |
||||
Arguments.of(createResolverForFactoryMethod(MultiArgsFactory.class, "multiArgs", ResourceLoader.class, |
||||
Environment.class, ObjectProvider.class))); |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
static class MultiArgsConstructor { |
||||
|
||||
public MultiArgsConstructor(ResourceLoader resourceLoader, Environment environment, ObjectProvider<String> provider) { |
||||
} |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
static class MultiArgsFactory { |
||||
|
||||
String multiArgs(ResourceLoader resourceLoader, Environment environment, ObjectProvider<String> provider) { |
||||
return "test"; |
||||
} |
||||
} |
||||
|
||||
static Stream<Arguments> mixedArgsConstruction() { |
||||
return Stream.of( |
||||
Arguments.of(createResolverForConstructor(MixedArgsConstructor.class, ResourceLoader.class, |
||||
String.class, Environment.class)), |
||||
Arguments.of(createResolverForFactoryMethod(MixedArgsFactory.class, "mixedArgs", ResourceLoader.class, |
||||
String.class, Environment.class))); |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
static class MixedArgsConstructor { |
||||
|
||||
public MixedArgsConstructor(ResourceLoader resourceLoader, String test, Environment environment) { |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
static class MixedArgsFactory { |
||||
|
||||
String mixedArgs(ResourceLoader resourceLoader, String test, Environment environment) { |
||||
return "test"; |
||||
} |
||||
|
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
static class CharDependency { |
||||
|
||||
CharDependency(char escapeChar) { |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,91 @@
@@ -0,0 +1,91 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator.config; |
||||
|
||||
import java.util.Collections; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verify; |
||||
import static org.mockito.Mockito.verifyNoInteractions; |
||||
|
||||
/** |
||||
* Tests for {@link InjectedElementAttributes}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class InjectedElementAttributesTests { |
||||
|
||||
private static final InjectedElementAttributes unresolved = new InjectedElementAttributes(null); |
||||
|
||||
private static final InjectedElementAttributes resolved = new InjectedElementAttributes(Collections.singletonList("test")); |
||||
|
||||
@Test |
||||
void isResolvedWithUnresolvedAttributes() { |
||||
assertThat(unresolved.isResolved()).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void isResolvedWithResoledAttributes() { |
||||
assertThat(resolved.isResolved()).isTrue(); |
||||
} |
||||
|
||||
@Test |
||||
void ifResolvedWithUnresolvedAttributesDoesNotInvokeRunnable() { |
||||
Runnable runnable = mock(Runnable.class); |
||||
unresolved.ifResolved(runnable); |
||||
verifyNoInteractions(runnable); |
||||
} |
||||
|
||||
@Test |
||||
void ifResolvedWithResolvedAttributesInvokesRunnable() { |
||||
Runnable runnable = mock(Runnable.class); |
||||
resolved.ifResolved(runnable); |
||||
verify(runnable).run(); |
||||
} |
||||
|
||||
@Test |
||||
@SuppressWarnings("unchecked") |
||||
void ifResolvedWithUnresolvedAttributesDoesNotInvokeConsumer() { |
||||
BeanDefinitionRegistrar.ThrowableConsumer<InjectedElementAttributes> consumer = mock(BeanDefinitionRegistrar.ThrowableConsumer.class); |
||||
unresolved.ifResolved(consumer); |
||||
verifyNoInteractions(consumer); |
||||
} |
||||
|
||||
@Test |
||||
@SuppressWarnings("unchecked") |
||||
void ifResolvedWithResolvedAttributesInvokesConsumer() { |
||||
BeanDefinitionRegistrar.ThrowableConsumer<InjectedElementAttributes> consumer = mock(BeanDefinitionRegistrar.ThrowableConsumer.class); |
||||
resolved.ifResolved(consumer); |
||||
verify(consumer).accept(resolved); |
||||
} |
||||
|
||||
@Test |
||||
void getWithAvailableAttribute() { |
||||
InjectedElementAttributes attributes = new InjectedElementAttributes(Collections.singletonList("test")); |
||||
assertThat((String) attributes.get(0)).isEqualTo("test"); |
||||
} |
||||
|
||||
@Test |
||||
void getWithTypeAndAvailableAttribute() { |
||||
InjectedElementAttributes attributes = new InjectedElementAttributes(Collections.singletonList("test")); |
||||
assertThat(attributes.get(0, String.class)).isEqualTo("test"); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator.config; |
||||
|
||||
import java.lang.reflect.Field; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException; |
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
||||
|
||||
/** |
||||
* Tests for {@link InjectedFieldResolver}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class InjectedFieldResolverTests { |
||||
|
||||
@Test |
||||
void resolveDependency() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerSingleton("one", "1"); |
||||
InjectedElementAttributes attributes = createResolver(TestBean.class, "string", |
||||
String.class).resolve(beanFactory, true); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
assertThat((String) attributes.get(0)).isEqualTo("1"); |
||||
} |
||||
|
||||
@Test |
||||
void resolveRequiredDependencyNotPresentThrowsUnsatisfiedDependencyException() { |
||||
Field field = ReflectionUtils.findField(TestBean.class, "string", String.class); |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
assertThatThrownBy(() -> createResolver(TestBean.class, "string", String.class).resolve(beanFactory)) |
||||
.isInstanceOfSatisfying(UnsatisfiedDependencyException.class, ex -> { |
||||
assertThat(ex.getBeanName()).isEqualTo("test"); |
||||
assertThat(ex.getInjectionPoint()).isNotNull(); |
||||
assertThat(ex.getInjectionPoint().getField()).isEqualTo(field); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void resolveNonRequiredDependency() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
InjectedElementAttributes attributes = createResolver(TestBean.class, "string", String.class).resolve(beanFactory, false); |
||||
assertThat(attributes.isResolved()).isFalse(); |
||||
} |
||||
|
||||
private InjectedFieldResolver createResolver(Class<?> beanType, String fieldName, Class<?> fieldType) { |
||||
Field field = ReflectionUtils.findField(beanType, fieldName, fieldType); |
||||
assertThat(field).isNotNull(); |
||||
return new InjectedFieldResolver(field, "test"); |
||||
} |
||||
|
||||
static class TestBean { |
||||
|
||||
private String string; |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,124 @@
@@ -0,0 +1,124 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.factory.generator.config; |
||||
|
||||
import java.lang.reflect.Method; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException; |
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
||||
import org.springframework.core.env.Environment; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verifyNoInteractions; |
||||
|
||||
/** |
||||
* Tests for {@link InjectedMethodResolver}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class InjectedMethodResolverTests { |
||||
|
||||
@Test |
||||
void resolveSingleDependency() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerSingleton("test", "testValue"); |
||||
InjectedElementAttributes attributes = createResolver(TestBean.class, "injectString", String.class) |
||||
.resolve(beanFactory, true); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
assertThat((String) attributes.get(0)).isEqualTo("testValue"); |
||||
} |
||||
|
||||
@Test |
||||
void resolveRequiredDependencyNotPresentThrowsUnsatisfiedDependencyException() { |
||||
Method method = ReflectionUtils.findMethod(TestBean.class, "injectString", String.class); |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
assertThatThrownBy(() -> createResolver(TestBean.class, "injectString", String.class) |
||||
.resolve(beanFactory)).isInstanceOfSatisfying(UnsatisfiedDependencyException.class, ex -> { |
||||
assertThat(ex.getBeanName()).isEqualTo("test"); |
||||
assertThat(ex.getInjectionPoint()).isNotNull(); |
||||
assertThat(ex.getInjectionPoint().getMember()).isEqualTo(method); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void resolveNonRequiredDependency() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
InjectedElementAttributes attributes = createResolver(TestBean.class, "injectString", String.class) |
||||
.resolve(beanFactory, false); |
||||
assertThat(attributes.isResolved()).isFalse(); |
||||
} |
||||
|
||||
@Test |
||||
void resolveDependencyAndEnvironment() { |
||||
Environment environment = mock(Environment.class); |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
beanFactory.registerSingleton("environment", environment); |
||||
beanFactory.registerSingleton("test", "testValue"); |
||||
InjectedElementAttributes attributes = createResolver(TestBean.class, "injectStringAndEnvironment", |
||||
String.class, Environment.class).resolve(beanFactory, true); |
||||
assertThat(attributes.isResolved()).isTrue(); |
||||
String string = attributes.get(0); |
||||
assertThat(string).isEqualTo("testValue"); |
||||
assertThat((Environment) attributes.get(1)).isEqualTo(environment); |
||||
} |
||||
|
||||
@Test |
||||
@SuppressWarnings("unchecked") |
||||
void createWithUnresolvedAttributesDoesNotInvokeCallback() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
BeanDefinitionRegistrar.ThrowableFunction<InjectedElementAttributes, ?> callback = mock(BeanDefinitionRegistrar.ThrowableFunction.class); |
||||
assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() -> |
||||
createResolver(TestBean.class, "injectString", String.class).create(beanFactory, callback)); |
||||
verifyNoInteractions(callback); |
||||
} |
||||
|
||||
@Test |
||||
@SuppressWarnings("unchecked") |
||||
void invokeWithUnresolvedAttributesDoesNotInvokeCallback() { |
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); |
||||
BeanDefinitionRegistrar.ThrowableConsumer<InjectedElementAttributes> callback = mock(BeanDefinitionRegistrar.ThrowableConsumer.class); |
||||
assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() -> |
||||
createResolver(TestBean.class, "injectString", String.class).invoke(beanFactory, callback)); |
||||
verifyNoInteractions(callback); |
||||
} |
||||
|
||||
private InjectedMethodResolver createResolver(Class<?> beanType, String methodName, Class<?>... parameterTypes) { |
||||
Method method = ReflectionUtils.findMethod(beanType, methodName, parameterTypes); |
||||
assertThat(method).isNotNull(); |
||||
return new InjectedMethodResolver(method, beanType, "test"); |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
static class TestBean { |
||||
|
||||
public void injectString(String string) { |
||||
|
||||
} |
||||
|
||||
public void injectStringAndEnvironment(String string, Environment environment) { |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.testfixture.beans.factory.generator; |
||||
|
||||
import org.springframework.core.env.Environment; |
||||
|
||||
public class InnerComponentConfiguration { |
||||
|
||||
public class NoDependencyComponent { |
||||
|
||||
public NoDependencyComponent() { |
||||
|
||||
} |
||||
} |
||||
|
||||
public class EnvironmentAwareComponent { |
||||
|
||||
public EnvironmentAwareComponent(Environment environment) { |
||||
|
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.testfixture.beans.factory.generator; |
||||
|
||||
public class SimpleConfiguration { |
||||
|
||||
public SimpleConfiguration() { |
||||
} |
||||
|
||||
public String stringBean() { |
||||
return "Hello"; |
||||
} |
||||
|
||||
public Integer integerBean() { |
||||
return 42; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.testfixture.beans.factory.generator.factory; |
||||
|
||||
import java.io.Serializable; |
||||
|
||||
/** |
||||
* A sample object with a generic type. |
||||
* |
||||
* @param <T> the number type |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
@SuppressWarnings("serial") |
||||
public class NumberHolder<T extends Number> implements Serializable { |
||||
|
||||
private final T number; |
||||
|
||||
public NumberHolder(T number) { |
||||
this.number = number; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.testfixture.beans.factory.generator.factory; |
||||
|
||||
import org.springframework.beans.factory.FactoryBean; |
||||
|
||||
/** |
||||
* A sample factory bean with a generic type. |
||||
* |
||||
* @param <T> the type of the number generated by this factory bean |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
public class NumberHolderFactoryBean<T extends Number> implements FactoryBean<NumberHolder<T>> { |
||||
|
||||
private T number; |
||||
|
||||
public void setNumber(T number) { |
||||
this.number = number; |
||||
} |
||||
|
||||
@Override |
||||
public NumberHolder<T> getObject() { |
||||
return new NumberHolder<>(this.number); |
||||
} |
||||
|
||||
@Override |
||||
public Class<?> getObjectType() { |
||||
return NumberHolder.class; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.testfixture.beans.factory.generator.factory; |
||||
|
||||
public abstract class SampleFactory { |
||||
|
||||
public static String create(String testBean) { |
||||
return testBean; |
||||
} |
||||
|
||||
public static String create(char character) { |
||||
return String.valueOf(character); |
||||
} |
||||
|
||||
public static String create(Number number, String test) { |
||||
return number + test; |
||||
} |
||||
|
||||
public static String create(Class<?> type) { |
||||
return type.getName(); |
||||
} |
||||
|
||||
public static Integer integerBean() { |
||||
return 42; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.testfixture.beans.factory.generator.injection; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
||||
@SuppressWarnings("unused") |
||||
public class InjectionComponent { |
||||
|
||||
private final String bean; |
||||
|
||||
private Integer counter; |
||||
|
||||
public InjectionComponent(String bean) { |
||||
this.bean = bean; |
||||
} |
||||
|
||||
@Autowired(required = false) |
||||
public void setCounter(Integer counter) { |
||||
this.counter = counter; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.testfixture.beans.factory.generator.property; |
||||
|
||||
public class ConfigurableBean { |
||||
|
||||
private String name; |
||||
|
||||
private Integer counter; |
||||
|
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
public void setCounter(Integer counter) { |
||||
this.counter = counter; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.testfixture.beans.factory.generator.visibility; |
||||
|
||||
public class ProtectedConstructorComponent { |
||||
|
||||
ProtectedConstructorComponent() { |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
/* |
||||
* Copyright 2002-2022 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.beans.testfixture.beans.factory.generator.visibility; |
||||
|
||||
public class ProtectedFactoryMethod { |
||||
|
||||
String testBean(Integer number) { |
||||
return "test-" + number; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,68 @@
@@ -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(")"); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,62 @@
@@ -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)); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue