Add bean instance generator infrastructure

This commit provides the necessary infrastructure to let components
contribute statements that are used to fully instantiate a bean
instance.

To ease code generation, a dedicated infrastructure to register bean
definition is provided in the o.s.beans.factory.generator package.
BeanDefinitionRegistrar offers a builder style API that provides a way
to hide how injected elements are resolved at runtime and let
contributors provide code that may throw a checked exception.

BeanInstanceContributor is the interface that components can implement
to contribute to a bean instance setup. DefaultBeanInstanceGenerator
generates, for a particular bean definition, the necessary statements
to instantiate a bean.

Closes gh-28047
This commit is contained in:
Stephane Nicoll
2022-02-14 14:31:19 +01:00
parent bfe9d4fc49
commit c5e1a774a5
33 changed files with 4119 additions and 11 deletions

View File

@@ -0,0 +1,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";
}
}
}

View File

@@ -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;
}
}

View File

@@ -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) {
}
}
}

View File

@@ -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;
}
}

View File

@@ -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) {
}
}
}

View File

@@ -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");
}
}

View File

@@ -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;
}
}

View File

@@ -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) {
}
}
}