Complete refactor of AOT concepts

Remove the AOT code that now has an alternative API.

Closes gh-28414
This commit is contained in:
Phillip Webb
2022-05-04 20:23:24 -07:00
parent 702207d9ee
commit 16e7f1f212
83 changed files with 9 additions and 10950 deletions

View File

@@ -1,170 +0,0 @@
/*
* 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.annotation;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generator.CodeContribution;
import org.springframework.aot.generator.DefaultCodeContribution;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessorTests.ResourceInjectionBean;
import org.springframework.beans.factory.generator.BeanInstantiationContribution;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.core.env.Environment;
import org.springframework.javapoet.support.CodeSnippet;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for code contribution of {@link AutowiredAnnotationBeanPostProcessor}.
*
* @author Stephane Nicoll
*/
class AutowiredAnnotationBeanInstantiationContributionTests {
@Test
void contributeWithPackageProtectedFieldInjection() {
CodeContribution contribution = contribute(PackageProtectedFieldInjectionSample.class);
assertThat(CodeSnippet.process(contribution.statements().toLambdaBody())).isEqualTo("""
instanceContext.field("environment")
.invoke(beanFactory, (attributes) -> bean.environment = attributes.get(0))""");
assertThat(contribution.runtimeHints().reflection().typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(PackageProtectedFieldInjectionSample.class));
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint -> {
assertThat(fieldHint.getName()).isEqualTo("environment");
assertThat(fieldHint.isAllowWrite()).isTrue();
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse();
});
});
assertThat(contribution.protectedAccess().getPrivilegedPackageName("com.example"))
.isEqualTo(PackageProtectedFieldInjectionSample.class.getPackageName());
}
@Test
void contributeWithPrivateFieldInjection() {
CodeContribution contribution = contribute(PrivateFieldInjectionSample.class);
assertThat(CodeSnippet.process(contribution.statements().toLambdaBody())).isEqualTo("""
instanceContext.field("environment")
.invoke(beanFactory, (attributes) -> {
Field environmentField = ReflectionUtils.findField(AutowiredAnnotationBeanInstantiationContributionTests.PrivateFieldInjectionSample.class, "environment");
ReflectionUtils.makeAccessible(environmentField);
ReflectionUtils.setField(environmentField, bean, attributes.get(0));
})""");
assertThat(contribution.runtimeHints().reflection().typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(PrivateFieldInjectionSample.class));
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint -> {
assertThat(fieldHint.getName()).isEqualTo("environment");
assertThat(fieldHint.isAllowWrite()).isTrue();
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse();
});
});
assertThat(contribution.protectedAccess().isAccessible("com.example")).isTrue();
}
@Test
void contributeWithPublicMethodInjection() {
CodeContribution contribution = contribute(PublicMethodInjectionSample.class);
assertThat(CodeSnippet.process(contribution.statements().toLambdaBody())).isEqualTo("""
instanceContext.method("setTestBean", TestBean.class)
.invoke(beanFactory, (attributes) -> bean.setTestBean(attributes.get(0)))""");
assertThat(contribution.runtimeHints().reflection().typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(PublicMethodInjectionSample.class));
assertThat(typeHint.methods()).singleElement().satisfies(methodHint -> {
assertThat(methodHint.getName()).isEqualTo("setTestBean");
assertThat(methodHint.getModes()).contains(ExecutableMode.INTROSPECT);
});
});
assertThat(contribution.protectedAccess().isAccessible("com.example")).isTrue();
}
@Test
void contributeWithInjectionPoints() {
CodeContribution contribution = contribute(ResourceInjectionBean.class);
assertThat(CodeSnippet.process(contribution.statements().toLambdaBody())).isEqualTo("""
instanceContext.field("testBean")
.resolve(beanFactory, false).ifResolved((attributes) -> {
Field testBeanField = ReflectionUtils.findField(AutowiredAnnotationBeanPostProcessorTests.ResourceInjectionBean.class, "testBean");
ReflectionUtils.makeAccessible(testBeanField);
ReflectionUtils.setField(testBeanField, bean, attributes.get(0));
});
instanceContext.method("setTestBean2", TestBean.class)
.invoke(beanFactory, (attributes) -> bean.setTestBean2(attributes.get(0)));""");
assertThat(contribution.runtimeHints().reflection().typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint ->
assertThat(fieldHint.getName()).isEqualTo("testBean"));
assertThat(typeHint.methods()).singleElement().satisfies(methodHint ->
assertThat(methodHint.getName()).isEqualTo("setTestBean2"));
});
assertThat(contribution.protectedAccess().isAccessible("com.example")).isTrue();
}
@Test
void contributeWithoutInjectionPoints() {
BeanInstantiationContribution contributor = createContribution(String.class);
assertThat(contributor).isNull();
}
private DefaultCodeContribution contribute(Class<?> type) {
BeanInstantiationContribution contributor = createContribution(type);
assertThat(contributor).isNotNull();
DefaultCodeContribution contribution = new DefaultCodeContribution(new RuntimeHints());
contributor.applyTo(contribution);
return contribution;
}
@Nullable
private BeanInstantiationContribution createContribution(Class<?> type) {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
RootBeanDefinition beanDefinition = new RootBeanDefinition(type);
return bpp.contribute(beanDefinition, type, "test");
}
public static class PackageProtectedFieldInjectionSample {
@Autowired
Environment environment;
}
public static class PrivateFieldInjectionSample {
@Autowired
@SuppressWarnings("unused")
private Environment environment;
}
public static class PublicMethodInjectionSample {
@Autowired
public void setTestBean(TestBean testBean) {
}
public void setUnrelated(String unrelated) {
}
}
}

View File

@@ -18,7 +18,6 @@ package org.springframework.beans.factory.annotation;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.generator.BeanInstantiationContribution;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -26,11 +25,8 @@ import org.springframework.beans.testfixture.beans.factory.generator.lifecycle.D
import org.springframework.beans.testfixture.beans.factory.generator.lifecycle.Init;
import org.springframework.beans.testfixture.beans.factory.generator.lifecycle.InitDestroyBean;
import org.springframework.beans.testfixture.beans.factory.generator.lifecycle.MultiInitDestroyBean;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
/**
* Tests for {@link InitDestroyAnnotationBeanPostProcessor}.
@@ -42,60 +38,6 @@ class InitDestroyAnnotationBeanPostProcessorTests {
private DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
@Test
void contributeWithNoCallbackDoesNotMutateRootBeanDefinition() {
RootBeanDefinition beanDefinition = mock(RootBeanDefinition.class);
assertThat(createAotBeanPostProcessor().contribute(
beanDefinition, String.class, "test")).isNull();
verifyNoInteractions(beanDefinition);
}
@Test
void contributeWithInitDestroyCallback() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyBean.class);
assertThat(createContribution(beanDefinition)).isNull();
assertThat(beanDefinition.getInitMethodNames()).containsExactly("initMethod");
assertThat(beanDefinition.getDestroyMethodNames()).containsExactly("destroyMethod");
}
@Test
void contributeWithInitDestroyCallbackRetainCustomMethods() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyBean.class);
beanDefinition.setInitMethodName("customInitMethod");
beanDefinition.setDestroyMethodNames("customDestroyMethod");
assertThat(createContribution(beanDefinition)).isNull();
assertThat(beanDefinition.getInitMethodNames())
.containsExactly("customInitMethod", "initMethod");
assertThat(beanDefinition.getDestroyMethodNames())
.containsExactly("customDestroyMethod", "destroyMethod");
}
@Test
void contributeWithInitDestroyCallbackFilterDuplicates() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyBean.class);
beanDefinition.setInitMethodName("initMethod");
beanDefinition.setDestroyMethodNames("destroyMethod");
assertThat(createContribution(beanDefinition)).isNull();
assertThat(beanDefinition.getInitMethodNames()).containsExactly("initMethod");
assertThat(beanDefinition.getDestroyMethodNames()).containsExactly("destroyMethod");
}
@Test
void contributeWithMultipleInitDestroyCallbacks() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(MultiInitDestroyBean.class);
assertThat(createContribution(beanDefinition)).isNull();
assertThat(beanDefinition.getInitMethodNames())
.containsExactly("initMethod", "anotherInitMethod");
assertThat(beanDefinition.getDestroyMethodNames())
.containsExactly("anotherDestroyMethod", "destroyMethod");
}
@Nullable
private BeanInstantiationContribution createContribution(RootBeanDefinition beanDefinition) {
InitDestroyAnnotationBeanPostProcessor bpp = createAotBeanPostProcessor();
return bpp.contribute(beanDefinition, beanDefinition.getResolvableType().toClass(), "test");
}
@Test
void processAheadOfTimeWhenNoCallbackDoesNotMutateRootBeanDefinition() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(String.class);

View File

@@ -1,216 +0,0 @@
/*
* 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.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;
import java.util.function.BiPredicate;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.BDDMockito;
import org.mockito.Mockito;
import org.springframework.aot.generator.DefaultGeneratedTypeContext;
import org.springframework.aot.generator.GeneratedType;
import org.springframework.aot.generator.GeneratedTypeContext;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.support.CodeSnippet;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link BeanDefinitionsContribution}.
*
* @author Stephane Nicoll
*/
class BeanDefinitionsContributionTests {
@Test
void loadContributorWithConstructorArgumentOnBeanFactory() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.setBeanClassLoader(new TestSpringFactoriesClassLoader(
"bean-registration-contribution-provider-constructor.factories"));
BeanDefinitionsContribution contribution = new BeanDefinitionsContribution(beanFactory);
assertThat(contribution).extracting("contributionProviders").asList()
.anySatisfy(provider -> assertThat(provider).isInstanceOfSatisfying(TestConstructorBeanRegistrationContributionProvider.class,
testProvider -> assertThat(testProvider.beanFactory).isSameAs(beanFactory)))
.anySatisfy(provider -> assertThat(provider).isInstanceOf(DefaultBeanRegistrationContributionProvider.class))
.hasSize(2);
}
@Test
void contributeThrowsContributionNotFoundIfNoContributionIsAvailable() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition("test", new RootBeanDefinition());
BeanDefinitionsContribution contribution = new BeanDefinitionsContribution(beanFactory,
List.of(Mockito.mock(BeanRegistrationContributionProvider.class)));
BeanFactoryInitialization initialization = new BeanFactoryInitialization(createGenerationContext());
assertThatThrownBy(() -> contribution.applyTo(initialization))
.isInstanceOfSatisfying(BeanRegistrationContributionNotFoundException.class, ex -> {
assertThat(ex.getBeanName()).isEqualTo("test");
assertThat(ex.getBeanDefinition()).isSameAs(beanFactory.getMergedBeanDefinition("test"));
});
}
@Test
void contributeThrowsBeanRegistrationExceptionIfContributionThrowsException() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition("test", new RootBeanDefinition());
BeanFactoryContribution testContribution = Mockito.mock(BeanFactoryContribution.class);
IllegalStateException testException = new IllegalStateException();
BDDMockito.willThrow(testException).given(testContribution).applyTo(ArgumentMatchers.any(BeanFactoryInitialization.class));
BeanDefinitionsContribution contribution = new BeanDefinitionsContribution(beanFactory,
List.of(new TestBeanRegistrationContributionProvider("test", testContribution)));
BeanFactoryInitialization initialization = new BeanFactoryInitialization(createGenerationContext());
assertThatThrownBy(() -> contribution.applyTo(initialization))
.isInstanceOfSatisfying(BeanDefinitionGenerationException.class, ex -> {
assertThat(ex.getBeanName()).isEqualTo("test");
assertThat(ex.getBeanDefinition()).isSameAs(beanFactory.getMergedBeanDefinition("test"));
assertThat(ex.getCause()).isEqualTo(testException);
});
}
@Test
void contributeGeneratesBeanDefinitionsInOrder() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition("counter", BeanDefinitionBuilder
.rootBeanDefinition(Integer.class, "valueOf").addConstructorArgValue(42).getBeanDefinition());
beanFactory.registerBeanDefinition("name", BeanDefinitionBuilder
.rootBeanDefinition(String.class).addConstructorArgValue("Hello").getBeanDefinition());
CodeSnippet code = contribute(beanFactory, createGenerationContext());
assertThat(code.getSnippet()).isEqualTo("""
BeanDefinitionRegistrar.of("counter", Integer.class).withFactoryMethod(Integer.class, "valueOf", int.class)
.instanceSupplier((instanceContext) -> instanceContext.create(beanFactory, (attributes) -> Integer.valueOf(attributes.get(0, int.class)))).customize((bd) -> bd.getConstructorArgumentValues().addIndexedArgumentValue(0, 42)).register(beanFactory);
BeanDefinitionRegistrar.of("name", String.class).withConstructor(String.class)
.instanceSupplier((instanceContext) -> instanceContext.create(beanFactory, (attributes) -> new String(attributes.get(0, String.class)))).customize((bd) -> bd.getConstructorArgumentValues().addIndexedArgumentValue(0, "Hello")).register(beanFactory);
""");
}
@Test
void getBeanDefinitionWithNoUnderlyingContributorReturnFalseByDefault() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
BiPredicate<String, BeanDefinition> excludeFilter = new BeanDefinitionsContribution(beanFactory)
.getBeanDefinitionExcludeFilter();
assertThat(excludeFilter.test("foo", new RootBeanDefinition())).isFalse();
}
@Test
@SuppressWarnings("unchecked")
void getBeanDefinitionExcludeFilterWrapsUnderlyingFilter() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition("bean1", new RootBeanDefinition());
beanFactory.registerBeanDefinition("bean2", new RootBeanDefinition());
BiPredicate<String, BeanDefinition> excludeFilter1 = Mockito.mock(BiPredicate.class);
BDDMockito.given(excludeFilter1.test(ArgumentMatchers.eq("bean1"), ArgumentMatchers.any(BeanDefinition.class))).willReturn(Boolean.TRUE);
BDDMockito.given(excludeFilter1.test(ArgumentMatchers.eq("bean2"), ArgumentMatchers.any(BeanDefinition.class))).willReturn(Boolean.FALSE);
BiPredicate<String, BeanDefinition> excludeFilter2 = Mockito.mock(BiPredicate.class);
BDDMockito.given(excludeFilter2.test(ArgumentMatchers.eq("bean2"), ArgumentMatchers.any(BeanDefinition.class))).willReturn(Boolean.TRUE);
BiPredicate<String, BeanDefinition> excludeFilter = new BeanDefinitionsContribution(beanFactory, List.of(
new TestBeanRegistrationContributionProvider("bean1", mockExcludeFilter(excludeFilter1)),
new TestBeanRegistrationContributionProvider("bean2", mockExcludeFilter(excludeFilter2)))
).getBeanDefinitionExcludeFilter();
assertThat(excludeFilter.test("bean2", new RootBeanDefinition())).isTrue();
Mockito.verify(excludeFilter1).test(ArgumentMatchers.eq("bean2"), ArgumentMatchers.any(BeanDefinition.class));
Mockito.verify(excludeFilter2).test(ArgumentMatchers.eq("bean2"), ArgumentMatchers.any(BeanDefinition.class));
assertThat(excludeFilter.test("bean1", new RootBeanDefinition())).isTrue();
Mockito.verify(excludeFilter1).test(ArgumentMatchers.eq("bean1"), ArgumentMatchers.any(BeanDefinition.class));
Mockito.verifyNoMoreInteractions(excludeFilter2);
}
private CodeSnippet contribute(DefaultListableBeanFactory beanFactory, GeneratedTypeContext generationContext) {
BeanDefinitionsContribution contribution = new BeanDefinitionsContribution(beanFactory);
BeanFactoryInitialization initialization = new BeanFactoryInitialization(generationContext);
contribution.applyTo(initialization);
return CodeSnippet.of(initialization.toCodeBlock());
}
private GeneratedTypeContext createGenerationContext() {
return new DefaultGeneratedTypeContext("com.example", packageName ->
GeneratedType.of(ClassName.get(packageName, "Test")));
}
private BeanFactoryContribution mockExcludeFilter(BiPredicate<String, BeanDefinition> excludeFilter) {
BeanFactoryContribution contribution = Mockito.mock(BeanFactoryContribution.class);
BDDMockito.given(contribution.getBeanDefinitionExcludeFilter()).willReturn(excludeFilter);
return contribution;
}
static class TestBeanRegistrationContributionProvider implements BeanRegistrationContributionProvider {
private final String beanName;
private final BeanFactoryContribution contribution;
public TestBeanRegistrationContributionProvider(String beanName, BeanFactoryContribution contribution) {
this.beanName = beanName;
this.contribution = contribution;
}
@Override
public BeanFactoryContribution getContributionFor(String beanName, RootBeanDefinition beanDefinition) {
return (beanName.equals(this.beanName) ? this.contribution : null);
}
}
static class TestConstructorBeanRegistrationContributionProvider implements BeanRegistrationContributionProvider {
private final ConfigurableListableBeanFactory beanFactory;
TestConstructorBeanRegistrationContributionProvider(ConfigurableListableBeanFactory beanFactory) {
Assert.notNull(beanFactory, "BeanFactory must not be null");
this.beanFactory = beanFactory;
}
@Nullable
@Override
public BeanFactoryContribution getContributionFor(String beanName, RootBeanDefinition beanDefinition) {
return null;
}
}
static class TestSpringFactoriesClassLoader extends ClassLoader {
private final String factoriesName;
TestSpringFactoriesClassLoader(String factoriesName) {
super(BeanDefinitionsContributionTests.class.getClassLoader());
this.factoriesName = factoriesName;
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
if ("META-INF/spring.factories".equals(name)) {
return super.getResources("org/springframework/beans/factory/generator/" + this.factoriesName);
}
return super.getResources(name);
}
}
}

View File

@@ -1,79 +0,0 @@
/*
* 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.Field;
import org.junit.jupiter.api.Test;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.support.CodeSnippet;
import org.springframework.javapoet.support.MultiStatement;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link BeanFieldGenerator}.
*
* @author Stephane Nicoll
*/
class BeanFieldGeneratorTests {
private final BeanFieldGenerator generator = new BeanFieldGenerator();
@Test
void generateSetFieldWithPublicField() {
MultiStatement statement = this.generator.generateSetValue("bean",
field(SampleBean.class, "one"), CodeBlock.of("$S", "test"));
assertThat(CodeSnippet.process(statement.toCodeBlock())).isEqualTo("""
bean.one = "test";
""");
}
@Test
void generateSetFieldWithPrivateField() {
MultiStatement statement = this.generator.generateSetValue("example",
field(SampleBean.class, "two"), CodeBlock.of("42"));
CodeSnippet code = CodeSnippet.of(statement.toCodeBlock());
assertThat(code.getSnippet()).isEqualTo("""
Field twoField = ReflectionUtils.findField(BeanFieldGeneratorTests.SampleBean.class, "two");
ReflectionUtils.makeAccessible(twoField);
ReflectionUtils.setField(twoField, example, 42);
""");
assertThat(code.hasImport(ReflectionUtils.class)).isTrue();
assertThat(code.hasImport(BeanFieldGeneratorTests.class)).isTrue();
}
private Field field(Class<?> type, String name) {
Field field = ReflectionUtils.findField(type, name);
assertThat(field).isNotNull();
return field;
}
public static class SampleBean {
public String one;
@SuppressWarnings("unused")
private int two;
}
}

View File

@@ -1,267 +0,0 @@
/*
* 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.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;
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 generateCharArray() {
char[] value = new char[] { 'v', 'a', 'l', 'u', 'e' };
assertThat(generate(value, ResolvableType.forArrayComponent(ResolvableType.forClass(char.class))))
.isEqualTo("new char[] { 'v', 'a', 'l', 'u', 'e' }");
}
@Test
void generateStringArray() {
String[] value = new String[] { "a", "test" };
assertThat(generate(value, ResolvableType.forArrayComponent(ResolvableType.forClass(String.class))))
.isEqualTo("new String[] { \"a\", \"test\" }");
}
@Test
void generateStringList() {
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 generateStringManagedList() {
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 generateEmptyList() {
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 generateStringSet() {
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 generateStringManagedSet() {
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 generateEmptySet() {
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 generateMap() {
Map<String, Object> value = new LinkedHashMap<>();
value.put("name", "Hello");
value.put("counter", 42);
assertThat(generate(value)).isEqualTo("Map.of(\"name\", \"Hello\", \"counter\", 42)");
}
@Test
void generateMapWithEnum() {
Map<String, Object> value = new HashMap<>();
value.put("unit", ChronoUnit.DAYS);
assertThat(generate(value)).isEqualTo("Map.of(\"unit\", ChronoUnit.DAYS)");
}
@Test
void generateEmptyMap() {
assertThat(generate(Map.of())).isEqualTo("Map.of()");
}
@Test
void generateString() {
assertThat(generate("test", ResolvableType.forClass(String.class))).isEqualTo("\"test\"");
}
@Test
void generateCharEscapeBackslash() {
assertThat(generate('\\', ResolvableType.forType(char.class))).isEqualTo("'\\\\'");
}
@ParameterizedTest
@MethodSource("primitiveValues")
void generatePrimitiveValue(Object value, String parameter) {
assertThat(generate(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 generateEnum() {
assertThat(generate(ChronoUnit.DAYS, ResolvableType.forClass(ChronoUnit.class)))
.isEqualTo("ChronoUnit.DAYS");
}
@Test
void generateClass() {
assertThat(generate(Integer.class, ResolvableType.forClass(Class.class)))
.isEqualTo("Integer.class");
}
@Test
void generateResolvableType() {
ResolvableType type = ResolvableType.forClassWithGenerics(Consumer.class, Integer.class);
assertThat(generate(type, type))
.isEqualTo("ResolvableType.forClassWithGenerics(Consumer.class, Integer.class)");
}
@Test
void generateExecutableParameterTypesWithConstructor() {
Constructor<?> constructor = TestSample.class.getDeclaredConstructors()[0];
assertThat(CodeSnippet.process(this.generator.generateExecutableParameterTypes(constructor)))
.isEqualTo("String.class, ResourceLoader.class");
}
@Test
void generateExecutableParameterTypesWithNoArgConstructor() {
Constructor<?> constructor = BeanParameterGeneratorTests.class.getDeclaredConstructors()[0];
assertThat(CodeSnippet.process(this.generator.generateExecutableParameterTypes(constructor)))
.isEmpty();
}
@Test
void generateExecutableParameterTypesWithMethod() {
Method method = ReflectionUtils.findMethod(TestSample.class, "createBean", String.class, Integer.class);
assertThat(CodeSnippet.process(this.generator.generateExecutableParameterTypes(method)))
.isEqualTo("String.class, Integer.class");
}
@Test
void generateNull() {
assertThat(generate(null)).isEqualTo("null");
}
@Test
void generateBeanReference() {
BeanReference beanReference = mock(BeanReference.class);
given(beanReference.getBeanName()).willReturn("testBean");
assertThat(generate(beanReference)).isEqualTo("new RuntimeBeanReference(\"testBean\")");
}
@Test
void generateBeanDefinitionCallsConsumer() {
BeanParameterGenerator customGenerator = new BeanParameterGenerator(
beanDefinition -> CodeBlock.of("test"));
assertThat(CodeSnippet.process(customGenerator.generateParameterValue(
new RootBeanDefinition()))).isEqualTo("test");
}
@Test
void generateBeanDefinitionWithoutConsumerFails() {
BeanParameterGenerator customGenerator = new BeanParameterGenerator();
assertThatIllegalStateException().isThrownBy(() -> customGenerator
.generateParameterValue(new RootBeanDefinition()));
}
@Test
void generateUnsupportedParameter() {
assertThatIllegalArgumentException().isThrownBy(() -> generate(new StringWriter()))
.withMessageContaining(StringWriter.class.getName());
}
private String generate(@Nullable Object value) {
return CodeSnippet.process(this.generator.generateParameterValue(value));
}
private String generate(Object value, ResolvableType resolvableType) {
return codeSnippet(value, resolvableType).getSnippet();
}
private CodeSnippet codeSnippet(Object value, ResolvableType resolvableType) {
return CodeSnippet.of(this.generator.generateParameterValue(value, () -> resolvableType));
}
@SuppressWarnings("unused")
static class TestSample {
public TestSample(String test, ResourceLoader resourceLoader) {
}
String createBean(String name, Integer counter) {
return "test";
}
}
}

View File

@@ -1,736 +0,0 @@
/*
* 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.IOException;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.lang.model.element.Modifier;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generator.DefaultGeneratedTypeContext;
import org.springframework.aot.generator.GeneratedType;
import org.springframework.aot.hint.ExecutableHint;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
import org.springframework.aot.test.generator.compile.TestCompiler;
import org.springframework.aot.test.generator.file.SourceFile;
import org.springframework.aot.test.generator.file.SourceFiles;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
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.beans.testfixture.beans.factory.generator.BeanFactoryInitializer;
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.SampleFactory;
import org.springframework.beans.testfixture.beans.factory.generator.injection.InjectionComponent;
import org.springframework.beans.testfixture.beans.factory.generator.lifecycle.InitDestroyBean;
import org.springframework.beans.testfixture.beans.factory.generator.property.ConfigurableBean;
import org.springframework.beans.testfixture.beans.factory.generator.visibility.ProtectedConstructorComponent;
import org.springframework.beans.testfixture.beans.factory.generator.visibility.ProtectedFactoryMethod;
import org.springframework.core.env.Environment;
import org.springframework.core.testfixture.aot.generator.visibility.PublicFactoryBean;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.CodeBlock.Builder;
import org.springframework.javapoet.JavaFile;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.support.CodeSnippet;
import org.springframework.javapoet.support.MultiStatement;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
/**
* Tests for {@link BeanRegistrationBeanFactoryContribution}.
*
* @author Stephane Nicoll
*/
class BeanRegistrationBeanFactoryContributionTests {
private final DefaultGeneratedTypeContext generatedTypeContext = new DefaultGeneratedTypeContext("com.example", packageName -> GeneratedType.of(ClassName.get(packageName, "Test")));
private final BeanFactoryInitialization initialization = new BeanFactoryInitialization(this.generatedTypeContext);
@Test
void generateUsingConstructor() {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(InjectionComponent.class).getBeanDefinition();
CodeSnippet registration = beanRegistration(beanDefinition, singleConstructor(InjectionComponent.class), code -> code.add("() -> test"));
assertThat(registration.getSnippet()).isEqualTo("""
BeanDefinitionRegistrar.of("test", InjectionComponent.class).withConstructor(String.class)
.instanceSupplier(() -> test).register(beanFactory);
""");
}
@Test
void generateUsingConstructorWithNoArgument() {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(SimpleConfiguration.class).getBeanDefinition();
CodeSnippet registration = beanRegistration(beanDefinition, singleConstructor(SimpleConfiguration.class), code -> code.add("() -> test"));
assertThat(registration.getSnippet()).isEqualTo("""
BeanDefinitionRegistrar.of("test", SimpleConfiguration.class)
.instanceSupplier(() -> test).register(beanFactory);
""");
}
@Test
void generateUsingConstructorOnInnerClass() {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(EnvironmentAwareComponent.class).getBeanDefinition();
CodeSnippet registration = beanRegistration(beanDefinition, singleConstructor(EnvironmentAwareComponent.class), code -> code.add("() -> test"));
assertThat(registration.getSnippet()).isEqualTo("""
BeanDefinitionRegistrar.of("test", InnerComponentConfiguration.EnvironmentAwareComponent.class).withConstructor(InnerComponentConfiguration.class, Environment.class)
.instanceSupplier(() -> test).register(beanFactory);
""");
}
@Test
void generateUsingConstructorOnInnerClassWithNoExtraArg() {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(NoDependencyComponent.class).getBeanDefinition();
CodeSnippet registration = beanRegistration(beanDefinition, singleConstructor(NoDependencyComponent.class), code -> code.add("() -> test"));
assertThat(registration.getSnippet()).isEqualTo("""
BeanDefinitionRegistrar.of("test", InnerComponentConfiguration.NoDependencyComponent.class)
.instanceSupplier(() -> test).register(beanFactory);
""");
}
@Test
void generateUsingFactoryMethod() {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(String.class).getBeanDefinition();
CodeSnippet registration = beanRegistration(beanDefinition, method(SampleFactory.class, "create", String.class), code -> code.add("() -> test"));
assertThat(registration.hasImport(SampleFactory.class)).isTrue();
assertThat(registration.getSnippet()).isEqualTo("""
BeanDefinitionRegistrar.of("test", String.class).withFactoryMethod(SampleFactory.class, "create", String.class)
.instanceSupplier(() -> test).register(beanFactory);
""");
}
@Test
void generateUsingFactoryMethodWithNoArgument() {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Integer.class).getBeanDefinition();
CodeSnippet registration = beanRegistration(beanDefinition, method(SampleFactory.class, "integerBean"), code -> code.add("() -> test"));
assertThat(registration.hasImport(SampleFactory.class)).isTrue();
assertThat(registration.getSnippet()).isEqualTo("""
BeanDefinitionRegistrar.of("test", Integer.class).withFactoryMethod(SampleFactory.class, "integerBean")
.instanceSupplier(() -> test).register(beanFactory);
""");
}
@Test
void generateUsingPublicAccessDoesNotAccessAnotherPackage() {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(SimpleConfiguration.class).getBeanDefinition();
getContributionFor(beanDefinition, singleConstructor(SimpleConfiguration.class)).applyTo(this.initialization);
assertThat(this.generatedTypeContext.toJavaFiles()).hasSize(1);
assertThat(CodeSnippet.of(this.initialization.toCodeBlock()).getSnippet()).isEqualTo("""
BeanDefinitionRegistrar.of("test", SimpleConfiguration.class)
.instanceSupplier(SimpleConfiguration::new).register(beanFactory);
""");
}
@Test
void generateUsingProtectedConstructorWritesToBlessedPackage() {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(ProtectedConstructorComponent.class).getBeanDefinition();
getContributionFor(beanDefinition, singleConstructor(ProtectedConstructorComponent.class)).applyTo(this.initialization);
assertThat(this.generatedTypeContext.hasGeneratedType(ProtectedConstructorComponent.class.getPackageName())).isTrue();
GeneratedType generatedType = this.generatedTypeContext.getGeneratedType(ProtectedConstructorComponent.class.getPackageName());
assertThat(removeIndent(codeOf(generatedType), 1)).containsSequence("""
public static void registerTest(DefaultListableBeanFactory beanFactory) {
BeanDefinitionRegistrar.of("test", ProtectedConstructorComponent.class)
.instanceSupplier(ProtectedConstructorComponent::new).register(beanFactory);
}""");
assertThat(CodeSnippet.of(this.initialization.toCodeBlock()).getSnippet()).isEqualTo(
ProtectedConstructorComponent.class.getPackageName() + ".Test.registerTest(beanFactory);\n");
}
@Test
void generateUsingProtectedFactoryMethodWritesToBlessedPackage() {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(String.class).getBeanDefinition();
getContributionFor(beanDefinition, method(ProtectedFactoryMethod.class, "testBean", Integer.class))
.applyTo(this.initialization);
assertThat(this.generatedTypeContext.hasGeneratedType(ProtectedFactoryMethod.class.getPackageName())).isTrue();
GeneratedType generatedType = this.generatedTypeContext.getGeneratedType(ProtectedConstructorComponent.class.getPackageName());
assertThat(removeIndent(codeOf(generatedType), 1)).containsSequence("""
public static void registerProtectedFactoryMethod_test(DefaultListableBeanFactory beanFactory) {
BeanDefinitionRegistrar.of("test", String.class).withFactoryMethod(ProtectedFactoryMethod.class, "testBean", Integer.class)
.instanceSupplier((instanceContext) -> instanceContext.create(beanFactory, (attributes) -> beanFactory.getBean(ProtectedFactoryMethod.class).testBean(attributes.get(0)))).register(beanFactory);
}""");
assertThat(CodeSnippet.of(this.initialization.toCodeBlock()).getSnippet()).isEqualTo(
ProtectedConstructorComponent.class.getPackageName() + ".Test.registerProtectedFactoryMethod_test(beanFactory);\n");
}
@Test
void generateUsingProtectedGenericTypeWritesToBlessedPackage() {
RootBeanDefinition beanDefinition = (RootBeanDefinition) BeanDefinitionBuilder.rootBeanDefinition(
PublicFactoryBean.class).getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, String.class);
// This resolve the generic parameter to a protected type
beanDefinition.setTargetType(PublicFactoryBean.resolveToProtectedGenericParameter());
getContributionFor(beanDefinition, singleConstructor(PublicFactoryBean.class)).applyTo(this.initialization);
assertThat(this.generatedTypeContext.hasGeneratedType(PublicFactoryBean.class.getPackageName())).isTrue();
GeneratedType generatedType = this.generatedTypeContext.getGeneratedType(PublicFactoryBean.class.getPackageName());
assertThat(removeIndent(codeOf(generatedType), 1)).containsSequence("""
public static void registerTest(DefaultListableBeanFactory beanFactory) {
BeanDefinitionRegistrar.of("test", ResolvableType.forClassWithGenerics(PublicFactoryBean.class, ProtectedType.class)).withConstructor(Class.class)
.instanceSupplier((instanceContext) -> instanceContext.create(beanFactory, (attributes) -> new PublicFactoryBean(attributes.get(0)))).customize((bd) -> bd.getConstructorArgumentValues().addIndexedArgumentValue(0, String.class)).register(beanFactory);
}""");
assertThat(CodeSnippet.of(this.initialization.toCodeBlock()).getSnippet()).isEqualTo(
PublicFactoryBean.class.getPackageName() + ".Test.registerTest(beanFactory);\n");
}
@Test
void generateWithBeanDefinitionHavingInitMethodName() {
compile(simpleConfigurationRegistration(bd -> bd.setInitMethodName("someMethod")),
hasBeanDefinition(generatedBd -> assertThat(generatedBd.getInitMethodNames()).containsExactly("someMethod")));
}
@Test
void generateWithBeanDefinitionHavingInitMethodNames() {
compile(simpleConfigurationRegistration(bd -> bd.setInitMethodNames("i1", "i2")),
hasBeanDefinition(generatedBd -> assertThat(generatedBd.getInitMethodNames()).containsExactly("i1", "i2")));
}
@Test
void generateWithBeanDefinitionHavingDestroyMethodName() {
compile(simpleConfigurationRegistration(bd -> bd.setDestroyMethodName("someMethod")),
hasBeanDefinition(generatedBd -> assertThat(generatedBd.getDestroyMethodNames()).containsExactly("someMethod")));
}
@Test
void generateWithBeanDefinitionHavingDestroyMethodNames() {
compile(simpleConfigurationRegistration(bd -> bd.setDestroyMethodNames("d1", "d2")),
hasBeanDefinition(generatedBd -> assertThat(generatedBd.getDestroyMethodNames()).containsExactly("d1", "d2")));
}
@Test
void generateWithBeanDefinitionHavingSyntheticFlag() {
compile(simpleConfigurationRegistration(bd -> bd.setSynthetic(true)),
hasBeanDefinition(generatedBd -> assertThat(generatedBd.isSynthetic()).isTrue()));
}
@Test
void generateWithBeanDefinitionHavingDependsOn() {
compile(simpleConfigurationRegistration(bd -> bd.setDependsOn("test")),
hasBeanDefinition(generatedBd -> assertThat(generatedBd.getDependsOn()).containsExactly("test")));
}
@Test
void generateWithBeanDefinitionHavingLazyInit() {
compile(simpleConfigurationRegistration(bd -> bd.setLazyInit(true)),
hasBeanDefinition(generatedBd -> assertThat(generatedBd.isLazyInit()).isTrue()));
}
@Test
void generateWithBeanDefinitionHavingRole() {
compile(simpleConfigurationRegistration(bd -> bd.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)),
hasBeanDefinition(generatedBd -> assertThat(generatedBd.getRole())
.isEqualTo(BeanDefinition.ROLE_INFRASTRUCTURE)));
}
@Test
void generateWithBeanDefinitionHavingScope() {
compile(simpleConfigurationRegistration(bd -> bd.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)),
hasBeanDefinition(generatedBd -> assertThat(generatedBd.getScope())
.isEqualTo(ConfigurableBeanFactory.SCOPE_PROTOTYPE)));
}
@Test
void generateWithBeanDefinitionHavingAutowiredCandidate() {
compile(simpleConfigurationRegistration(bd -> bd.setAutowireCandidate(false)),
hasBeanDefinition(generatedBd -> assertThat(generatedBd.isAutowireCandidate()).isFalse()));
}
@Test
void generateWithBeanDefinitionHavingDefaultKeepsThem() {
compile(simpleConfigurationRegistration(bd -> {}), hasBeanDefinition(generatedBd -> {
assertThat(generatedBd.isSynthetic()).isFalse();
assertThat(generatedBd.getDependsOn()).isNull();
assertThat(generatedBd.isLazyInit()).isFalse();
assertThat(generatedBd.getRole()).isEqualTo(BeanDefinition.ROLE_APPLICATION);
assertThat(generatedBd.getScope()).isEqualTo(ConfigurableBeanFactory.SCOPE_SINGLETON);
assertThat(generatedBd.isAutowireCandidate()).isTrue();
}));
}
@Test
void generateWithBeanDefinitionHavingMultipleAttributes() {
compile(simpleConfigurationRegistration(bd -> {
bd.setSynthetic(true);
bd.setPrimary(true);
}), hasBeanDefinition(generatedBd -> {
assertThat(generatedBd.isSynthetic()).isTrue();
assertThat(generatedBd.isPrimary()).isTrue();
}));
}
@Test
void generateWithBeanDefinitionHavingProperty() {
compile(simpleConfigurationRegistration(bd -> bd.getPropertyValues().addPropertyValue("test", "Hello")),
hasBeanDefinition(generatedBd -> {
assertThat(generatedBd.getPropertyValues().contains("test")).isTrue();
assertThat(generatedBd.getPropertyValues().get("test")).isEqualTo("Hello");
}));
}
@Test
void generateWithBeanDefinitionHavingSeveralProperties() {
compile(simpleConfigurationRegistration(bd -> {
bd.getPropertyValues().addPropertyValue("test", "Hello");
bd.getPropertyValues().addPropertyValue("counter", 42);
}), hasBeanDefinition(generatedBd -> {
assertThat(generatedBd.getPropertyValues().contains("test")).isTrue();
assertThat(generatedBd.getPropertyValues().get("test")).isEqualTo("Hello");
assertThat(generatedBd.getPropertyValues().contains("counter")).isTrue();
assertThat(generatedBd.getPropertyValues().get("counter")).isEqualTo(42);
}));
}
@Test
void generateWithBeanDefinitionHavingPropertyReference() {
compile(simpleConfigurationRegistration(bd -> bd.getPropertyValues().addPropertyValue(
"myService", new RuntimeBeanReference("test"))), hasBeanDefinition(generatedBd -> {
assertThat(generatedBd.getPropertyValues().contains("myService")).isTrue();
assertThat(generatedBd.getPropertyValues().get("myService"))
.isInstanceOfSatisfying(RuntimeBeanReference.class, ref ->
assertThat(ref.getBeanName()).isEqualTo("test"));
}));
}
@Test
void generateWithBeanDefinitionHavingPropertyAsBeanDefinition() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
BeanDefinition innerBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(SimpleConfiguration.class, "stringBean")
.getBeanDefinition();
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(ConfigurableBean.class)
.addPropertyValue("name", innerBeanDefinition).getBeanDefinition();
compile(getDefaultContribution(beanFactory, beanDefinition), hasBeanDefinition(generatedBd -> {
assertThat(generatedBd.getPropertyValues().contains("name")).isTrue();
assertThat(generatedBd.getPropertyValues().get("name")).isInstanceOfSatisfying(RootBeanDefinition.class, innerGeneratedBd ->
assertThat(innerGeneratedBd.getResolvedFactoryMethod()).isEqualTo(method(SimpleConfiguration.class, "stringBean")));
}));
}
@Test
void generateWithBeanDefinitionHavingPropertyAsListOfBeanDefinitions() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
BeanDefinition innerBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(SimpleConfiguration.class, "stringBean")
.getBeanDefinition();
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(ConfigurableBean.class)
.addPropertyValue("names", List.of(innerBeanDefinition, innerBeanDefinition)).getBeanDefinition();
compile(getDefaultContribution(beanFactory, beanDefinition), hasBeanDefinition(generatedBd -> {
assertThat(generatedBd.getPropertyValues().contains("names")).isTrue();
assertThat(generatedBd.getPropertyValues().get("names")).asList().hasSize(2);
}));
}
@Test
void generateWithBeanDefinitionHavingPropertyAsBeanDefinitionUseDedicatedVariableNames() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
BeanDefinition innerBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(SimpleConfiguration.class, "stringBean")
.setRole(2).getBeanDefinition();
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(ConfigurableBean.class)
.addPropertyValue("name", innerBeanDefinition).getBeanDefinition();
getDefaultContribution(beanFactory, beanDefinition).applyTo(this.initialization);
CodeSnippet registration = CodeSnippet.of(this.initialization.toCodeBlock());
assertThat(registration.getSnippet()).isEqualTo("""
BeanDefinitionRegistrar.of("test", ConfigurableBean.class)
.instanceSupplier(ConfigurableBean::new).customize((bd) -> bd.getPropertyValues().addPropertyValue("name", BeanDefinitionRegistrar.inner(SimpleConfiguration.class).withFactoryMethod(SimpleConfiguration.class, "stringBean")
.instanceSupplier(() -> beanFactory.getBean(SimpleConfiguration.class).stringBean()).customize((bd_) -> bd_.setRole(2)).toBeanDefinition())).register(beanFactory);
""");
assertThat(registration.hasImport(SimpleConfiguration.class)).isTrue();
}
@Test
void generateUsingSingleConstructorArgument() {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(String.class).getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, "hello");
compile(getContributionFor(beanDefinition, method(SampleFactory.class, "create", String.class)), beanFactory ->
assertThat(beanFactory.getBean(String.class)).isEqualTo("hello"));
}
@Test
void generateUsingSeveralConstructorArguments() {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(String.class)
.addConstructorArgValue(42).addConstructorArgValue("testBean")
.getBeanDefinition();
compile(getContributionFor(beanDefinition, method(SampleFactory.class, "create", Number.class, String.class)), beanFactory ->
assertThat(beanFactory.getBean(String.class)).isEqualTo("42testBean"));
}
@Test
void generateWithBeanDefinitionHavingAttributesDoesNotWriteThemByDefault() {
compile(simpleConfigurationRegistration(bd -> {
bd.setAttribute("test", "value");
bd.setAttribute("counter", 42);
}), hasBeanDefinition(generatedBd -> {
assertThat(generatedBd.getAttribute("test")).isNull();
assertThat(generatedBd.getAttribute("counter")).isNull();
}));
}
@Test
void generateWithBeanDefinitionHavingAttributesUseCustomFilter() {
RootBeanDefinition bd = new RootBeanDefinition(SimpleConfiguration.class);
bd.setAttribute("test", "value");
bd.setAttribute("counter", 42);
DefaultBeanInstantiationGenerator beanInstantiationGenerator = new DefaultBeanInstantiationGenerator(
singleConstructor(SimpleConfiguration.class), Collections.emptyList());
compile(new BeanRegistrationBeanFactoryContribution("test", bd, beanInstantiationGenerator) {
@Override
protected Predicate<String> getAttributeFilter() {
return candidate -> candidate.equals("counter");
}
}, hasBeanDefinition(generatedBd -> {
assertThat(generatedBd.getAttribute("test")).isNull();
assertThat(generatedBd.getAttribute("counter")).isNotNull().isEqualTo(42);
}));
}
@Test
void registerRuntimeHintsWithInitMethodNames() {
RootBeanDefinition bd = new RootBeanDefinition(InitDestroyBean.class);
bd.setInitMethodNames("customInitMethod", "initMethod");
RuntimeHints runtimeHints = new RuntimeHints();
getDefaultContribution(new DefaultListableBeanFactory(), bd).registerRuntimeHints(runtimeHints);
assertThat(runtimeHints.reflection().getTypeHint(InitDestroyBean.class)).satisfies(hint ->
assertThat(hint.methods()).anySatisfy(invokeMethodHint("customInitMethod"))
.anySatisfy(invokeMethodHint("initMethod")).hasSize(2));
}
@Test
void registerRuntimeHintsWithDestroyMethodNames() {
RootBeanDefinition bd = new RootBeanDefinition(InitDestroyBean.class);
bd.setDestroyMethodNames("customDestroyMethod", "destroyMethod");
RuntimeHints runtimeHints = new RuntimeHints();
getDefaultContribution(new DefaultListableBeanFactory(), bd).registerRuntimeHints(runtimeHints);
assertThat(runtimeHints.reflection().getTypeHint(InitDestroyBean.class)).satisfies(hint ->
assertThat(hint.methods()).anySatisfy(invokeMethodHint("customDestroyMethod"))
.anySatisfy(invokeMethodHint("destroyMethod")).hasSize(2));
}
@Test
void registerRuntimeHintsWithNoPropertyValuesDoesNotAccessRuntimeHints() {
RootBeanDefinition bd = new RootBeanDefinition(String.class);
RuntimeHints runtimeHints = mock(RuntimeHints.class);
getDefaultContribution(new DefaultListableBeanFactory(), bd).registerRuntimeHints(runtimeHints);
verifyNoInteractions(runtimeHints);
}
@Test
void registerRuntimeHintsWithInvalidProperty() {
BeanDefinition bd = BeanDefinitionBuilder.rootBeanDefinition(ConfigurableBean.class)
.addPropertyValue("notAProperty", "invalid").addPropertyValue("name", "hello")
.getBeanDefinition();
RuntimeHints runtimeHints = new RuntimeHints();
getDefaultContribution(new DefaultListableBeanFactory(), bd).registerRuntimeHints(runtimeHints);
assertThat(runtimeHints.reflection().getTypeHint(ConfigurableBean.class)).satisfies(hint -> {
assertThat(hint.fields()).isEmpty();
assertThat(hint.constructors()).isEmpty();
assertThat(hint.methods()).singleElement().satisfies(methodHint -> {
assertThat(methodHint.getName()).isEqualTo("setName");
assertThat(methodHint.getParameterTypes()).containsExactly(TypeReference.of(String.class));
assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE);
});
assertThat(hint.getMemberCategories()).isEmpty();
});
}
@Test
void registerRuntimeHintsForPropertiesUseDeclaringClass() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("environment", mock(Environment.class));
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(IntegerFactoryBean.class)
.addConstructorArgReference("environment")
.addPropertyValue("name", "Hello").getBeanDefinition();
getDefaultContribution(beanFactory, beanDefinition).applyTo(this.initialization);
ReflectionHints reflectionHints = this.initialization.generatedTypeContext().runtimeHints().reflection();
assertThat(reflectionHints.typeHints()).anySatisfy(typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(BaseFactoryBean.class));
assertThat(typeHint.constructors()).isEmpty();
assertThat(typeHint.methods()).singleElement()
.satisfies(invokeMethodHint("setName", String.class));
assertThat(typeHint.fields()).isEmpty();
}).anySatisfy(typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IntegerFactoryBean.class));
assertThat(typeHint.constructors()).singleElement()
.satisfies(introspectConstructorHint(Environment.class));
assertThat(typeHint.methods()).isEmpty();
assertThat(typeHint.fields()).isEmpty();
}).hasSize(2);
}
@Test
void registerRuntimeHintsForProperties() {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(NameAndCountersComponent.class)
.addPropertyValue("name", "Hello").addPropertyValue("counter", 42).getBeanDefinition();
getDefaultContribution(new DefaultListableBeanFactory(), beanDefinition).applyTo(this.initialization);
ReflectionHints reflectionHints = this.initialization.generatedTypeContext().runtimeHints().reflection();
assertThat(reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(NameAndCountersComponent.class));
assertThat(typeHint.constructors()).isEmpty();
assertThat(typeHint.methods()).anySatisfy(invokeMethodHint("setName", String.class))
.anySatisfy(invokeMethodHint("setCounter", Integer.class)).hasSize(2);
assertThat(typeHint.fields()).isEmpty();
});
}
@Test
void registerReflectionEntriesForInnerBeanDefinition() {
AbstractBeanDefinition innerBd = BeanDefinitionBuilder.rootBeanDefinition(IntegerFactoryBean.class)
.addPropertyValue("name", "test").getBeanDefinition();
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(NameAndCountersComponent.class)
.addPropertyValue("counter", innerBd).getBeanDefinition();
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("environment", Environment.class);
getDefaultContribution(beanFactory, beanDefinition).applyTo(this.initialization);
ReflectionHints reflectionHints = this.initialization.generatedTypeContext().runtimeHints().reflection();
assertThat(reflectionHints.typeHints()).anySatisfy(typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(NameAndCountersComponent.class));
assertThat(typeHint.constructors()).isEmpty();
assertThat(typeHint.methods()).singleElement().satisfies(invokeMethodHint("setCounter", Integer.class));
assertThat(typeHint.fields()).isEmpty();
}).anySatisfy(typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(BaseFactoryBean.class));
assertThat(typeHint.methods()).singleElement().satisfies(invokeMethodHint("setName", String.class));
}).anySatisfy(typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IntegerFactoryBean.class));
assertThat(typeHint.constructors()).singleElement().satisfies(introspectConstructorHint(Environment.class));
}).hasSize(3);
}
@Test
void registerReflectionEntriesForListOfInnerBeanDefinition() {
AbstractBeanDefinition innerBd1 = BeanDefinitionBuilder.rootBeanDefinition(IntegerFactoryBean.class)
.addPropertyValue("name", "test").getBeanDefinition();
AbstractBeanDefinition innerBd2 = BeanDefinitionBuilder.rootBeanDefinition(AnotherIntegerFactoryBean.class)
.addPropertyValue("name", "test").getBeanDefinition();
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(NameAndCountersComponent.class)
.addPropertyValue("counters", List.of(innerBd1, innerBd2)).getBeanDefinition();
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("environment", Environment.class);
getDefaultContribution(beanFactory, beanDefinition).applyTo(this.initialization);
ReflectionHints reflectionHints = this.initialization.generatedTypeContext().runtimeHints().reflection();
assertThat(reflectionHints.typeHints()).anySatisfy(typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(NameAndCountersComponent.class));
assertThat(typeHint.constructors()).isEmpty();
assertThat(typeHint.methods()).singleElement().satisfies(invokeMethodHint("setCounters", List.class));
assertThat(typeHint.fields()).isEmpty();
}).anySatisfy(typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(BaseFactoryBean.class));
assertThat(typeHint.methods()).singleElement().satisfies(invokeMethodHint("setName", String.class));
}).anySatisfy(typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IntegerFactoryBean.class));
assertThat(typeHint.constructors()).singleElement().satisfies(introspectConstructorHint(Environment.class));
}).anySatisfy(typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(AnotherIntegerFactoryBean.class));
assertThat(typeHint.constructors()).singleElement().satisfies(introspectConstructorHint(Environment.class));
}).hasSize(4);
}
private Consumer<ExecutableHint> invokeMethodHint(String name, Class<?>... parameterTypes) {
return executableHint(ExecutableMode.INVOKE, name, parameterTypes);
}
private Consumer<ExecutableHint> introspectConstructorHint(Class<?>... parameterTypes) {
return executableHint(ExecutableMode.INTROSPECT, "<init>", parameterTypes);
}
private Consumer<ExecutableHint> executableHint(ExecutableMode mode, String name, Class<?>... parameterTypes) {
return executableHint -> {
assertThat(executableHint.getName()).isEqualTo(name);
assertThat(executableHint.getParameterTypes()).containsExactly(Arrays.stream(parameterTypes)
.map(TypeReference::of).toArray(TypeReference[]::new));
assertThat(executableHint.getModes()).containsExactly(mode);
};
}
private Consumer<DefaultListableBeanFactory> hasBeanDefinition(Consumer<RootBeanDefinition> bd) {
return beanFactory -> {
assertThat(beanFactory.getBeanDefinitionNames()).contains("test");
RootBeanDefinition beanDefinition = (RootBeanDefinition) beanFactory.getMergedBeanDefinition("test");
bd.accept(beanDefinition);
};
}
private BeanFactoryContribution simpleConfigurationRegistration(Consumer<RootBeanDefinition> bd) {
RootBeanDefinition beanDefinition = (RootBeanDefinition) BeanDefinitionBuilder
.rootBeanDefinition(SimpleConfiguration.class).getBeanDefinition();
bd.accept(beanDefinition);
return getDefaultContribution(new DefaultListableBeanFactory(), beanDefinition);
}
private BeanRegistrationBeanFactoryContribution getDefaultContribution(DefaultListableBeanFactory beanFactory, BeanDefinition beanDefinition) {
BeanRegistrationBeanFactoryContribution contribution = new DefaultBeanRegistrationContributionProvider(beanFactory)
.getContributionFor("test", (RootBeanDefinition) beanDefinition);
assertThat(contribution).isNotNull();
return contribution;
}
private BeanRegistrationBeanFactoryContribution getContributionFor(BeanDefinition beanDefinition, Executable instanceCreator) {
return new BeanRegistrationBeanFactoryContribution("test", (RootBeanDefinition) beanDefinition,
new DefaultBeanInstantiationGenerator(instanceCreator, Collections.emptyList()));
}
private CodeSnippet beanRegistration(BeanDefinition beanDefinition, Executable instanceCreator, Consumer<Builder> instanceSupplier) {
BeanRegistrationBeanFactoryContribution generator = new BeanRegistrationBeanFactoryContribution(
"test", (RootBeanDefinition) beanDefinition,
new DefaultBeanInstantiationGenerator(instanceCreator, Collections.emptyList()));
return CodeSnippet.of(generator.generateBeanRegistration(new RuntimeHints(),
toMultiStatements(instanceSupplier)));
}
private Constructor<?> singleConstructor(Class<?> type) {
return type.getDeclaredConstructors()[0];
}
private Method method(Class<?> type, String name, Class<?>... parameterTypes) {
Method method = ReflectionUtils.findMethod(type, name, parameterTypes);
assertThat(method).isNotNull();
return method;
}
private MultiStatement toMultiStatements(Consumer<Builder> instanceSupplier) {
Builder code = CodeBlock.builder();
instanceSupplier.accept(code);
MultiStatement statements = new MultiStatement();
statements.add(code.build());
return statements;
}
private String codeOf(GeneratedType type) {
try {
StringWriter out = new StringWriter();
type.toJavaFile().writeTo(out);
return out.toString();
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
private String removeIndent(String content, int indent) {
return content.lines().map(line -> {
for (int i = 0; i < indent; i++) {
if (line.startsWith("\t")) {
line = line.substring(1);
}
}
return line;
}).collect(Collectors.joining("\n"));
}
private void compile(BeanFactoryContribution contribution, Consumer<DefaultListableBeanFactory> beanFactory) {
contribution.applyTo(this.initialization);
GeneratedType generatedType = this.generatedTypeContext.getMainGeneratedType();
generatedType.customizeType(type -> {
type.addModifiers(Modifier.PUBLIC);
type.addSuperinterface(BeanFactoryInitializer.class);
});
generatedType.addMethod(MethodSpec.methodBuilder("initializeBeanFactory")
.addModifiers(Modifier.PUBLIC).addAnnotation(Override.class)
.addParameter(DefaultListableBeanFactory.class, "beanFactory")
.addCode(this.initialization.toCodeBlock()));
SourceFiles sourceFiles = SourceFiles.none();
for (JavaFile javaFile : this.generatedTypeContext.toJavaFiles()) {
sourceFiles = sourceFiles.and(SourceFile.of((javaFile::writeTo)));
}
TestCompiler.forSystem().withSources(sourceFiles).compile(compiled -> {
BeanFactoryInitializer initializer = compiled.getInstance(BeanFactoryInitializer.class,
generatedType.getClassName().canonicalName());
DefaultListableBeanFactory freshBeanFactory = new DefaultListableBeanFactory();
initializer.initializeBeanFactory(freshBeanFactory);
beanFactory.accept(freshBeanFactory);
});
}
static abstract class BaseFactoryBean {
public void setName(String name) {
}
}
@SuppressWarnings("unused")
static class IntegerFactoryBean extends BaseFactoryBean implements FactoryBean<Integer> {
public IntegerFactoryBean(Environment environment) {
}
@Override
public Class<?> getObjectType() {
return Integer.class;
}
@Override
public Integer getObject() {
return 42;
}
}
@SuppressWarnings("unused")
static class AnotherIntegerFactoryBean extends IntegerFactoryBean {
public AnotherIntegerFactoryBean(Environment environment) {
super(environment);
}
}
static class NameAndCountersComponent {
@SuppressWarnings("unused")
private String name;
@SuppressWarnings("unused")
private List<Integer> counters;
public void setName(String name) {
this.name = name;
}
public void setCounter(Integer counter) {
setCounters(List.of(counter));
}
public void setCounters(List<Integer> counters) {
this.counters = counters;
}
}
}

View File

@@ -1,255 +0,0 @@
/*
* 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 DefaultBeanInstantiationGenerator}.
*
* @author Stephane Nicoll
*/
class DefaultBeanInstantiationGeneratorTests {
@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 generateUsingNoArgConstructorAndContributionsDoesNotUseMethodReference() {
CodeContribution contribution = generate(SimpleConfiguration.class.getDeclaredConstructors()[0],
contrib -> contrib.statements().add(CodeBlock.of("// hello\n")),
contrib -> {});
assertThat(code(contribution)).isEqualTo("""
(instanceContext) -> {
SimpleConfiguration bean = new SimpleConfiguration();
// hello
return bean;
}""");
}
@Test
void generateUsingContributionsRegisterHints() {
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 generateUsingMethodAndContributions() {
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().toLambdaBody());
}
@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,
BeanInstantiationContribution... beanInstantiationContributions) {
DefaultBeanInstantiationGenerator generator = new DefaultBeanInstantiationGenerator(executable,
Arrays.asList(beanInstantiationContributions));
return generator.generateBeanInstantiation(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

@@ -1,67 +0,0 @@
/*
* 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.junit.jupiter.api.Test;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.factory.generator.SimpleConfiguration;
import org.springframework.core.Ordered;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests for {@link DefaultBeanRegistrationContributionProvider}.
*
* @author Stephane Nicoll
*/
class DefaultBeanRegistrationContributionProviderTests {
@Test
void aotContributingBeanPostProcessorsAreIncluded() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AotContributingBeanPostProcessor first = mockNoOpPostProcessor(-1);
AotContributingBeanPostProcessor second = mockNoOpPostProcessor(5);
beanFactory.registerBeanDefinition("second", BeanDefinitionBuilder.rootBeanDefinition(
AotContributingBeanPostProcessor.class, () -> second).getBeanDefinition());
beanFactory.registerBeanDefinition("first", BeanDefinitionBuilder.rootBeanDefinition(
AotContributingBeanPostProcessor.class, () -> first).getBeanDefinition());
RootBeanDefinition beanDefinition = new RootBeanDefinition(SimpleConfiguration.class);
new DefaultBeanRegistrationContributionProvider(beanFactory).getContributionFor(
"test", beanDefinition);
verify((Ordered) second).getOrder();
verify((Ordered) first).getOrder();
verify(first).contribute(beanDefinition, SimpleConfiguration.class, "test");
verify(second).contribute(beanDefinition, SimpleConfiguration.class, "test");
verifyNoMoreInteractions(first, second);
}
private AotContributingBeanPostProcessor mockNoOpPostProcessor(int order) {
AotContributingBeanPostProcessor postProcessor = mock(AotContributingBeanPostProcessor.class);
given(postProcessor.contribute(any(), any(), any())).willReturn(null);
given(postProcessor.getOrder()).willReturn(order);
return postProcessor;
}
}

View File

@@ -1,336 +0,0 @@
/*
* 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.aot.generator.ProtectedAccess;
import org.springframework.aot.generator.ProtectedAccess.Options;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.generator.InjectionGeneratorTests.SimpleConstructorBean.InnerClass;
import org.springframework.beans.testfixture.beans.factory.generator.factory.SampleFactory;
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 {
private final ProtectedAccess protectedAccess = new ProtectedAccess();
@Test
void generateInstantiationForConstructorWithNoArgUseShortcut() {
Constructor<?> constructor = SimpleBean.class.getDeclaredConstructors()[0];
assertThat(generateInstantiation(constructor).lines())
.containsExactly("new InjectionGeneratorTests.SimpleBean()");
}
@Test
void generateInstantiationForConstructorWithNonGenericParameter() {
Constructor<?> constructor = SimpleConstructorBean.class.getDeclaredConstructors()[0];
assertThat(generateInstantiation(constructor).lines()).containsExactly(
"instanceContext.create(beanFactory, (attributes) -> new InjectionGeneratorTests.SimpleConstructorBean(attributes.get(0), attributes.get(1)))");
}
@Test
void generateInstantiationForConstructorWithGenericParameter() {
Constructor<?> constructor = GenericConstructorBean.class.getDeclaredConstructors()[0];
assertThat(generateInstantiation(constructor).lines()).containsExactly(
"instanceContext.create(beanFactory, (attributes) -> new InjectionGeneratorTests.GenericConstructorBean(attributes.get(0)))");
}
@Test
void generateInstantiationForAmbiguousConstructor() throws Exception {
Constructor<?> constructor = AmbiguousConstructorBean.class.getDeclaredConstructor(String.class, Number.class);
assertThat(generateInstantiation(constructor).lines()).containsExactly(
"instanceContext.create(beanFactory, (attributes) -> new InjectionGeneratorTests.AmbiguousConstructorBean(attributes.get(0, String.class), attributes.get(1, Number.class)))");
}
@Test
void generateInstantiationForConstructorInInnerClass() {
Constructor<?> constructor = InnerClass.class.getDeclaredConstructors()[0];
assertThat(generateInstantiation(constructor).lines()).containsExactly(
"beanFactory.getBean(InjectionGeneratorTests.SimpleConstructorBean.class).new InnerClass()");
}
@Test
void generateInstantiationForMethodWithNoArgUseShortcut() {
assertThat(generateInstantiation(method(SimpleBean.class, "name")).lines()).containsExactly(
"beanFactory.getBean(InjectionGeneratorTests.SimpleBean.class).name()");
}
@Test
void generateInstantiationForStaticMethodWithNoArgUseShortcut() {
assertThat(generateInstantiation(method(SimpleBean.class, "number")).lines()).containsExactly(
"InjectionGeneratorTests.SimpleBean.number()");
}
@Test
void generateInstantiationForMethodWithNonGenericParameter() {
assertThat(generateInstantiation(method(SampleBean.class, "source", Integer.class)).lines()).containsExactly(
"instanceContext.create(beanFactory, (attributes) -> beanFactory.getBean(InjectionGeneratorTests.SampleBean.class).source(attributes.get(0)))");
}
@Test
void generateInstantiationForStaticMethodWithNonGenericParameter() {
assertThat(generateInstantiation(method(SampleBean.class, "staticSource", Integer.class)).lines()).containsExactly(
"instanceContext.create(beanFactory, (attributes) -> InjectionGeneratorTests.SampleBean.staticSource(attributes.get(0)))");
}
@Test
void generateInstantiationForMethodWithGenericParameters() {
assertThat(generateInstantiation(method(SampleBean.class, "sourceWithProvider", ObjectProvider.class)).lines()).containsExactly(
"instanceContext.create(beanFactory, (attributes) -> beanFactory.getBean(InjectionGeneratorTests.SampleBean.class).sourceWithProvider(attributes.get(0)))");
}
@Test
void generateInstantiationForAmbiguousMethod() {
assertThat(generateInstantiation(method(SampleFactory.class, "create", String.class)).lines()).containsExactly(
"instanceContext.create(beanFactory, (attributes) -> SampleFactory.create(attributes.get(0, String.class)))");
}
@Test
void generateInjectionForUnsupportedMember() {
assertThatIllegalArgumentException().isThrownBy(() -> generateInjection(mock(Member.class), false));
}
@Test
void generateInjectionForNonRequiredMethodWithNonGenericParameters() {
Method method = method(SampleBean.class, "sourceAndCounter", String.class, Integer.class);
assertThat(generateInjection(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 generateInjectionForRequiredMethodWithGenericParameter() {
Method method = method(SampleBean.class, "nameAndCounter", String.class, ObjectProvider.class);
assertThat(generateInjection(method, true)).isEqualTo("""
instanceContext.method("nameAndCounter", String.class, ObjectProvider.class)
.invoke(beanFactory, (attributes) -> bean.nameAndCounter(attributes.get(0), attributes.get(1)))""");
}
@Test
void generateInjectionForNonRequiredMethodWithGenericParameter() {
Method method = method(SampleBean.class, "nameAndCounter", String.class, ObjectProvider.class);
assertThat(generateInjection(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 generateInjectionForRequiredField() {
Field field = field(SampleBean.class, "counter");
assertThat(generateInjection(field, true)).isEqualTo("""
instanceContext.field("counter")
.invoke(beanFactory, (attributes) -> bean.counter = attributes.get(0))""");
}
@Test
void generateInjectionForNonRequiredField() {
Field field = field(SampleBean.class, "counter");
assertThat(generateInjection(field, false)).isEqualTo("""
instanceContext.field("counter")
.resolve(beanFactory, false).ifResolved((attributes) -> bean.counter = attributes.get(0))""");
}
@Test
void generateInjectionForRequiredPrivateField() {
Field field = field(SampleBean.class, "source");
assertThat(generateInjection(field, true)).isEqualTo("""
instanceContext.field("source")
.invoke(beanFactory, (attributes) -> {
Field sourceField = ReflectionUtils.findField(InjectionGeneratorTests.SampleBean.class, "source");
ReflectionUtils.makeAccessible(sourceField);
ReflectionUtils.setField(sourceField, bean, attributes.get(0));
})""");
}
@Test
void getProtectedAccessInjectionOptionsForUnsupportedMember() {
assertThatIllegalArgumentException().isThrownBy(() ->
getProtectedAccessInjectionOptions(mock(Member.class)));
}
@Test
void getProtectedAccessInjectionOptionsForPackagePublicField() {
analyzeProtectedAccess(field(SampleBean.class, "enabled"));
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
}
@Test
void getProtectedAccessInjectionOptionsForPackageProtectedField() {
analyzeProtectedAccess(field(SampleBean.class, "counter"));
assertPrivilegedAccess(SampleBean.class);
}
@Test
void getProtectedAccessInjectionOptionsForPrivateField() {
analyzeProtectedAccess(field(SampleBean.class, "source"));
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
}
@Test
void getProtectedAccessInjectionOptionsForPublicMethod() {
analyzeProtectedAccess(method(SampleBean.class, "setEnabled", Boolean.class));
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
}
@Test
void getProtectedAccessInjectionOptionsForPackageProtectedMethod() {
analyzeProtectedAccess(method(SampleBean.class, "sourceAndCounter", String.class, Integer.class));
assertPrivilegedAccess(SampleBean.class);
}
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 generateInstantiation(Executable creator) {
return CodeSnippet.process(code -> code.add(new InjectionGenerator().generateInstantiation(creator)));
}
private String generateInjection(Member member, boolean required) {
return CodeSnippet.process(code -> code.add(new InjectionGenerator().generateInjection(member, required)));
}
private void analyzeProtectedAccess(Member member) {
this.protectedAccess.analyze(member, getProtectedAccessInjectionOptions(member));
}
private Options getProtectedAccessInjectionOptions(Member member) {
return new InjectionGenerator().getProtectedAccessInjectionOptions(member);
}
private void assertPrivilegedAccess(Class<?> target) {
assertThat(this.protectedAccess.isAccessible("com.example")).isFalse();
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")).isEqualTo(target.getPackageName());
assertThat(this.protectedAccess.isAccessible(target.getPackageName())).isTrue();
}
@SuppressWarnings("unused")
public static class SampleBean {
public Boolean enabled;
private String source;
Integer counter;
public void setEnabled(Boolean enabled) {
}
void sourceAndCounter(String source, Integer counter) {
}
void nameAndCounter(String name, ObjectProvider<Integer> counter) {
}
String source(Integer counter) {
return "source" + counter;
}
String sourceWithProvider(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

@@ -1,463 +0,0 @@
/*
* 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").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").resolve(beanFactory)).register(beanFactory);
assertThatThrownBy(() -> beanFactory.getBean(InjectionSample.class)
).isInstanceOf(BeanCreationException.class).hasMessageContaining(
"No field 'doesNotExist' found on " + 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 {
@SuppressWarnings("unused")
private final String name;
@SuppressWarnings("unused")
private final Integer counter;
public MultiArgConstructorSample(String name, Integer counter) {
this.name = name;
this.counter = counter;
}
}
static class InjectionSample {
private Environment environment;
@SuppressWarnings("unused")
private String name;
@SuppressWarnings("unused")
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> {
@SuppressWarnings("unused")
private final N number;
public NumberHolder(N number) {
this.number = number;
}
}
static class NumberHolderSample {
@Autowired
@SuppressWarnings("unused")
private NumberHolder<Integer> numberHolder;
}
}

View File

@@ -1,476 +0,0 @@
/*
* 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

@@ -1,91 +0,0 @@
/*
* 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

@@ -1,79 +0,0 @@
/*
* 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 {
@SuppressWarnings("unused")
private String string;
}
}

View File

@@ -1,124 +0,0 @@
/*
* 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) {
}
}
}