Complete refactor of AOT concepts
Remove the AOT code that now has an alternative API. Closes gh-28414
This commit is contained in:
parent
702207d9ee
commit
16e7f1f212
|
@ -1,115 +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.aop.scope;
|
||||
|
||||
import java.lang.reflect.Executable;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aot.generator.CodeContribution;
|
||||
import org.springframework.aot.generator.DefaultCodeContribution;
|
||||
import org.springframework.aot.generator.ProtectedAccess.Options;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.generator.BeanFactoryContribution;
|
||||
import org.springframework.beans.factory.generator.BeanInstantiationGenerator;
|
||||
import org.springframework.beans.factory.generator.BeanRegistrationBeanFactoryContribution;
|
||||
import org.springframework.beans.factory.generator.BeanRegistrationContributionProvider;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.javapoet.support.MultiStatement;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link BeanRegistrationContributionProvider} for {@link ScopedProxyFactoryBean}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ScopedProxyBeanRegistrationContributionProvider implements BeanRegistrationContributionProvider {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ScopedProxyBeanRegistrationContributionProvider.class);
|
||||
|
||||
|
||||
private final ConfigurableBeanFactory beanFactory;
|
||||
|
||||
ScopedProxyBeanRegistrationContributionProvider(ConfigurableBeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BeanFactoryContribution getContributionFor(String beanName, RootBeanDefinition beanDefinition) {
|
||||
Class<?> beanType = beanDefinition.getResolvableType().toClass();
|
||||
return (beanType.equals(ScopedProxyFactoryBean.class))
|
||||
? createScopedProxyBeanFactoryContribution(beanName, beanDefinition) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BeanFactoryContribution createScopedProxyBeanFactoryContribution(String beanName, RootBeanDefinition beanDefinition) {
|
||||
String targetBeanName = getTargetBeanName(beanDefinition);
|
||||
BeanDefinition targetBeanDefinition = getTargetBeanDefinition(targetBeanName);
|
||||
if (targetBeanDefinition == null) {
|
||||
logger.warn("Could not handle " + ScopedProxyFactoryBean.class.getSimpleName() +
|
||||
": no target bean definition found with name " + targetBeanName);
|
||||
return null;
|
||||
}
|
||||
RootBeanDefinition processedBeanDefinition = new RootBeanDefinition(beanDefinition);
|
||||
processedBeanDefinition.setTargetType(targetBeanDefinition.getResolvableType());
|
||||
processedBeanDefinition.getPropertyValues().removePropertyValue("targetBeanName");
|
||||
return new BeanRegistrationBeanFactoryContribution(beanName, processedBeanDefinition,
|
||||
getBeanInstantiationGenerator(targetBeanName));
|
||||
}
|
||||
|
||||
private BeanInstantiationGenerator getBeanInstantiationGenerator(String targetBeanName) {
|
||||
return new BeanInstantiationGenerator() {
|
||||
|
||||
@Override
|
||||
public Executable getInstanceCreator() {
|
||||
return ScopedProxyFactoryBean.class.getDeclaredConstructors()[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeContribution generateBeanInstantiation(RuntimeHints runtimeHints) {
|
||||
CodeContribution codeContribution = new DefaultCodeContribution(runtimeHints);
|
||||
codeContribution.protectedAccess().analyze(getInstanceCreator(), Options.defaults().build());
|
||||
MultiStatement statements = new MultiStatement();
|
||||
statements.addStatement("$T factory = new $T()", ScopedProxyFactoryBean.class, ScopedProxyFactoryBean.class);
|
||||
statements.addStatement("factory.setTargetBeanName($S)", targetBeanName);
|
||||
statements.addStatement("factory.setBeanFactory(beanFactory)");
|
||||
statements.addStatement("return factory.getObject()");
|
||||
codeContribution.statements().add(statements.toLambda("() ->"));
|
||||
return codeContribution;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getTargetBeanName(BeanDefinition beanDefinition) {
|
||||
Object value = beanDefinition.getPropertyValues().get("targetBeanName");
|
||||
return (value instanceof String targetBeanName) ? targetBeanName : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BeanDefinition getTargetBeanDefinition(@Nullable String targetBeanName) {
|
||||
if (targetBeanName != null && this.beanFactory.containsBean(targetBeanName)) {
|
||||
return this.beanFactory.getMergedBeanDefinition(targetBeanName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,113 +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.aop.scope;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aop.testfixture.scope.SimpleTarget;
|
||||
import org.springframework.aot.generator.DefaultGeneratedTypeContext;
|
||||
import org.springframework.aot.generator.GeneratedType;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.PropertiesFactoryBean;
|
||||
import org.springframework.beans.factory.generator.BeanFactoryContribution;
|
||||
import org.springframework.beans.factory.generator.BeanFactoryInitialization;
|
||||
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.factory.NumberHolder;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.javapoet.support.CodeSnippet;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ScopedProxyBeanRegistrationContributionProvider}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ScopedProxyBeanRegistrationContributionProviderTests {
|
||||
|
||||
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
|
||||
@Test
|
||||
void getWithNonScopedProxy() {
|
||||
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(PropertiesFactoryBean.class)
|
||||
.getBeanDefinition();
|
||||
assertThat(getBeanFactoryContribution("test", beanDefinition)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithScopedProxyWithoutTargetBeanName() {
|
||||
BeanDefinition scopeBean = BeanDefinitionBuilder.rootBeanDefinition(ScopedProxyFactoryBean.class)
|
||||
.getBeanDefinition();
|
||||
assertThat(getBeanFactoryContribution("test", scopeBean)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithScopedProxyWithInvalidTargetBeanName() {
|
||||
BeanDefinition scopeBean = BeanDefinitionBuilder.rootBeanDefinition(ScopedProxyFactoryBean.class)
|
||||
.addPropertyValue("targetBeanName", "testDoesNotExist").getBeanDefinition();
|
||||
assertThat(getBeanFactoryContribution("test", scopeBean)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithScopedProxyWithTargetBeanName() {
|
||||
BeanDefinition targetBean = BeanDefinitionBuilder.rootBeanDefinition(SimpleTarget.class)
|
||||
.getBeanDefinition();
|
||||
beanFactory.registerBeanDefinition("simpleTarget", targetBean);
|
||||
BeanDefinition scopeBean = BeanDefinitionBuilder.rootBeanDefinition(ScopedProxyFactoryBean.class)
|
||||
.addPropertyValue("targetBeanName", "simpleTarget").getBeanDefinition();
|
||||
assertThat(getBeanFactoryContribution("test", scopeBean)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeBeanRegistrationForScopedProxy() {
|
||||
RootBeanDefinition targetBean = new RootBeanDefinition();
|
||||
targetBean.setTargetType(ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class));
|
||||
targetBean.setScope("custom");
|
||||
this.beanFactory.registerBeanDefinition("numberHolder", targetBean);
|
||||
BeanDefinition scopeBean = BeanDefinitionBuilder.rootBeanDefinition(ScopedProxyFactoryBean.class)
|
||||
.addPropertyValue("targetBeanName", "numberHolder").getBeanDefinition();
|
||||
assertThat(writeBeanRegistration("test", scopeBean).getSnippet()).isEqualTo("""
|
||||
BeanDefinitionRegistrar.of("test", ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class))
|
||||
.instanceSupplier(() -> {
|
||||
ScopedProxyFactoryBean factory = new ScopedProxyFactoryBean();
|
||||
factory.setTargetBeanName("numberHolder");
|
||||
factory.setBeanFactory(beanFactory);
|
||||
return factory.getObject();
|
||||
}).register(beanFactory);
|
||||
""");
|
||||
}
|
||||
|
||||
private CodeSnippet writeBeanRegistration(String beanName, BeanDefinition beanDefinition) {
|
||||
BeanFactoryContribution contribution = getBeanFactoryContribution(beanName, beanDefinition);
|
||||
assertThat(contribution).isNotNull();
|
||||
BeanFactoryInitialization initialization = new BeanFactoryInitialization(new DefaultGeneratedTypeContext("comp.example", packageName -> GeneratedType.of(ClassName.get(packageName, "Test"))));
|
||||
contribution.applyTo(initialization);
|
||||
return CodeSnippet.of(initialization.toCodeBlock());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
BeanFactoryContribution getBeanFactoryContribution(String beanName, BeanDefinition beanDefinition) {
|
||||
ScopedProxyBeanRegistrationContributionProvider provider = new ScopedProxyBeanRegistrationContributionProvider(this.beanFactory);
|
||||
return provider.getContributionFor(beanName, (RootBeanDefinition) beanDefinition);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -43,7 +43,6 @@ import org.apache.commons.logging.LogFactory;
|
|||
import org.springframework.aot.generate.AccessVisibility;
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.generate.MethodReference;
|
||||
import org.springframework.aot.generator.CodeContribution;
|
||||
import org.springframework.aot.hint.ExecutableHint;
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.FieldHint;
|
||||
|
@ -59,16 +58,12 @@ import org.springframework.beans.factory.BeanFactoryUtils;
|
|||
import org.springframework.beans.factory.InjectionPoint;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
import org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationCode;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
|
||||
import org.springframework.beans.factory.generator.AotContributingBeanPostProcessor;
|
||||
import org.springframework.beans.factory.generator.BeanInstantiationContribution;
|
||||
import org.springframework.beans.factory.generator.InjectionGenerator;
|
||||
import org.springframework.beans.factory.support.LookupOverride;
|
||||
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
|
@ -157,8 +152,7 @@ import org.springframework.util.StringUtils;
|
|||
* @see Value
|
||||
*/
|
||||
public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
|
||||
MergedBeanDefinitionPostProcessor, AotContributingBeanPostProcessor, BeanRegistrationAotProcessor,
|
||||
PriorityOrdered, BeanFactoryAware {
|
||||
MergedBeanDefinitionPostProcessor, BeanRegistrationAotProcessor, PriorityOrdered, BeanFactoryAware {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
|
@ -285,15 +279,6 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
|||
findInjectionMetadata(beanName, beanType, beanDefinition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanInstantiationContribution contribute(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
|
||||
InjectionMetadata metadata = findInjectionMetadata(beanName, beanType, beanDefinition);
|
||||
Collection<InjectedElement> injectedElements = metadata.getInjectedElements();
|
||||
return (!ObjectUtils.isEmpty(injectedElements)
|
||||
? new AutowiredAnnotationBeanInstantiationContribution(injectedElements)
|
||||
: null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||
Class<?> beanClass = registeredBean.getBeanClass();
|
||||
|
@ -866,52 +851,6 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
|||
}
|
||||
}
|
||||
|
||||
private static final class AutowiredAnnotationBeanInstantiationContribution implements BeanInstantiationContribution {
|
||||
|
||||
private final Collection<InjectedElement> injectedElements;
|
||||
|
||||
private final InjectionGenerator generator;
|
||||
|
||||
AutowiredAnnotationBeanInstantiationContribution(Collection<InjectedElement> injectedElements) {
|
||||
this.injectedElements = injectedElements;
|
||||
this.generator = new InjectionGenerator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(CodeContribution contribution) {
|
||||
this.injectedElements.forEach(element -> {
|
||||
boolean isRequired = isRequired(element);
|
||||
Member member = element.getMember();
|
||||
analyzeMember(contribution, member);
|
||||
contribution.statements().addStatement(this.generator.generateInjection(member, isRequired));
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isRequired(InjectedElement element) {
|
||||
if (element instanceof AutowiredMethodElement injectedMethod) {
|
||||
return injectedMethod.required;
|
||||
}
|
||||
else if (element instanceof AutowiredFieldElement injectedField) {
|
||||
return injectedField.required;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void analyzeMember(CodeContribution contribution, Member member) {
|
||||
if (member instanceof Method method) {
|
||||
contribution.runtimeHints().reflection().registerMethod(method,
|
||||
hint -> hint.setModes(ExecutableMode.INTROSPECT));
|
||||
contribution.protectedAccess().analyze(member,
|
||||
this.generator.getProtectedAccessInjectionOptions(member));
|
||||
}
|
||||
else if (member instanceof Field field) {
|
||||
contribution.runtimeHints().reflection().registerField(field);
|
||||
contribution.protectedAccess().analyze(member,
|
||||
this.generator.getProtectedAccessInjectionOptions(member));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* DependencyDescriptor variant with a pre-resolved target bean name.
|
||||
|
|
|
@ -42,8 +42,6 @@ import org.springframework.beans.factory.BeanCreationException;
|
|||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
|
||||
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
|
||||
import org.springframework.beans.factory.generator.AotContributingBeanPostProcessor;
|
||||
import org.springframework.beans.factory.generator.BeanInstantiationContribution;
|
||||
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
|
@ -87,8 +85,7 @@ import org.springframework.util.ReflectionUtils;
|
|||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor,
|
||||
MergedBeanDefinitionPostProcessor, AotContributingBeanPostProcessor, BeanRegistrationAotProcessor,
|
||||
PriorityOrdered, Serializable {
|
||||
MergedBeanDefinitionPostProcessor, BeanRegistrationAotProcessor, PriorityOrdered, Serializable {
|
||||
|
||||
private final transient LifecycleMetadata emptyLifecycleMetadata =
|
||||
new LifecycleMetadata(Object.class, Collections.emptyList(), Collections.emptyList()) {
|
||||
|
@ -159,22 +156,6 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
|
|||
findInjectionMetadata(beanDefinition, beanType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanInstantiationContribution contribute(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
|
||||
LifecycleMetadata metadata = findInjectionMetadata(beanDefinition, beanType);
|
||||
if (!CollectionUtils.isEmpty(metadata.initMethods)) {
|
||||
String[] initMethodNames = safeMerge(
|
||||
beanDefinition.getInitMethodNames(), metadata.initMethods);
|
||||
beanDefinition.setInitMethodNames(initMethodNames);
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(metadata.destroyMethods)) {
|
||||
String[] destroyMethodNames = safeMerge(
|
||||
beanDefinition.getDestroyMethodNames(), metadata.destroyMethods);
|
||||
beanDefinition.setDestroyMethodNames(destroyMethodNames);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||
RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition();
|
||||
|
|
|
@ -1,48 +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.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Specialization of {@link BeanFactoryPostProcessor} that contributes bean
|
||||
* factory optimizations ahead of time, using generated code that replaces
|
||||
* runtime behavior.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface AotContributingBeanFactoryPostProcessor extends BeanFactoryPostProcessor {
|
||||
|
||||
/**
|
||||
* Contribute a {@link BeanFactoryContribution} for the given bean factory,
|
||||
* if applicable.
|
||||
* @param beanFactory the bean factory to optimize
|
||||
* @return the contribution to use or {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
BeanFactoryContribution contribute(ConfigurableListableBeanFactory beanFactory);
|
||||
|
||||
@Override
|
||||
default void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,49 +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.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.PriorityOrdered;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Specialization of a priority ordered {@link BeanPostProcessor} that
|
||||
* contributes to bean instantiation ahead of time, providing generated code
|
||||
* that is equivalent to its runtime behavior.
|
||||
*
|
||||
* <p>Contrary to other bean post processors, implementations of this interface
|
||||
* are instantiated at build-time and should not rely on other beans in the
|
||||
* context.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface AotContributingBeanPostProcessor extends BeanPostProcessor, PriorityOrdered {
|
||||
|
||||
/**
|
||||
* Contribute a {@link BeanInstantiationContribution} for the given bean definition,
|
||||
* if applicable.
|
||||
* @param beanDefinition the merged bean definition for the bean
|
||||
* @param beanType the inferred type of the bean
|
||||
* @param beanName the name of the bean
|
||||
* @return the contribution to use or {@code null} if the bean should not be processed
|
||||
*/
|
||||
@Nullable
|
||||
BeanInstantiationContribution contribute(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);
|
||||
|
||||
}
|
|
@ -1,62 +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.springframework.beans.factory.config.BeanDefinition;
|
||||
|
||||
/**
|
||||
* Thrown when a bean definition could not be generated.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class BeanDefinitionGenerationException extends RuntimeException {
|
||||
|
||||
private final String beanName;
|
||||
|
||||
private final BeanDefinition beanDefinition;
|
||||
|
||||
public BeanDefinitionGenerationException(String beanName, BeanDefinition beanDefinition, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.beanName = beanName;
|
||||
this.beanDefinition = beanDefinition;
|
||||
}
|
||||
|
||||
public BeanDefinitionGenerationException(String beanName, BeanDefinition beanDefinition, String message) {
|
||||
super(message);
|
||||
this.beanName = beanName;
|
||||
this.beanDefinition = beanDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean name that could not be generated.
|
||||
* @return the bean name
|
||||
*/
|
||||
public String getBeanName() {
|
||||
return this.beanName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean definition that could not be generated.
|
||||
* @return the bean definition
|
||||
*/
|
||||
public BeanDefinition getBeanDefinition() {
|
||||
return this.beanDefinition;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,127 +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.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader.ArgumentResolver;
|
||||
|
||||
/**
|
||||
* A {@link BeanFactoryContribution} that generates the bean definitions of a
|
||||
* bean factory, using {@link BeanRegistrationContributionProvider} to use
|
||||
* appropriate customizations if necessary.
|
||||
*
|
||||
* <p>{@link BeanRegistrationContributionProvider} can be ordered, with the default
|
||||
* implementation always coming last.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
* @see DefaultBeanRegistrationContributionProvider
|
||||
*/
|
||||
public class BeanDefinitionsContribution implements BeanFactoryContribution {
|
||||
|
||||
private final DefaultListableBeanFactory beanFactory;
|
||||
|
||||
private final List<BeanRegistrationContributionProvider> contributionProviders;
|
||||
|
||||
private final Map<String, BeanFactoryContribution> contributions;
|
||||
|
||||
BeanDefinitionsContribution(DefaultListableBeanFactory beanFactory,
|
||||
List<BeanRegistrationContributionProvider> contributionProviders) {
|
||||
this.beanFactory = beanFactory;
|
||||
this.contributionProviders = contributionProviders;
|
||||
this.contributions = new HashMap<>();
|
||||
}
|
||||
|
||||
public BeanDefinitionsContribution(DefaultListableBeanFactory beanFactory) {
|
||||
this(beanFactory, initializeProviders(beanFactory));
|
||||
}
|
||||
|
||||
private static List<BeanRegistrationContributionProvider> initializeProviders(DefaultListableBeanFactory beanFactory) {
|
||||
List<BeanRegistrationContributionProvider> providers = new ArrayList<>(
|
||||
SpringFactoriesLoader.forDefaultResourceLocation(beanFactory.getBeanClassLoader()).load(
|
||||
BeanRegistrationContributionProvider.class,
|
||||
ArgumentResolver.from(type -> type.isInstance(beanFactory) ? beanFactory : null)));
|
||||
providers.add(new DefaultBeanRegistrationContributionProvider(beanFactory));
|
||||
return providers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(BeanFactoryInitialization initialization) {
|
||||
writeBeanDefinitions(initialization);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiPredicate<String, BeanDefinition> getBeanDefinitionExcludeFilter() {
|
||||
List<BiPredicate<String, BeanDefinition>> predicates = new ArrayList<>();
|
||||
for (String beanName : this.beanFactory.getBeanDefinitionNames()) {
|
||||
handleMergedBeanDefinition(beanName, beanDefinition -> predicates.add(
|
||||
getBeanRegistrationContribution(beanName, beanDefinition).getBeanDefinitionExcludeFilter()));
|
||||
}
|
||||
return predicates.stream().filter(Objects::nonNull).reduce((n, d) -> false, BiPredicate::or);
|
||||
}
|
||||
|
||||
private void writeBeanDefinitions(BeanFactoryInitialization initialization) {
|
||||
for (String beanName : this.beanFactory.getBeanDefinitionNames()) {
|
||||
handleMergedBeanDefinition(beanName, beanDefinition -> {
|
||||
BeanFactoryContribution registrationContribution = getBeanRegistrationContribution(
|
||||
beanName, beanDefinition);
|
||||
registrationContribution.applyTo(initialization);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private BeanFactoryContribution getBeanRegistrationContribution(
|
||||
String beanName, RootBeanDefinition beanDefinition) {
|
||||
return this.contributions.computeIfAbsent(beanName, name -> {
|
||||
for (BeanRegistrationContributionProvider provider : this.contributionProviders) {
|
||||
BeanFactoryContribution contribution = provider.getContributionFor(
|
||||
beanName, beanDefinition);
|
||||
if (contribution != null) {
|
||||
return contribution;
|
||||
}
|
||||
}
|
||||
throw new BeanRegistrationContributionNotFoundException(beanName, beanDefinition);
|
||||
});
|
||||
}
|
||||
|
||||
private void handleMergedBeanDefinition(String beanName, Consumer<RootBeanDefinition> consumer) {
|
||||
RootBeanDefinition beanDefinition = (RootBeanDefinition) this.beanFactory.getMergedBeanDefinition(beanName);
|
||||
try {
|
||||
consumer.accept(beanDefinition);
|
||||
}
|
||||
catch (BeanDefinitionGenerationException ex) {
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
String msg = String.format("Failed to handle bean with name '%s' and type '%s'",
|
||||
beanName, beanDefinition.getResolvableType());
|
||||
throw new BeanDefinitionGenerationException(beanName, beanDefinition, msg, ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,48 +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.util.function.BiPredicate;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
|
||||
/**
|
||||
* Contribute optimizations ahead of time to initialize a bean factory.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface BeanFactoryContribution {
|
||||
|
||||
/**
|
||||
* Contribute ahead of time optimizations to the specific
|
||||
* {@link BeanFactoryInitialization}.
|
||||
* @param initialization {@link BeanFactoryInitialization} to contribute to
|
||||
*/
|
||||
void applyTo(BeanFactoryInitialization initialization);
|
||||
|
||||
/**
|
||||
* Return a predicate that determines if a particular bean definition
|
||||
* should be excluded from processing. Can be used to exclude infrastructure
|
||||
* that has been optimized using generated code.
|
||||
* @return the predicate to use
|
||||
*/
|
||||
default BiPredicate<String, BeanDefinition> getBeanDefinitionExcludeFilter() {
|
||||
return (beanName, beanDefinition) -> false;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,110 +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.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.springframework.aot.generator.GeneratedType;
|
||||
import org.springframework.aot.generator.GeneratedTypeContext;
|
||||
import org.springframework.aot.generator.ProtectedAccess;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.CodeBlock.Builder;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
|
||||
/**
|
||||
* The initialization of a {@link BeanFactory}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public class BeanFactoryInitialization {
|
||||
|
||||
private final GeneratedTypeContext generatedTypeContext;
|
||||
|
||||
private final CodeBlock.Builder codeContributions;
|
||||
|
||||
public BeanFactoryInitialization(GeneratedTypeContext generatedTypeContext) {
|
||||
this.generatedTypeContext = generatedTypeContext;
|
||||
this.codeContributions = CodeBlock.builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link GeneratedTypeContext} to use to contribute
|
||||
* additional methods or hints.
|
||||
* @return the generation context
|
||||
*/
|
||||
public GeneratedTypeContext generatedTypeContext() {
|
||||
return this.generatedTypeContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contribute code that initializes the bean factory and that does not
|
||||
* require any privileged access.
|
||||
* @param code the code to contribute
|
||||
*/
|
||||
public void contribute(Consumer<Builder> code) {
|
||||
CodeBlock.Builder builder = CodeBlock.builder();
|
||||
code.accept(builder);
|
||||
CodeBlock codeBlock = builder.build();
|
||||
this.codeContributions.add(codeBlock);
|
||||
if (!codeBlock.toString().endsWith("\n")) {
|
||||
this.codeContributions.add("\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contribute code that initializes the bean factory. If privileged access
|
||||
* is required, a public method in the target package is created and
|
||||
* invoked, rather than contributing the code directly.
|
||||
* @param protectedAccess the {@link ProtectedAccess} instance to use
|
||||
* @param methodName a method name to use if privileged access is required
|
||||
* @param methodBody the contribution
|
||||
*/
|
||||
public void contribute(ProtectedAccess protectedAccess, Supplier<String> methodName,
|
||||
Consumer<Builder> methodBody) {
|
||||
String targetPackageName = this.generatedTypeContext.getMainGeneratedType().getClassName().packageName();
|
||||
String protectedPackageName = protectedAccess.getPrivilegedPackageName(targetPackageName);
|
||||
if (protectedPackageName != null) {
|
||||
GeneratedType type = this.generatedTypeContext.getGeneratedType(protectedPackageName);
|
||||
MethodSpec.Builder method = MethodSpec.methodBuilder(methodName.get())
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(DefaultListableBeanFactory.class, "beanFactory");
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
methodBody.accept(code);
|
||||
method.addCode(code.build());
|
||||
contribute(main -> main.addStatement("$T.$N(beanFactory)", type.getClassName(), type.addMethod(method)));
|
||||
}
|
||||
else {
|
||||
contribute(methodBody);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the code that has been contributed to this instance.
|
||||
* @return the code
|
||||
*/
|
||||
public CodeBlock toCodeBlock() {
|
||||
return this.codeContributions.build();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,65 +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 java.lang.reflect.Modifier;
|
||||
|
||||
import org.springframework.aot.generator.ProtectedAccess.Options;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.support.MultiStatement;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Support for generating {@link Field} access.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public class BeanFieldGenerator {
|
||||
|
||||
/**
|
||||
* The {@link Options} to use to access a field.
|
||||
*/
|
||||
public static final Options FIELD_OPTIONS = Options.defaults()
|
||||
.useReflection(member -> Modifier.isPrivate(member.getModifiers())).build();
|
||||
|
||||
|
||||
/**
|
||||
* Generate the necessary code to set the specified field. Use reflection
|
||||
* using {@link ReflectionUtils} if necessary.
|
||||
* @param field the field to set
|
||||
* @param value a code representation of the field value
|
||||
* @return the code to set the specified field
|
||||
*/
|
||||
public MultiStatement generateSetValue(String target, Field field, CodeBlock value) {
|
||||
MultiStatement statement = new MultiStatement();
|
||||
boolean useReflection = Modifier.isPrivate(field.getModifiers());
|
||||
if (useReflection) {
|
||||
String fieldName = String.format("%sField", field.getName());
|
||||
statement.addStatement("$T $L = $T.findField($T.class, $S)", Field.class, fieldName, ReflectionUtils.class,
|
||||
field.getDeclaringClass(), field.getName());
|
||||
statement.addStatement("$T.makeAccessible($L)", ReflectionUtils.class, fieldName);
|
||||
statement.addStatement("$T.setField($L, $L, $L)", ReflectionUtils.class, fieldName, target, value);
|
||||
}
|
||||
else {
|
||||
statement.addStatement("$L.$L = $L", target, field.getName(), value);
|
||||
}
|
||||
return statement;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,44 +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.springframework.aot.generator.CodeContribution;
|
||||
|
||||
/**
|
||||
* A contribution to the instantiation of a bean following ahead of time
|
||||
* processing.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BeanInstantiationContribution {
|
||||
|
||||
/**
|
||||
* Contribute bean instantiation to the specified {@link CodeContribution}.
|
||||
* <p>Implementations of this interface can assume the following variables
|
||||
* to be accessible:
|
||||
* <ul>
|
||||
* <li>{@code beanFactory}: the general {@code DefaultListableBeanFactory}</li>
|
||||
* <li>{@code instanceContext}: the {@code BeanInstanceContext} callback</li>
|
||||
* <li>{@code bean}: the variable that refers to the bean instance</li>
|
||||
* </ul>
|
||||
* @param contribution the {@link CodeContribution} to use
|
||||
*/
|
||||
void applyTo(CodeContribution contribution);
|
||||
|
||||
}
|
|
@ -1,47 +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.Executable;
|
||||
|
||||
import org.springframework.aot.generator.CodeContribution;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
|
||||
/**
|
||||
* Generate code that instantiate a particular bean.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface BeanInstantiationGenerator {
|
||||
|
||||
/**
|
||||
* Return the {@link Executable} that is used to create the bean instance
|
||||
* for further metadata processing.
|
||||
* @return the executable that is used to create the bean instance
|
||||
*/
|
||||
Executable getInstanceCreator();
|
||||
|
||||
/**
|
||||
* Return the necessary code to instantiate a bean.
|
||||
* @param runtimeHints the runtime hints instance to use
|
||||
* @return a code contribution that provides an initialized bean instance
|
||||
*/
|
||||
CodeContribution generateBeanInstantiation(RuntimeHints runtimeHints);
|
||||
|
||||
}
|
||||
|
|
@ -1,222 +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.Executable;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.aot.generator.ResolvableTypeGenerator;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanReference;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.support.ManagedList;
|
||||
import org.springframework.beans.factory.support.ManagedSet;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.CodeBlock.Builder;
|
||||
import org.springframework.javapoet.support.MultiCodeBlock;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Support for generating parameters.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class BeanParameterGenerator {
|
||||
|
||||
/**
|
||||
* A default instance that does not handle inner bean definitions.
|
||||
*/
|
||||
public static final BeanParameterGenerator INSTANCE = new BeanParameterGenerator();
|
||||
|
||||
private final ResolvableTypeGenerator typeGenerator = new ResolvableTypeGenerator();
|
||||
|
||||
private final Function<BeanDefinition, CodeBlock> innerBeanDefinitionGenerator;
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance with the callback to use to generate an inner bean
|
||||
* definition.
|
||||
* @param innerBeanDefinitionGenerator the inner bean definition generator
|
||||
*/
|
||||
public BeanParameterGenerator(Function<BeanDefinition, CodeBlock> innerBeanDefinitionGenerator) {
|
||||
this.innerBeanDefinitionGenerator = innerBeanDefinitionGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance with no support for inner bean definitions.
|
||||
*/
|
||||
public BeanParameterGenerator() {
|
||||
this(beanDefinition -> {
|
||||
throw new IllegalStateException("Inner bean definition is not supported by this instance");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate the specified parameter {@code value}.
|
||||
* @param value the value of the parameter
|
||||
* @return the value of the parameter
|
||||
*/
|
||||
public CodeBlock generateParameterValue(@Nullable Object value) {
|
||||
return generateParameterValue(value, () -> ResolvableType.forInstance(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the specified parameter {@code value}.
|
||||
* @param value the value of the parameter
|
||||
* @param parameterType the type of the parameter
|
||||
* @return the value of the parameter
|
||||
*/
|
||||
public CodeBlock generateParameterValue(@Nullable Object value, Supplier<ResolvableType> parameterType) {
|
||||
Builder code = CodeBlock.builder();
|
||||
generateParameterValue(code, value, parameterType);
|
||||
return code.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the parameter types of the specified {@link Executable}.
|
||||
* @param executable the executable
|
||||
* @return the parameter types of the executable as a comma separated list
|
||||
*/
|
||||
public CodeBlock generateExecutableParameterTypes(Executable executable) {
|
||||
Class<?>[] parameterTypes = Arrays.stream(executable.getParameters())
|
||||
.map(Parameter::getType).toArray(Class<?>[]::new);
|
||||
return CodeBlock.of(Arrays.stream(parameterTypes).map(d -> "$T.class")
|
||||
.collect(Collectors.joining(", ")), (Object[]) parameterTypes);
|
||||
}
|
||||
|
||||
private void generateParameterValue(Builder code, @Nullable Object value, Supplier<ResolvableType> parameterTypeSupplier) {
|
||||
if (value == null) {
|
||||
code.add("null");
|
||||
return;
|
||||
}
|
||||
ResolvableType parameterType = parameterTypeSupplier.get();
|
||||
if (parameterType.isArray()) {
|
||||
code.add("new $T { ", parameterType.toClass());
|
||||
code.add(generateAll(Arrays.asList(ObjectUtils.toObjectArray(value)),
|
||||
item -> parameterType.getComponentType()));
|
||||
code.add(" }");
|
||||
}
|
||||
else if (value instanceof List<?> list) {
|
||||
if (list.isEmpty()) {
|
||||
code.add("$T.emptyList()", Collections.class);
|
||||
}
|
||||
else {
|
||||
Class<?> listType = (value instanceof ManagedList ? ManagedList.class : List.class);
|
||||
code.add("$T.of(", listType);
|
||||
ResolvableType collectionType = parameterType.as(List.class).getGenerics()[0];
|
||||
code.add(generateAll(list, item -> collectionType));
|
||||
code.add(")");
|
||||
}
|
||||
}
|
||||
else if (value instanceof Set<?> set) {
|
||||
if (set.isEmpty()) {
|
||||
code.add("$T.emptySet()", Collections.class);
|
||||
}
|
||||
else {
|
||||
Class<?> setType = (value instanceof ManagedSet ? ManagedSet.class : Set.class);
|
||||
code.add("$T.of(", setType);
|
||||
ResolvableType collectionType = parameterType.as(Set.class).getGenerics()[0];
|
||||
code.add(generateAll(set, item -> collectionType));
|
||||
code.add(")");
|
||||
}
|
||||
}
|
||||
else if (value instanceof Map<?, ?> map) {
|
||||
if (map.size() <= 10) {
|
||||
code.add("$T.of(", Map.class);
|
||||
List<Object> parameters = new ArrayList<>();
|
||||
map.forEach((mapKey, mapValue) -> {
|
||||
parameters.add(mapKey);
|
||||
parameters.add(mapValue);
|
||||
});
|
||||
code.add(generateAll(parameters, ResolvableType::forInstance));
|
||||
code.add(")");
|
||||
}
|
||||
}
|
||||
else if (value instanceof Character character) {
|
||||
String result = '\'' + characterLiteralWithoutSingleQuotes(character) + '\'';
|
||||
code.add(result);
|
||||
}
|
||||
else if (isPrimitiveOrWrapper(value)) {
|
||||
code.add("$L", value);
|
||||
}
|
||||
else if (value instanceof String) {
|
||||
code.add("$S", value);
|
||||
}
|
||||
else if (value instanceof Enum<?> enumValue) {
|
||||
code.add("$T.$N", enumValue.getClass(), enumValue.name());
|
||||
}
|
||||
else if (value instanceof Class) {
|
||||
code.add("$T.class", value);
|
||||
}
|
||||
else if (value instanceof ResolvableType) {
|
||||
code.add(this.typeGenerator.generateTypeFor((ResolvableType) value));
|
||||
}
|
||||
else if (value instanceof BeanDefinition bd) {
|
||||
code.add(this.innerBeanDefinitionGenerator.apply(bd));
|
||||
}
|
||||
else if (value instanceof BeanReference) {
|
||||
code.add("new $T($S)", RuntimeBeanReference.class, ((BeanReference) value).getBeanName());
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Parameter of type " + parameterType + " is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
private <T> CodeBlock generateAll(Iterable<T> items, Function<T, ResolvableType> elementType) {
|
||||
MultiCodeBlock multi = new MultiCodeBlock();
|
||||
items.forEach(item -> multi.add(code ->
|
||||
generateParameterValue(code, item, () -> elementType.apply(item))));
|
||||
return multi.join(", ");
|
||||
}
|
||||
|
||||
private boolean isPrimitiveOrWrapper(Object value) {
|
||||
Class<?> valueType = value.getClass();
|
||||
return (valueType.isPrimitive() || valueType == Double.class || valueType == Float.class
|
||||
|| valueType == Long.class || valueType == Integer.class || valueType == Short.class
|
||||
|| valueType == Character.class || valueType == Byte.class || valueType == Boolean.class);
|
||||
}
|
||||
|
||||
// Copied from com.squareup.javapoet.Util
|
||||
private static String characterLiteralWithoutSingleQuotes(char c) {
|
||||
// see https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6
|
||||
return switch (c) {
|
||||
case '\b' -> "\\b"; /* \u0008: backspace (BS) */
|
||||
case '\t' -> "\\t"; /* \u0009: horizontal tab (HT) */
|
||||
case '\n' -> "\\n"; /* \u000a: linefeed (LF) */
|
||||
case '\f' -> "\\f"; /* \u000c: form feed (FF) */
|
||||
case '\r' -> "\\r"; /* \u000d: carriage return (CR) */
|
||||
case '\"' -> "\""; /* \u0022: double quote (") */
|
||||
case '\'' -> "\\'"; /* \u0027: single quote (') */
|
||||
case '\\' -> "\\\\"; /* \u005c: backslash (\) */
|
||||
default -> Character.isISOControl(c) ? String.format("\\u%04x", (int) c) : Character.toString(c);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -1,514 +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.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.lang.model.SourceVersion;
|
||||
|
||||
import org.springframework.aot.generator.CodeContribution;
|
||||
import org.springframework.aot.generator.ProtectedAccess;
|
||||
import org.springframework.aot.generator.ResolvableTypeGenerator;
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.beans.BeanInfoFactory;
|
||||
import org.springframework.beans.ExtendedBeanInfoFactory;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.PropertyValue;
|
||||
import org.springframework.beans.PropertyValues;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
|
||||
import org.springframework.beans.factory.generator.config.BeanDefinitionRegistrar;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.AttributeAccessor;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.CodeBlock.Builder;
|
||||
import org.springframework.javapoet.support.MultiStatement;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link BeanFactoryContribution} that registers a bean with the bean
|
||||
* factory.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public class BeanRegistrationBeanFactoryContribution implements BeanFactoryContribution {
|
||||
|
||||
private static final BeanInfoFactory beanInfoFactory = new ExtendedBeanInfoFactory();
|
||||
|
||||
private static final ResolvableTypeGenerator typeGenerator = new ResolvableTypeGenerator();
|
||||
|
||||
private final String beanName;
|
||||
|
||||
private final RootBeanDefinition beanDefinition;
|
||||
|
||||
private final BeanInstantiationGenerator beanInstantiationGenerator;
|
||||
|
||||
@Nullable
|
||||
private final DefaultBeanRegistrationContributionProvider innerBeanRegistrationContributionProvider;
|
||||
|
||||
private int nesting = 0;
|
||||
|
||||
BeanRegistrationBeanFactoryContribution(String beanName, RootBeanDefinition beanDefinition,
|
||||
BeanInstantiationGenerator beanInstantiationGenerator,
|
||||
@Nullable DefaultBeanRegistrationContributionProvider innerBeanRegistrationContributionProvider) {
|
||||
this.beanName = beanName;
|
||||
this.beanDefinition = beanDefinition;
|
||||
this.beanInstantiationGenerator = beanInstantiationGenerator;
|
||||
this.innerBeanRegistrationContributionProvider = innerBeanRegistrationContributionProvider;
|
||||
}
|
||||
|
||||
public BeanRegistrationBeanFactoryContribution(String beanName, RootBeanDefinition beanDefinition,
|
||||
BeanInstantiationGenerator beanInstantiationGenerator) {
|
||||
this(beanName, beanDefinition, beanInstantiationGenerator, null);
|
||||
}
|
||||
|
||||
String getBeanName() {
|
||||
return this.beanName;
|
||||
}
|
||||
|
||||
RootBeanDefinition getBeanDefinition() {
|
||||
return this.beanDefinition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(BeanFactoryInitialization initialization) {
|
||||
RuntimeHints runtimeHints = initialization.generatedTypeContext().runtimeHints();
|
||||
registerRuntimeHints(runtimeHints);
|
||||
CodeContribution beanInstanceContribution = generateBeanInstance(runtimeHints);
|
||||
// Write everything in one place
|
||||
ProtectedAccess protectedAccess = beanInstanceContribution.protectedAccess();
|
||||
protectedAccess.analyze(this.beanDefinition.getResolvableType());
|
||||
initialization.contribute(protectedAccess, this::registerBeanMethodName, code ->
|
||||
code.add(generateBeanRegistration(runtimeHints, beanInstanceContribution.statements())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the necessary hints that are required to process the bean
|
||||
* registration generated by this instance.
|
||||
* @param runtimeHints the runtime hints to use
|
||||
*/
|
||||
void registerRuntimeHints(RuntimeHints runtimeHints) {
|
||||
String[] initMethodNames = this.beanDefinition.getInitMethodNames();
|
||||
if (!ObjectUtils.isEmpty(initMethodNames)) {
|
||||
registerInitDestroyMethodsRuntimeHints(initMethodNames, runtimeHints);
|
||||
}
|
||||
String[] destroyMethodNames = this.beanDefinition.getDestroyMethodNames();
|
||||
if (!ObjectUtils.isEmpty(destroyMethodNames)) {
|
||||
registerInitDestroyMethodsRuntimeHints(destroyMethodNames, runtimeHints);
|
||||
}
|
||||
registerPropertyValuesRuntimeHints(runtimeHints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the necessary code to register a {@link BeanDefinition} in the
|
||||
* bean registry.
|
||||
* @param runtimeHints the hints to use
|
||||
* @param beanInstanceStatements the {@linkplain MultiStatement statements}
|
||||
* to create and initialize the bean instance
|
||||
* @return bean registration code
|
||||
*/
|
||||
CodeBlock generateBeanRegistration(RuntimeHints runtimeHints, MultiStatement beanInstanceStatements) {
|
||||
BeanParameterGenerator parameterGenerator = createBeanParameterGenerator(runtimeHints);
|
||||
Generator generator = new Generator(parameterGenerator);
|
||||
return generator.generateBeanRegistration(beanInstanceStatements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the necessary code to create a {@link BeanDefinition}.
|
||||
* @param runtimeHints the hints to use
|
||||
* @return bean definition code
|
||||
*/
|
||||
CodeBlock generateBeanDefinition(RuntimeHints runtimeHints) {
|
||||
CodeContribution beanInstanceContribution = generateBeanInstance(runtimeHints);
|
||||
BeanParameterGenerator parameterGenerator = createBeanParameterGenerator(runtimeHints);
|
||||
Generator generator = new Generator(parameterGenerator);
|
||||
return generator.generateBeanDefinition(beanInstanceContribution.statements());
|
||||
}
|
||||
|
||||
private BeanParameterGenerator createBeanParameterGenerator(RuntimeHints runtimeHints) {
|
||||
return new BeanParameterGenerator(beanDefinition ->
|
||||
generateInnerBeanDefinition(beanDefinition, runtimeHints));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the predicate to use to include Bean Definition
|
||||
* {@link AttributeAccessor attributes}.
|
||||
* @return the bean definition's attributes include filter
|
||||
*/
|
||||
protected Predicate<String> getAttributeFilter() {
|
||||
return candidate -> false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify if the creator {@link Executable} should be defined. By default,
|
||||
* a creator is specified if the {@code instanceSupplier} callback is used
|
||||
* with an {@code instanceContext} callback.
|
||||
* @param instanceCreator the executable to use to instantiate the bean
|
||||
* @return {@code true} to declare the creator
|
||||
*/
|
||||
protected boolean shouldDeclareCreator(Executable instanceCreator) {
|
||||
if (instanceCreator instanceof Method) {
|
||||
return true;
|
||||
}
|
||||
if (instanceCreator instanceof Constructor<?> constructor) {
|
||||
int minArgs = isInnerClass(constructor.getDeclaringClass()) ? 2 : 1;
|
||||
return instanceCreator.getParameterCount() >= minArgs;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the necessary code to instantiate and post-process a bean.
|
||||
* @param runtimeHints the {@link RuntimeHints} to use
|
||||
* @return a code contribution that provides an initialized bean instance
|
||||
*/
|
||||
protected CodeContribution generateBeanInstance(RuntimeHints runtimeHints) {
|
||||
return this.beanInstantiationGenerator.generateBeanInstantiation(runtimeHints);
|
||||
}
|
||||
|
||||
private void registerInitDestroyMethodsRuntimeHints(String[] methodNames, RuntimeHints runtimeHints) {
|
||||
for (String methodName : methodNames) {
|
||||
Method method = ReflectionUtils.findMethod(getUserBeanClass(), methodName);
|
||||
if (method != null) {
|
||||
runtimeHints.reflection().registerMethod(method, hint -> hint.withMode(ExecutableMode.INVOKE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerPropertyValuesRuntimeHints(RuntimeHints runtimeHints) {
|
||||
if (!this.beanDefinition.hasPropertyValues()) {
|
||||
return;
|
||||
}
|
||||
BeanInfo beanInfo = getBeanInfo(this.beanDefinition.getResolvableType().toClass());
|
||||
if (beanInfo != null) {
|
||||
ReflectionHints reflectionHints = runtimeHints.reflection();
|
||||
this.beanDefinition.getPropertyValues().getPropertyValueList().forEach(propertyValue -> {
|
||||
Method writeMethod = findWriteMethod(beanInfo, propertyValue.getName());
|
||||
if (writeMethod != null) {
|
||||
reflectionHints.registerMethod(writeMethod, hint -> hint.withMode(ExecutableMode.INVOKE));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BeanInfo getBeanInfo(Class<?> beanType) {
|
||||
try {
|
||||
BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanType);
|
||||
if (beanInfo != null) {
|
||||
return beanInfo;
|
||||
}
|
||||
return Introspector.getBeanInfo(beanType, Introspector.IGNORE_ALL_BEANINFO);
|
||||
}
|
||||
catch (IntrospectionException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Method findWriteMethod(BeanInfo beanInfo, String propertyName) {
|
||||
return Arrays.stream(beanInfo.getPropertyDescriptors())
|
||||
.filter(pd -> propertyName.equals(pd.getName()))
|
||||
.map(java.beans.PropertyDescriptor::getWriteMethod)
|
||||
.filter(Objects::nonNull).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
protected CodeBlock initializeBeanDefinitionRegistrar() {
|
||||
return CodeBlock.of("$T.of($S, ", BeanDefinitionRegistrar.class, this.beanName);
|
||||
}
|
||||
|
||||
private Class<?> getUserBeanClass() {
|
||||
return ClassUtils.getUserClass(this.beanDefinition.getResolvableType().toClass());
|
||||
}
|
||||
|
||||
private void handleCreatorReference(Builder code, Executable creator) {
|
||||
if (creator instanceof Method) {
|
||||
code.add(".withFactoryMethod($T.class, $S", creator.getDeclaringClass(), creator.getName());
|
||||
if (creator.getParameterCount() > 0) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
else {
|
||||
code.add(".withConstructor(");
|
||||
}
|
||||
code.add(BeanParameterGenerator.INSTANCE.generateExecutableParameterTypes(creator));
|
||||
code.add(")");
|
||||
}
|
||||
|
||||
private CodeBlock generateInnerBeanDefinition(BeanDefinition beanDefinition, RuntimeHints runtimeHints) {
|
||||
if (this.innerBeanRegistrationContributionProvider == null) {
|
||||
throw new IllegalStateException("This generator does not handle inner bean definition " + beanDefinition);
|
||||
}
|
||||
BeanRegistrationBeanFactoryContribution innerBeanRegistrationContribution = this.innerBeanRegistrationContributionProvider
|
||||
.getInnerBeanRegistrationContribution(this, beanDefinition);
|
||||
innerBeanRegistrationContribution.nesting = this.nesting + 1;
|
||||
innerBeanRegistrationContribution.registerRuntimeHints(runtimeHints);
|
||||
return innerBeanRegistrationContribution.generateBeanDefinition(runtimeHints);
|
||||
}
|
||||
|
||||
private String registerBeanMethodName() {
|
||||
Executable instanceCreator = this.beanInstantiationGenerator.getInstanceCreator();
|
||||
if (instanceCreator instanceof Method method) {
|
||||
String target = (isValidName(this.beanName)) ? this.beanName : method.getName();
|
||||
return String.format("register%s_%s", method.getDeclaringClass().getSimpleName(), target);
|
||||
}
|
||||
else if (instanceCreator.getDeclaringClass().getEnclosingClass() != null) {
|
||||
String target = (isValidName(this.beanName)) ? this.beanName : getUserBeanClass().getSimpleName();
|
||||
Class<?> enclosingClass = instanceCreator.getDeclaringClass().getEnclosingClass();
|
||||
return String.format("register%s_%s", enclosingClass.getSimpleName(), target);
|
||||
}
|
||||
else {
|
||||
String target = (isValidName(this.beanName)) ? this.beanName : getUserBeanClass().getSimpleName();
|
||||
return "register" + StringUtils.capitalize(target);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidName(@Nullable String name) {
|
||||
return name != null && SourceVersion.isIdentifier(name) && !SourceVersion.isKeyword(name);
|
||||
}
|
||||
|
||||
private String determineVariableName(String name) {
|
||||
return name + "_".repeat(this.nesting);
|
||||
}
|
||||
|
||||
private static boolean isInnerClass(Class<?> type) {
|
||||
return type.isMemberClass() && !java.lang.reflect.Modifier.isStatic(type.getModifiers());
|
||||
}
|
||||
|
||||
class Generator {
|
||||
|
||||
private final BeanParameterGenerator parameterGenerator;
|
||||
|
||||
private final RootBeanDefinition beanDefinition;
|
||||
|
||||
Generator(BeanParameterGenerator parameterGenerator) {
|
||||
this.parameterGenerator = parameterGenerator;
|
||||
this.beanDefinition = BeanRegistrationBeanFactoryContribution.this.beanDefinition;
|
||||
}
|
||||
|
||||
CodeBlock generateBeanRegistration(MultiStatement instanceStatements) {
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
initializeBeanDefinitionRegistrar(instanceStatements, code);
|
||||
code.addStatement(".register(beanFactory)");
|
||||
return code.build();
|
||||
}
|
||||
|
||||
CodeBlock generateBeanDefinition(MultiStatement instanceStatements) {
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
initializeBeanDefinitionRegistrar(instanceStatements, code);
|
||||
code.add(".toBeanDefinition()");
|
||||
return code.build();
|
||||
}
|
||||
|
||||
private void initializeBeanDefinitionRegistrar(MultiStatement instanceStatements, Builder code) {
|
||||
Executable instanceCreator = BeanRegistrationBeanFactoryContribution.this.beanInstantiationGenerator.getInstanceCreator();
|
||||
code.add(BeanRegistrationBeanFactoryContribution.this.initializeBeanDefinitionRegistrar());
|
||||
generateBeanType(code);
|
||||
code.add(")");
|
||||
boolean shouldDeclareCreator = shouldDeclareCreator(instanceCreator);
|
||||
if (shouldDeclareCreator) {
|
||||
handleCreatorReference(code, instanceCreator);
|
||||
}
|
||||
code.add("\n").indent().indent();
|
||||
code.add(".instanceSupplier(");
|
||||
code.add(instanceStatements.toLambdaBody());
|
||||
code.add(")").unindent().unindent();
|
||||
handleBeanDefinitionMetadata(code);
|
||||
}
|
||||
|
||||
private void generateBeanType(Builder code) {
|
||||
ResolvableType resolvableType = this.beanDefinition.getResolvableType();
|
||||
if (resolvableType.hasGenerics() && !hasUnresolvedGenerics(resolvableType)) {
|
||||
code.add(typeGenerator.generateTypeFor(resolvableType));
|
||||
}
|
||||
else {
|
||||
code.add("$T.class", getUserBeanClass());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasUnresolvedGenerics(ResolvableType resolvableType) {
|
||||
if (resolvableType.hasUnresolvableGenerics()) {
|
||||
return true;
|
||||
}
|
||||
for (ResolvableType generic : resolvableType.getGenerics()) {
|
||||
if (hasUnresolvedGenerics(generic)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleBeanDefinitionMetadata(Builder code) {
|
||||
String bdVariable = determineVariableName("bd");
|
||||
MultiStatement statements = new MultiStatement();
|
||||
String[] initMethodNames = this.beanDefinition.getInitMethodNames();
|
||||
if (!ObjectUtils.isEmpty(initMethodNames)) {
|
||||
handleInitMethodNames(statements, bdVariable, initMethodNames);
|
||||
}
|
||||
String[] destroyMethodNames = this.beanDefinition.getDestroyMethodNames();
|
||||
if (!ObjectUtils.isEmpty(destroyMethodNames)) {
|
||||
handleDestroyMethodNames(statements, bdVariable, destroyMethodNames);
|
||||
}
|
||||
if (this.beanDefinition.isPrimary()) {
|
||||
statements.addStatement("$L.setPrimary(true)", bdVariable);
|
||||
}
|
||||
String scope = this.beanDefinition.getScope();
|
||||
if (StringUtils.hasText(scope) && !ConfigurableBeanFactory.SCOPE_SINGLETON.equals(scope)) {
|
||||
statements.addStatement("$L.setScope($S)", bdVariable, scope);
|
||||
}
|
||||
String[] dependsOn = this.beanDefinition.getDependsOn();
|
||||
if (!ObjectUtils.isEmpty(dependsOn)) {
|
||||
statements.addStatement("$L.setDependsOn($L)", bdVariable,
|
||||
this.parameterGenerator.generateParameterValue(dependsOn));
|
||||
}
|
||||
if (this.beanDefinition.isLazyInit()) {
|
||||
statements.addStatement("$L.setLazyInit(true)", bdVariable);
|
||||
}
|
||||
if (!this.beanDefinition.isAutowireCandidate()) {
|
||||
statements.addStatement("$L.setAutowireCandidate(false)", bdVariable);
|
||||
}
|
||||
if (this.beanDefinition.isSynthetic()) {
|
||||
statements.addStatement("$L.setSynthetic(true)", bdVariable);
|
||||
}
|
||||
if (this.beanDefinition.getRole() != BeanDefinition.ROLE_APPLICATION) {
|
||||
statements.addStatement("$L.setRole($L)", bdVariable, this.beanDefinition.getRole());
|
||||
}
|
||||
Map<Integer, ValueHolder> indexedArgumentValues = this.beanDefinition.getConstructorArgumentValues()
|
||||
.getIndexedArgumentValues();
|
||||
if (!indexedArgumentValues.isEmpty()) {
|
||||
handleArgumentValues(statements, bdVariable, indexedArgumentValues);
|
||||
}
|
||||
if (this.beanDefinition.hasPropertyValues()) {
|
||||
handlePropertyValues(statements, bdVariable, this.beanDefinition.getPropertyValues());
|
||||
}
|
||||
if (this.beanDefinition.attributeNames().length > 0) {
|
||||
handleAttributes(statements, bdVariable);
|
||||
}
|
||||
if (statements.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
code.add(statements.toLambda(".customize((" + bdVariable + ") ->"));
|
||||
code.add(")");
|
||||
}
|
||||
|
||||
private void handleInitMethodNames(MultiStatement statements, String bdVariable, String[] initMethodNames) {
|
||||
if (initMethodNames.length == 1) {
|
||||
statements.addStatement("$L.setInitMethodName($S)", bdVariable, initMethodNames[0]);
|
||||
}
|
||||
else {
|
||||
statements.addStatement("$L.setInitMethodNames($L)", bdVariable,
|
||||
this.parameterGenerator.generateParameterValue(initMethodNames));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDestroyMethodNames(MultiStatement statements, String bdVariable, String[] destroyMethodNames) {
|
||||
if (destroyMethodNames.length == 1) {
|
||||
statements.addStatement("$L.setDestroyMethodName($S)", bdVariable, destroyMethodNames[0]);
|
||||
}
|
||||
else {
|
||||
statements.addStatement("$L.setDestroyMethodNames($L)", bdVariable,
|
||||
this.parameterGenerator.generateParameterValue(destroyMethodNames));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleArgumentValues(MultiStatement statements, String bdVariable,
|
||||
Map<Integer, ValueHolder> indexedArgumentValues) {
|
||||
if (indexedArgumentValues.size() == 1) {
|
||||
Entry<Integer, ValueHolder> entry = indexedArgumentValues.entrySet().iterator().next();
|
||||
statements.addStatement(generateArgumentValue(bdVariable + ".getConstructorArgumentValues().",
|
||||
entry.getKey(), entry.getValue()));
|
||||
}
|
||||
else {
|
||||
String avVariable = determineVariableName("argumentValues");
|
||||
statements.addStatement("$T $L = $L.getConstructorArgumentValues()", ConstructorArgumentValues.class, avVariable, bdVariable);
|
||||
statements.addAll(indexedArgumentValues.entrySet(), entry -> generateArgumentValue(avVariable + ".",
|
||||
entry.getKey(), entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
private CodeBlock generateArgumentValue(String prefix, Integer index, ValueHolder valueHolder) {
|
||||
Builder code = CodeBlock.builder();
|
||||
code.add(prefix);
|
||||
code.add("addIndexedArgumentValue($L, ", index);
|
||||
Object value = valueHolder.getValue();
|
||||
code.add(this.parameterGenerator.generateParameterValue(value));
|
||||
code.add(")");
|
||||
return code.build();
|
||||
}
|
||||
|
||||
private void handlePropertyValues(MultiStatement statements, String bdVariable,
|
||||
PropertyValues propertyValues) {
|
||||
PropertyValue[] properties = propertyValues.getPropertyValues();
|
||||
if (properties.length == 1) {
|
||||
statements.addStatement(generatePropertyValue(bdVariable + ".getPropertyValues().", properties[0]));
|
||||
}
|
||||
else {
|
||||
String pvVariable = determineVariableName("propertyValues");
|
||||
statements.addStatement("$T $L = $L.getPropertyValues()", MutablePropertyValues.class, pvVariable, bdVariable);
|
||||
for (PropertyValue property : properties) {
|
||||
statements.addStatement(generatePropertyValue(pvVariable + ".", property));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CodeBlock generatePropertyValue(String prefix, PropertyValue property) {
|
||||
Builder code = CodeBlock.builder();
|
||||
code.add(prefix);
|
||||
code.add("addPropertyValue($S, ", property.getName());
|
||||
Object value = property.getValue();
|
||||
code.add(this.parameterGenerator.generateParameterValue(value));
|
||||
code.add(")");
|
||||
return code.build();
|
||||
}
|
||||
|
||||
private void handleAttributes(MultiStatement statements, String bdVariable) {
|
||||
String[] attributeNames = this.beanDefinition.attributeNames();
|
||||
Predicate<String> filter = getAttributeFilter();
|
||||
for (String attributeName : attributeNames) {
|
||||
if (filter.test(attributeName)) {
|
||||
Object value = this.beanDefinition.getAttribute(attributeName);
|
||||
Builder code = CodeBlock.builder();
|
||||
code.add("$L.setAttribute($S, ", bdVariable, attributeName);
|
||||
code.add((this.parameterGenerator.generateParameterValue(value)));
|
||||
code.add(")");
|
||||
statements.addStatement(code.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,37 +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.springframework.beans.factory.config.BeanDefinition;
|
||||
|
||||
/**
|
||||
* Thrown when no suitable {@link BeanFactoryContribution} can be provided
|
||||
* for the registration of a given bean definition.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class BeanRegistrationContributionNotFoundException extends BeanDefinitionGenerationException {
|
||||
|
||||
public BeanRegistrationContributionNotFoundException(String beanName, BeanDefinition beanDefinition) {
|
||||
super(beanName, beanDefinition, String.format(
|
||||
"No suitable contribution found for bean with name '%s' and type '%s'",
|
||||
beanName, beanDefinition.getResolvableType()));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,43 +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.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Strategy interface to be implemented by components that require custom
|
||||
* contribution for a bean definition.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BeanRegistrationContributionProvider {
|
||||
|
||||
/**
|
||||
* Return the {@link BeanFactoryContribution} that is capable of contributing
|
||||
* the registration of a bean for the given {@link RootBeanDefinition} or
|
||||
* {@code null} if the specified bean definition is not supported.
|
||||
* @param beanName the bean name to handle
|
||||
* @param beanDefinition the merged bean definition
|
||||
* @return a contribution for the specified bean definition or {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
BeanFactoryContribution getContributionFor(String beanName, RootBeanDefinition beanDefinition);
|
||||
|
||||
}
|
|
@ -1,168 +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.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.aot.generator.CodeContribution;
|
||||
import org.springframework.aot.generator.DefaultCodeContribution;
|
||||
import org.springframework.aot.generator.ProtectedAccess.Options;
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Default {@link BeanInstantiationGenerator} implementation.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @see BeanInstantiationContribution
|
||||
*/
|
||||
class DefaultBeanInstantiationGenerator implements BeanInstantiationGenerator {
|
||||
|
||||
private final Executable instanceCreator;
|
||||
|
||||
private final List<BeanInstantiationContribution> contributions;
|
||||
|
||||
private final InjectionGenerator injectionGenerator;
|
||||
|
||||
private final Options beanInstanceOptions;
|
||||
|
||||
|
||||
DefaultBeanInstantiationGenerator(Executable instanceCreator, List<BeanInstantiationContribution> contributions) {
|
||||
this.instanceCreator = instanceCreator;
|
||||
this.contributions = List.copyOf(contributions);
|
||||
this.injectionGenerator = new InjectionGenerator();
|
||||
this.beanInstanceOptions = Options.defaults().useReflection(member -> false)
|
||||
.assignReturnType(member -> !this.contributions.isEmpty()).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Executable getInstanceCreator() {
|
||||
return this.instanceCreator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeContribution generateBeanInstantiation(RuntimeHints runtimeHints) {
|
||||
DefaultCodeContribution codeContribution = new DefaultCodeContribution(runtimeHints);
|
||||
codeContribution.protectedAccess().analyze(this.instanceCreator, this.beanInstanceOptions);
|
||||
if (this.instanceCreator instanceof Constructor<?> constructor) {
|
||||
generateBeanInstantiation(codeContribution, constructor);
|
||||
}
|
||||
else if (this.instanceCreator instanceof Method method) {
|
||||
generateBeanInstantiation(codeContribution, method);
|
||||
}
|
||||
return codeContribution;
|
||||
}
|
||||
|
||||
private void generateBeanInstantiation(CodeContribution codeContribution, Constructor<?> constructor) {
|
||||
Class<?> declaringType = ClassUtils.getUserClass(constructor.getDeclaringClass());
|
||||
boolean innerClass = isInnerClass(declaringType);
|
||||
boolean multiStatements = !this.contributions.isEmpty();
|
||||
int minArgs = isInnerClass(declaringType) ? 2 : 1;
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
// Shortcut for common case
|
||||
if (!multiStatements && constructor.getParameterTypes().length < minArgs) {
|
||||
if (innerClass) {
|
||||
code.add("() -> beanFactory.getBean($T.class).new $L()",
|
||||
declaringType.getEnclosingClass(), declaringType.getSimpleName());
|
||||
}
|
||||
else {
|
||||
// Only apply the shortcut if there's one candidate
|
||||
if (declaringType.getDeclaredConstructors().length > 1) {
|
||||
code.add("() -> new $T()", declaringType);
|
||||
}
|
||||
else {
|
||||
code.add("$T::new", declaringType);
|
||||
}
|
||||
}
|
||||
codeContribution.statements().addStatement(code.build());
|
||||
return;
|
||||
}
|
||||
codeContribution.runtimeHints().reflection().registerConstructor(constructor,
|
||||
hint -> hint.withMode(ExecutableMode.INTROSPECT));
|
||||
code.add("(instanceContext) ->");
|
||||
branch(multiStatements, () -> code.beginControlFlow(""), () -> code.add(" "));
|
||||
if (multiStatements) {
|
||||
code.add("$T bean = ", declaringType);
|
||||
}
|
||||
code.add(this.injectionGenerator.generateInstantiation(constructor));
|
||||
codeContribution.statements().addStatement(code.build());
|
||||
|
||||
if (multiStatements) {
|
||||
for (BeanInstantiationContribution contribution : this.contributions) {
|
||||
contribution.applyTo(codeContribution);
|
||||
}
|
||||
codeContribution.statements().addStatement("return bean")
|
||||
.add(codeBlock -> codeBlock.unindent().add("}"));
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isInnerClass(Class<?> type) {
|
||||
return type.isMemberClass() && !Modifier.isStatic(type.getModifiers());
|
||||
}
|
||||
|
||||
private void generateBeanInstantiation(CodeContribution codeContribution, Method method) {
|
||||
// Factory method can be introspected
|
||||
codeContribution.runtimeHints().reflection().registerMethod(method,
|
||||
hint -> hint.withMode(ExecutableMode.INTROSPECT));
|
||||
List<Class<?>> parameterTypes = new ArrayList<>(Arrays.asList(method.getParameterTypes()));
|
||||
boolean multiStatements = !this.contributions.isEmpty();
|
||||
Class<?> declaringType = method.getDeclaringClass();
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
// Shortcut for common case
|
||||
if (!multiStatements && parameterTypes.isEmpty()) {
|
||||
code.add("() -> ");
|
||||
branch(Modifier.isStatic(method.getModifiers()),
|
||||
() -> code.add("$T", declaringType),
|
||||
() -> code.add("beanFactory.getBean($T.class)", declaringType));
|
||||
code.add(".$L()", method.getName());
|
||||
codeContribution.statements().addStatement(code.build());
|
||||
return;
|
||||
}
|
||||
code.add("(instanceContext) ->");
|
||||
branch(multiStatements, () -> code.beginControlFlow(""), () -> code.add(" "));
|
||||
if (multiStatements) {
|
||||
code.add("$T bean = ", method.getReturnType());
|
||||
}
|
||||
code.add(this.injectionGenerator.generateInstantiation(method));
|
||||
codeContribution.statements().addStatement(code.build());
|
||||
if (multiStatements) {
|
||||
for (BeanInstantiationContribution contribution : this.contributions) {
|
||||
contribution.applyTo(codeContribution);
|
||||
}
|
||||
codeContribution.statements().addStatement("return bean")
|
||||
.add(codeBlock -> codeBlock.unindent().add("}"));
|
||||
}
|
||||
}
|
||||
|
||||
private static void branch(boolean condition, Runnable ifTrue, Runnable ifFalse) {
|
||||
if (condition) {
|
||||
ifTrue.run();
|
||||
}
|
||||
else {
|
||||
ifFalse.run();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,494 +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.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanReference;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionValueResolver;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.OrderComparator;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.function.SingletonSupplier;
|
||||
|
||||
/**
|
||||
* Default {@link BeanRegistrationContributionProvider} implementation.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class DefaultBeanRegistrationContributionProvider implements BeanRegistrationContributionProvider {
|
||||
|
||||
private final DefaultListableBeanFactory beanFactory;
|
||||
|
||||
private final ExecutableProvider executableProvider;
|
||||
|
||||
private final Supplier<List<AotContributingBeanPostProcessor>> beanPostProcessors;
|
||||
|
||||
public DefaultBeanRegistrationContributionProvider(DefaultListableBeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
this.executableProvider = new ExecutableProvider(beanFactory);
|
||||
this.beanPostProcessors = new SingletonSupplier<>(null,
|
||||
() -> loadAotContributingBeanPostProcessors(beanFactory));
|
||||
}
|
||||
|
||||
private static List<AotContributingBeanPostProcessor> loadAotContributingBeanPostProcessors(
|
||||
DefaultListableBeanFactory beanFactory) {
|
||||
String[] postProcessorNames = beanFactory.getBeanNamesForType(AotContributingBeanPostProcessor.class, true, false);
|
||||
List<AotContributingBeanPostProcessor> postProcessors = new ArrayList<>();
|
||||
for (String ppName : postProcessorNames) {
|
||||
postProcessors.add(beanFactory.getBean(ppName, AotContributingBeanPostProcessor.class));
|
||||
}
|
||||
sortPostProcessors(postProcessors, beanFactory);
|
||||
return postProcessors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanRegistrationBeanFactoryContribution getContributionFor(
|
||||
String beanName, RootBeanDefinition beanDefinition) {
|
||||
BeanInstantiationGenerator beanInstantiationGenerator = getBeanInstantiationGenerator(
|
||||
beanName, beanDefinition);
|
||||
return new BeanRegistrationBeanFactoryContribution(beanName, beanDefinition, beanInstantiationGenerator, this);
|
||||
}
|
||||
|
||||
public BeanInstantiationGenerator getBeanInstantiationGenerator(
|
||||
String beanName, RootBeanDefinition beanDefinition) {
|
||||
return new DefaultBeanInstantiationGenerator(determineExecutable(beanDefinition),
|
||||
determineBeanInstanceContributions(beanName, beanDefinition));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link BeanRegistrationBeanFactoryContribution} that is capable of
|
||||
* contributing the specified inner {@link BeanDefinition}.
|
||||
* @param parent the contribution of the parent bean definition
|
||||
* @param innerBeanDefinition the inner bean definition
|
||||
* @return a contribution for the specified inner bean definition
|
||||
*/
|
||||
BeanRegistrationBeanFactoryContribution getInnerBeanRegistrationContribution(
|
||||
BeanRegistrationBeanFactoryContribution parent, BeanDefinition innerBeanDefinition) {
|
||||
BeanDefinitionValueResolver bdvr = new BeanDefinitionValueResolver(this.beanFactory,
|
||||
parent.getBeanName(), parent.getBeanDefinition());
|
||||
return bdvr.resolveInnerBean(null, innerBeanDefinition, (beanName, bd) ->
|
||||
new InnerBeanRegistrationBeanFactoryContribution(beanName, bd,
|
||||
getBeanInstantiationGenerator(beanName, bd), this));
|
||||
}
|
||||
|
||||
private Executable determineExecutable(RootBeanDefinition beanDefinition) {
|
||||
Executable executable = this.executableProvider.detectBeanInstanceExecutable(beanDefinition);
|
||||
if (executable == null) {
|
||||
throw new IllegalStateException("No suitable executor found for " + beanDefinition);
|
||||
}
|
||||
return executable;
|
||||
}
|
||||
|
||||
private List<BeanInstantiationContribution> determineBeanInstanceContributions(
|
||||
String beanName, RootBeanDefinition beanDefinition) {
|
||||
List<BeanInstantiationContribution> contributions = new ArrayList<>();
|
||||
for (AotContributingBeanPostProcessor pp : this.beanPostProcessors.get()) {
|
||||
BeanInstantiationContribution contribution = pp.contribute(beanDefinition,
|
||||
beanDefinition.getResolvableType().toClass(), beanName);
|
||||
if (contribution != null) {
|
||||
contributions.add(contribution);
|
||||
}
|
||||
}
|
||||
return contributions;
|
||||
}
|
||||
|
||||
private static void sortPostProcessors(List<?> postProcessors, ConfigurableListableBeanFactory beanFactory) {
|
||||
// Nothing to sort?
|
||||
if (postProcessors.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
Comparator<Object> comparatorToUse = null;
|
||||
if (beanFactory instanceof DefaultListableBeanFactory) {
|
||||
comparatorToUse = ((DefaultListableBeanFactory) beanFactory).getDependencyComparator();
|
||||
}
|
||||
if (comparatorToUse == null) {
|
||||
comparatorToUse = OrderComparator.INSTANCE;
|
||||
}
|
||||
postProcessors.sort(comparatorToUse);
|
||||
}
|
||||
|
||||
// FIXME: copy-paste from Spring Native that should go away in favor of ConstructorResolver
|
||||
private static class ExecutableProvider {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ExecutableProvider.class);
|
||||
|
||||
private final ConfigurableBeanFactory beanFactory;
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
ExecutableProvider(ConfigurableBeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
this.classLoader = (beanFactory.getBeanClassLoader() != null
|
||||
? beanFactory.getBeanClassLoader() : getClass().getClassLoader());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Executable detectBeanInstanceExecutable(BeanDefinition beanDefinition) {
|
||||
Supplier<ResolvableType> beanType = () -> getBeanType(beanDefinition);
|
||||
List<ResolvableType> valueTypes = beanDefinition.hasConstructorArgumentValues()
|
||||
? determineParameterValueTypes(beanDefinition.getConstructorArgumentValues()) : Collections.emptyList();
|
||||
Method resolvedFactoryMethod = resolveFactoryMethod(beanDefinition, valueTypes);
|
||||
if (resolvedFactoryMethod != null) {
|
||||
return resolvedFactoryMethod;
|
||||
}
|
||||
Class<?> factoryBeanClass = getFactoryBeanClass(beanDefinition);
|
||||
if (factoryBeanClass != null && !factoryBeanClass.equals(beanDefinition.getResolvableType().toClass())) {
|
||||
ResolvableType resolvableType = beanDefinition.getResolvableType();
|
||||
boolean isCompatible = ResolvableType.forClass(factoryBeanClass).as(FactoryBean.class)
|
||||
.getGeneric(0).isAssignableFrom(resolvableType);
|
||||
if (isCompatible) {
|
||||
return resolveConstructor(() -> ResolvableType.forClass(factoryBeanClass), valueTypes);
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(String.format("Incompatible target type '%s' for factory bean '%s'",
|
||||
resolvableType.toClass().getName(), factoryBeanClass.getName()));
|
||||
}
|
||||
}
|
||||
Executable resolvedConstructor = resolveConstructor(beanType, valueTypes);
|
||||
if (resolvedConstructor != null) {
|
||||
return resolvedConstructor;
|
||||
}
|
||||
Executable resolvedConstructorOrFactoryMethod = getField(beanDefinition,
|
||||
"resolvedConstructorOrFactoryMethod", Executable.class);
|
||||
if (resolvedConstructorOrFactoryMethod != null) {
|
||||
logger.error("resolvedConstructorOrFactoryMethod required for " + beanDefinition);
|
||||
return resolvedConstructorOrFactoryMethod;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<ResolvableType> determineParameterValueTypes(ConstructorArgumentValues constructorArgumentValues) {
|
||||
List<ResolvableType> parameterTypes = new ArrayList<>();
|
||||
for (ValueHolder valueHolder : constructorArgumentValues.getIndexedArgumentValues().values()) {
|
||||
if (valueHolder.getType() != null) {
|
||||
parameterTypes.add(ResolvableType.forClass(loadClass(valueHolder.getType())));
|
||||
}
|
||||
else {
|
||||
Object value = valueHolder.getValue();
|
||||
if (value instanceof BeanReference) {
|
||||
parameterTypes.add(ResolvableType.forClass(
|
||||
this.beanFactory.getType(((BeanReference) value).getBeanName(), false)));
|
||||
}
|
||||
else if (value instanceof BeanDefinition) {
|
||||
parameterTypes.add(extractTypeFromBeanDefinition(getBeanType((BeanDefinition) value)));
|
||||
}
|
||||
else {
|
||||
parameterTypes.add(ResolvableType.forInstance(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
return parameterTypes;
|
||||
}
|
||||
|
||||
private ResolvableType extractTypeFromBeanDefinition(ResolvableType type) {
|
||||
if (FactoryBean.class.isAssignableFrom(type.toClass())) {
|
||||
return type.as(FactoryBean.class).getGeneric(0);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Method resolveFactoryMethod(BeanDefinition beanDefinition, List<ResolvableType> valueTypes) {
|
||||
if (beanDefinition instanceof RootBeanDefinition rbd) {
|
||||
Method resolvedFactoryMethod = rbd.getResolvedFactoryMethod();
|
||||
if (resolvedFactoryMethod != null) {
|
||||
return resolvedFactoryMethod;
|
||||
}
|
||||
}
|
||||
String factoryMethodName = beanDefinition.getFactoryMethodName();
|
||||
if (factoryMethodName != null) {
|
||||
List<Method> methods = new ArrayList<>();
|
||||
Class<?> beanClass = getBeanClass(beanDefinition);
|
||||
if (beanClass == null) {
|
||||
throw new IllegalStateException("Failed to determine bean class of " + beanDefinition);
|
||||
}
|
||||
ReflectionUtils.doWithMethods(beanClass, methods::add,
|
||||
method -> isFactoryMethodCandidate(beanClass, method, factoryMethodName));
|
||||
if (methods.size() >= 1) {
|
||||
Function<Method, List<ResolvableType>> parameterTypesFactory = method -> {
|
||||
List<ResolvableType> types = new ArrayList<>();
|
||||
for (int i = 0; i < method.getParameterCount(); i++) {
|
||||
types.add(ResolvableType.forMethodParameter(method, i));
|
||||
}
|
||||
return types;
|
||||
};
|
||||
return (Method) resolveFactoryMethod(methods, parameterTypesFactory, valueTypes);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isFactoryMethodCandidate(Class<?> beanClass, Method method, String factoryMethodName) {
|
||||
if (method.getName().equals(factoryMethodName)) {
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
return method.getDeclaringClass().equals(beanClass);
|
||||
}
|
||||
return !Modifier.isPrivate(method.getModifiers());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Executable resolveConstructor(Supplier<ResolvableType> beanType, List<ResolvableType> valueTypes) {
|
||||
Class<?> type = ClassUtils.getUserClass(beanType.get().toClass());
|
||||
Constructor<?>[] constructors = type.getDeclaredConstructors();
|
||||
if (constructors.length == 1) {
|
||||
return constructors[0];
|
||||
}
|
||||
for (Constructor<?> constructor : constructors) {
|
||||
if (MergedAnnotations.from(constructor).isPresent(Autowired.class)) {
|
||||
return constructor;
|
||||
}
|
||||
}
|
||||
Function<Constructor<?>, List<ResolvableType>> parameterTypesFactory = executable -> {
|
||||
List<ResolvableType> types = new ArrayList<>();
|
||||
for (int i = 0; i < executable.getParameterCount(); i++) {
|
||||
types.add(ResolvableType.forConstructorParameter(executable, i));
|
||||
}
|
||||
return types;
|
||||
};
|
||||
List<? extends Executable> matches = Arrays.stream(constructors)
|
||||
.filter(executable -> match(parameterTypesFactory.apply(executable),
|
||||
valueTypes, FallbackMode.NONE)).toList();
|
||||
if (matches.size() == 1) {
|
||||
return matches.get(0);
|
||||
}
|
||||
List<? extends Executable> assignableElementFallbackMatches = Arrays.stream(constructors)
|
||||
.filter(executable -> match(parameterTypesFactory.apply(executable),
|
||||
valueTypes, FallbackMode.ASSIGNABLE_ELEMENT)).toList();
|
||||
if (assignableElementFallbackMatches.size() == 1) {
|
||||
return assignableElementFallbackMatches.get(0);
|
||||
}
|
||||
List<? extends Executable> typeConversionFallbackMatches = Arrays.stream(constructors)
|
||||
.filter(executable -> match(parameterTypesFactory.apply(executable),
|
||||
valueTypes, ExecutableProvider.FallbackMode.TYPE_CONVERSION)).toList();
|
||||
return (typeConversionFallbackMatches.size() == 1) ? typeConversionFallbackMatches.get(0) : null;
|
||||
}
|
||||
|
||||
private Executable resolveFactoryMethod(List<Method> executables,
|
||||
Function<Method, List<ResolvableType>> parameterTypesFactory, List<ResolvableType> valueTypes) {
|
||||
List<? extends Executable> matches = executables.stream()
|
||||
.filter(executable -> match(parameterTypesFactory.apply(executable),
|
||||
valueTypes, ExecutableProvider.FallbackMode.NONE)).toList();
|
||||
if (matches.size() == 1) {
|
||||
return matches.get(0);
|
||||
}
|
||||
List<? extends Executable> assignableElementFallbackMatches = executables.stream()
|
||||
.filter(executable -> match(parameterTypesFactory.apply(executable),
|
||||
valueTypes, ExecutableProvider.FallbackMode.ASSIGNABLE_ELEMENT)).toList();
|
||||
if (assignableElementFallbackMatches.size() == 1) {
|
||||
return assignableElementFallbackMatches.get(0);
|
||||
}
|
||||
List<? extends Executable> typeConversionFallbackMatches = executables.stream()
|
||||
.filter(executable -> match(parameterTypesFactory.apply(executable),
|
||||
valueTypes, ExecutableProvider.FallbackMode.TYPE_CONVERSION)).toList();
|
||||
if (typeConversionFallbackMatches.size() > 1) {
|
||||
throw new IllegalStateException("Multiple matches with parameters '"
|
||||
+ valueTypes + "': " + typeConversionFallbackMatches);
|
||||
}
|
||||
return (typeConversionFallbackMatches.size() == 1) ? typeConversionFallbackMatches.get(0) : null;
|
||||
}
|
||||
|
||||
private boolean match(List<ResolvableType> parameterTypes, List<ResolvableType> valueTypes,
|
||||
ExecutableProvider.FallbackMode fallbackMode) {
|
||||
if (parameterTypes.size() != valueTypes.size()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < parameterTypes.size(); i++) {
|
||||
if (!isMatch(parameterTypes.get(i), valueTypes.get(i), fallbackMode)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isMatch(ResolvableType parameterType, ResolvableType valueType,
|
||||
ExecutableProvider.FallbackMode fallbackMode) {
|
||||
if (isAssignable(valueType).test(parameterType)) {
|
||||
return true;
|
||||
}
|
||||
return switch (fallbackMode) {
|
||||
case ASSIGNABLE_ELEMENT -> isAssignable(valueType).test(extractElementType(parameterType));
|
||||
case TYPE_CONVERSION -> typeConversionFallback(valueType).test(parameterType);
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private Predicate<ResolvableType> isAssignable(ResolvableType valueType) {
|
||||
return parameterType -> {
|
||||
if (valueType.hasUnresolvableGenerics()) {
|
||||
return parameterType.toClass().isAssignableFrom(valueType.toClass());
|
||||
}
|
||||
else {
|
||||
return parameterType.isAssignableFrom(valueType);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ResolvableType extractElementType(ResolvableType parameterType) {
|
||||
if (parameterType.isArray()) {
|
||||
return parameterType.getComponentType();
|
||||
}
|
||||
if (Collection.class.isAssignableFrom(parameterType.toClass())) {
|
||||
return parameterType.as(Collection.class).getGeneric(0);
|
||||
}
|
||||
return ResolvableType.NONE;
|
||||
}
|
||||
|
||||
private Predicate<ResolvableType> typeConversionFallback(ResolvableType valueType) {
|
||||
return parameterType -> {
|
||||
if (valueOrCollection(valueType, this::isStringForClassFallback).test(parameterType)) {
|
||||
return true;
|
||||
}
|
||||
return valueOrCollection(valueType, this::isSimpleConvertibleType).test(parameterType);
|
||||
};
|
||||
}
|
||||
|
||||
private Predicate<ResolvableType> valueOrCollection(ResolvableType valueType,
|
||||
Function<ResolvableType, Predicate<ResolvableType>> predicateProvider) {
|
||||
return parameterType -> {
|
||||
if (predicateProvider.apply(valueType).test(parameterType)) {
|
||||
return true;
|
||||
}
|
||||
if (predicateProvider.apply(extractElementType(valueType)).test(extractElementType(parameterType))) {
|
||||
return true;
|
||||
}
|
||||
return (predicateProvider.apply(valueType).test(extractElementType(parameterType)));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Predicate} for a parameter type that checks if its target value
|
||||
* is a {@link Class} and the value type is a {@link String}. This is a regular use
|
||||
* cases where a {@link Class} is defined in the bean definition as an FQN.
|
||||
* @param valueType the type of the value
|
||||
* @return a predicate to indicate a fallback match for a String to Class parameter
|
||||
*/
|
||||
private Predicate<ResolvableType> isStringForClassFallback(ResolvableType valueType) {
|
||||
return parameterType -> (valueType.isAssignableFrom(String.class)
|
||||
&& parameterType.isAssignableFrom(Class.class));
|
||||
}
|
||||
|
||||
private Predicate<ResolvableType> isSimpleConvertibleType(ResolvableType valueType) {
|
||||
return parameterType -> isSimpleConvertibleType(parameterType.toClass())
|
||||
&& isSimpleConvertibleType(valueType.toClass());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Class<?> getFactoryBeanClass(BeanDefinition beanDefinition) {
|
||||
if (beanDefinition instanceof RootBeanDefinition rbd) {
|
||||
if (rbd.hasBeanClass()) {
|
||||
Class<?> beanClass = rbd.getBeanClass();
|
||||
return FactoryBean.class.isAssignableFrom(beanClass) ? beanClass : null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Class<?> getBeanClass(BeanDefinition beanDefinition) {
|
||||
if (beanDefinition instanceof AbstractBeanDefinition abd) {
|
||||
return abd.hasBeanClass() ? abd.getBeanClass() : loadClass(abd.getBeanClassName());
|
||||
}
|
||||
return (beanDefinition.getBeanClassName() != null) ? loadClass(beanDefinition.getBeanClassName()) : null;
|
||||
}
|
||||
|
||||
private ResolvableType getBeanType(BeanDefinition beanDefinition) {
|
||||
ResolvableType resolvableType = beanDefinition.getResolvableType();
|
||||
if (resolvableType != ResolvableType.NONE) {
|
||||
return resolvableType;
|
||||
}
|
||||
if (beanDefinition instanceof RootBeanDefinition rbd) {
|
||||
if (rbd.hasBeanClass()) {
|
||||
return ResolvableType.forClass(rbd.getBeanClass());
|
||||
}
|
||||
}
|
||||
String beanClassName = beanDefinition.getBeanClassName();
|
||||
if (beanClassName != null) {
|
||||
return ResolvableType.forClass(loadClass(beanClassName));
|
||||
}
|
||||
throw new IllegalStateException("Failed to determine bean class of " + beanDefinition);
|
||||
}
|
||||
|
||||
private Class<?> loadClass(String beanClassName) {
|
||||
try {
|
||||
return ClassUtils.forName(beanClassName, this.classLoader);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new IllegalStateException("Failed to load class " + beanClassName);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private <T> T getField(BeanDefinition beanDefinition, String fieldName, Class<T> targetType) {
|
||||
Field field = ReflectionUtils.findField(RootBeanDefinition.class, fieldName);
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
return targetType.cast(ReflectionUtils.getField(field, beanDefinition));
|
||||
}
|
||||
|
||||
public static boolean isSimpleConvertibleType(Class<?> type) {
|
||||
return (type.isPrimitive() && type != void.class) ||
|
||||
type == Double.class || type == Float.class || type == Long.class ||
|
||||
type == Integer.class || type == Short.class || type == Character.class ||
|
||||
type == Byte.class || type == Boolean.class || type == String.class;
|
||||
}
|
||||
|
||||
|
||||
enum FallbackMode {
|
||||
|
||||
NONE,
|
||||
|
||||
ASSIGNABLE_ELEMENT,
|
||||
|
||||
TYPE_CONVERSION
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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.Field;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.aot.generator.ProtectedAccess;
|
||||
import org.springframework.aot.generator.ProtectedAccess.Options;
|
||||
import org.springframework.beans.factory.generator.config.BeanDefinitionRegistrar.BeanInstanceContext;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.CodeBlock.Builder;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Generate the necessary code to {@link #generateInstantiation(Executable)
|
||||
* create a bean instance} or {@link #generateInjection(Member, boolean)
|
||||
* inject dependencies}.
|
||||
* <p/>
|
||||
* The generator assumes a number of variables to be accessible:
|
||||
* <ul>
|
||||
* <li>{@code beanFactory}: the general {@code DefaultListableBeanFactory}</li>
|
||||
* <li>{@code instanceContext}: the {@link BeanInstanceContext} callback</li>
|
||||
* <li>{@code bean}: the variable that refers to the bean instance</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class InjectionGenerator {
|
||||
|
||||
private static final Options METHOD_INJECTION_OPTIONS = Options.defaults()
|
||||
.useReflection(member -> false).build();
|
||||
|
||||
private final BeanParameterGenerator parameterGenerator = new BeanParameterGenerator();
|
||||
|
||||
private final BeanFieldGenerator fieldGenerator = new BeanFieldGenerator();
|
||||
|
||||
|
||||
/**
|
||||
* Generate the necessary code to instantiate an object using the specified
|
||||
* {@link Executable}. The code is suitable to be assigned to a variable
|
||||
* or used as a {@literal return} statement.
|
||||
* @param creator the executable to invoke to create an instance of the
|
||||
* requested object
|
||||
* @return the code to instantiate an object using the specified executable
|
||||
*/
|
||||
public CodeBlock generateInstantiation(Executable creator) {
|
||||
if (creator instanceof Constructor<?> constructor) {
|
||||
return generateConstructorInstantiation(constructor);
|
||||
}
|
||||
if (creator instanceof Method method) {
|
||||
return generateMethodInstantiation(method);
|
||||
}
|
||||
throw new IllegalArgumentException("Could not handle creator " + creator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the code to inject a value resolved by {@link BeanInstanceContext}
|
||||
* in the specified {@link Member}.
|
||||
* @param member the field or method to inject
|
||||
* @param required whether the value is required
|
||||
* @return a statement that injects a value to the specified member
|
||||
* @see #getProtectedAccessInjectionOptions(Member)
|
||||
*/
|
||||
public CodeBlock generateInjection(Member member, boolean required) {
|
||||
if (member instanceof Method method) {
|
||||
return generateMethodInjection(method, required);
|
||||
}
|
||||
if (member instanceof Field field) {
|
||||
return generateFieldInjection(field, required);
|
||||
}
|
||||
throw new IllegalArgumentException("Could not handle member " + member);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Options} to use if protected access analysis is
|
||||
* required for the specified {@link Member}.
|
||||
* @param member the field or method to handle
|
||||
* @return the options to use to analyse protected access
|
||||
* @see ProtectedAccess
|
||||
*/
|
||||
public Options getProtectedAccessInjectionOptions(Member member) {
|
||||
if (member instanceof Method) {
|
||||
return METHOD_INJECTION_OPTIONS;
|
||||
}
|
||||
if (member instanceof Field) {
|
||||
return BeanFieldGenerator.FIELD_OPTIONS;
|
||||
}
|
||||
throw new IllegalArgumentException("Could not handle member " + member);
|
||||
}
|
||||
|
||||
private CodeBlock generateConstructorInstantiation(Constructor<?> creator) {
|
||||
Builder code = CodeBlock.builder();
|
||||
Class<?> declaringType = ClassUtils.getUserClass(creator.getDeclaringClass());
|
||||
boolean innerClass = isInnerClass(declaringType);
|
||||
Class<?>[] parameterTypes = Arrays.stream(creator.getParameters()).map(Parameter::getType)
|
||||
.toArray(Class<?>[]::new);
|
||||
// Shortcut for common case
|
||||
if (innerClass && parameterTypes.length == 1) {
|
||||
code.add("beanFactory.getBean($T.class).new $L()", declaringType.getEnclosingClass(),
|
||||
declaringType.getSimpleName());
|
||||
return code.build();
|
||||
}
|
||||
if (parameterTypes.length == 0) {
|
||||
code.add("new $T()", declaringType);
|
||||
return code.build();
|
||||
}
|
||||
boolean isAmbiguous = Arrays.stream(creator.getDeclaringClass().getDeclaredConstructors())
|
||||
.filter(constructor -> constructor.getParameterCount() == parameterTypes.length).count() > 1;
|
||||
code.add("instanceContext.create(beanFactory, (attributes) ->");
|
||||
List<CodeBlock> parameters = resolveParameters(creator.getParameters(), isAmbiguous);
|
||||
if (innerClass) { // Remove the implicit argument
|
||||
parameters.remove(0);
|
||||
}
|
||||
|
||||
code.add(" ");
|
||||
if (innerClass) {
|
||||
code.add("beanFactory.getBean($T.class).new $L(", declaringType.getEnclosingClass(),
|
||||
declaringType.getSimpleName());
|
||||
}
|
||||
else {
|
||||
code.add("new $T(", declaringType);
|
||||
}
|
||||
for (int i = 0; i < parameters.size(); i++) {
|
||||
code.add(parameters.get(i));
|
||||
if (i < parameters.size() - 1) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
code.add(")");
|
||||
code.add(")");
|
||||
return code.build();
|
||||
}
|
||||
|
||||
private static boolean isInnerClass(Class<?> type) {
|
||||
return type.isMemberClass() && !Modifier.isStatic(type.getModifiers());
|
||||
}
|
||||
|
||||
private CodeBlock generateMethodInstantiation(Method injectionPoint) {
|
||||
if (injectionPoint.getParameterCount() == 0) {
|
||||
Builder code = CodeBlock.builder();
|
||||
Class<?> declaringType = injectionPoint.getDeclaringClass();
|
||||
if (Modifier.isStatic(injectionPoint.getModifiers())) {
|
||||
code.add("$T", declaringType);
|
||||
}
|
||||
else {
|
||||
code.add("beanFactory.getBean($T.class)", declaringType);
|
||||
}
|
||||
code.add(".$L()", injectionPoint.getName());
|
||||
return code.build();
|
||||
}
|
||||
return generateMethodInvocation(injectionPoint, code -> code.add(".create(beanFactory, (attributes) ->"), true);
|
||||
}
|
||||
|
||||
private CodeBlock generateMethodInjection(Method injectionPoint, boolean required) {
|
||||
Consumer<Builder> attributesResolver = code -> {
|
||||
if (required) {
|
||||
code.add(".invoke(beanFactory, (attributes) ->");
|
||||
}
|
||||
else {
|
||||
code.add(".resolve(beanFactory, false).ifResolved((attributes) ->");
|
||||
}
|
||||
};
|
||||
return generateMethodInvocation(injectionPoint, attributesResolver, false);
|
||||
}
|
||||
|
||||
private CodeBlock generateMethodInvocation(Method injectionPoint, Consumer<Builder> attributesResolver, boolean instantiation) {
|
||||
Builder code = CodeBlock.builder();
|
||||
code.add("instanceContext");
|
||||
if (!instantiation) {
|
||||
code.add(".method($S, ", injectionPoint.getName());
|
||||
code.add(this.parameterGenerator.generateExecutableParameterTypes(injectionPoint));
|
||||
code.add(")\n").indent().indent();
|
||||
}
|
||||
attributesResolver.accept(code);
|
||||
Parameter[] methodParameters = injectionPoint.getParameters();
|
||||
boolean isAmbiguous = Arrays.stream(injectionPoint.getDeclaringClass().getDeclaredMethods())
|
||||
.filter(method -> method.getName().equals(injectionPoint.getName()) && method.getParameterCount() == methodParameters.length).count() > 1;
|
||||
List<CodeBlock> parameters = resolveParameters(methodParameters, isAmbiguous);
|
||||
code.add(" ");
|
||||
if (instantiation) {
|
||||
if (Modifier.isStatic(injectionPoint.getModifiers())) {
|
||||
code.add("$T", injectionPoint.getDeclaringClass());
|
||||
}
|
||||
else {
|
||||
code.add("beanFactory.getBean($T.class)", injectionPoint.getDeclaringClass());
|
||||
}
|
||||
}
|
||||
else {
|
||||
code.add("bean");
|
||||
}
|
||||
code.add(".$L(", injectionPoint.getName());
|
||||
code.add(CodeBlock.join(parameters, ", "));
|
||||
code.add(")");
|
||||
code.add(")");
|
||||
if (!instantiation) {
|
||||
code.unindent().unindent();
|
||||
}
|
||||
return code.build();
|
||||
}
|
||||
|
||||
CodeBlock generateFieldInjection(Field injectionPoint, boolean required) {
|
||||
Builder code = CodeBlock.builder();
|
||||
code.add("instanceContext.field($S", injectionPoint.getName());
|
||||
code.add(")\n").indent().indent();
|
||||
if (required) {
|
||||
code.add(".invoke(beanFactory, ");
|
||||
}
|
||||
else {
|
||||
code.add(".resolve(beanFactory, false).ifResolved(");
|
||||
}
|
||||
code.add(this.fieldGenerator.generateSetValue("bean", injectionPoint,
|
||||
CodeBlock.of("attributes.get(0)")).toLambda("(attributes) ->"));
|
||||
code.add(")").unindent().unindent();
|
||||
return code.build();
|
||||
}
|
||||
|
||||
private List<CodeBlock> resolveParameters(Parameter[] parameters, boolean shouldCast) {
|
||||
List<CodeBlock> parameterValues = new ArrayList<>();
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
if (shouldCast) {
|
||||
parameterValues.add(CodeBlock.of("attributes.get($L, $T.class)", i, parameters[i].getType()));
|
||||
}
|
||||
else {
|
||||
parameterValues.add(CodeBlock.of("attributes.get($L)", i));
|
||||
}
|
||||
}
|
||||
return parameterValues;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,42 +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.springframework.beans.factory.generator.config.BeanDefinitionRegistrar;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
|
||||
/**
|
||||
* A specialization of {@link BeanRegistrationContributionProvider} that handles
|
||||
* inner bean definitions.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class InnerBeanRegistrationBeanFactoryContribution extends BeanRegistrationBeanFactoryContribution {
|
||||
|
||||
InnerBeanRegistrationBeanFactoryContribution(String beanName, RootBeanDefinition beanDefinition,
|
||||
BeanInstantiationGenerator beanInstantiationGenerator,
|
||||
DefaultBeanRegistrationContributionProvider innerBeanRegistrationContributionProvider) {
|
||||
super(beanName, beanDefinition, beanInstantiationGenerator, innerBeanRegistrationContributionProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CodeBlock initializeBeanDefinitionRegistrar() {
|
||||
return CodeBlock.of("$T.inner(", BeanDefinitionRegistrar.class);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,401 +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.Executable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.FatalBeanException;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.MethodIntrospector;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link BeanDefinition} registration mechanism offering transparent
|
||||
* dependency resolution, as well as exception management.
|
||||
*
|
||||
* <p>Used by code generators and for internal use within the framework
|
||||
* only.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class BeanDefinitionRegistrar {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(BeanDefinitionRegistrar.class);
|
||||
|
||||
@Nullable
|
||||
private final String beanName;
|
||||
|
||||
private final Class<?> beanClass;
|
||||
|
||||
@Nullable
|
||||
private final ResolvableType beanType;
|
||||
|
||||
private final List<Consumer<RootBeanDefinition>> customizers;
|
||||
|
||||
@Nullable
|
||||
private Executable instanceCreator;
|
||||
|
||||
@Nullable
|
||||
private RootBeanDefinition beanDefinition;
|
||||
|
||||
|
||||
private BeanDefinitionRegistrar(@Nullable String beanName, Class<?> beanClass, @Nullable ResolvableType beanType) {
|
||||
this.beanName = beanName;
|
||||
this.beanClass = beanClass;
|
||||
this.beanType = beanType;
|
||||
this.customizers = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the registration of a bean with the specified name and type.
|
||||
* @param beanName the name of the bean
|
||||
* @param beanType the type of the bean
|
||||
* @return a registrar for the specified bean
|
||||
*/
|
||||
public static BeanDefinitionRegistrar of(String beanName, ResolvableType beanType) {
|
||||
return new BeanDefinitionRegistrar(beanName, beanType.toClass(), beanType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the registration of a bean with the specified name and type.
|
||||
* @param beanName the name of the bean
|
||||
* @param beanType the type of the bean
|
||||
* @return a registrar for the specified bean
|
||||
*/
|
||||
public static BeanDefinitionRegistrar of(String beanName, Class<?> beanType) {
|
||||
return new BeanDefinitionRegistrar(beanName, beanType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the registration of an inner bean with the specified type.
|
||||
* @param beanType the type of the inner bean
|
||||
* @return a registrar for the specified inner bean
|
||||
*/
|
||||
public static BeanDefinitionRegistrar inner(ResolvableType beanType) {
|
||||
return new BeanDefinitionRegistrar(null, beanType.toClass(), beanType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the registration of an inner bean with the specified type.
|
||||
* @param beanType the type of the inner bean
|
||||
* @return a registrar for the specified inner bean
|
||||
*/
|
||||
public static BeanDefinitionRegistrar inner(Class<?> beanType) {
|
||||
return new BeanDefinitionRegistrar(null, beanType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the factory method to use to instantiate the bean.
|
||||
* @param declaredType the {@link Method#getDeclaringClass() declared type}
|
||||
* of the factory method.
|
||||
* @param name the name of the method
|
||||
* @param parameterTypes the parameter types of the method
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @see RootBeanDefinition#getResolvedFactoryMethod()
|
||||
*/
|
||||
public BeanDefinitionRegistrar withFactoryMethod(Class<?> declaredType, String name, Class<?>... parameterTypes) {
|
||||
this.instanceCreator = getMethod(declaredType, name, parameterTypes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the constructor to use to instantiate the bean.
|
||||
* @param parameterTypes the parameter types of the constructor
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public BeanDefinitionRegistrar withConstructor(Class<?>... parameterTypes) {
|
||||
this.instanceCreator = getConstructor(this.beanClass, parameterTypes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify how the bean instance should be created and initialized, using
|
||||
* the {@link BeanInstanceContext} to resolve dependencies if necessary.
|
||||
* @param instanceContext the {@link BeanInstanceContext} to use
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public BeanDefinitionRegistrar instanceSupplier(ThrowableFunction<BeanInstanceContext, ?> instanceContext) {
|
||||
return customize(beanDefinition -> beanDefinition.setInstanceSupplier(() ->
|
||||
instanceContext.apply(createBeanInstanceContext())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify how the bean instance should be created and initialized.
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public BeanDefinitionRegistrar instanceSupplier(ThrowableSupplier<?> instanceSupplier) {
|
||||
return customize(beanDefinition -> beanDefinition.setInstanceSupplier(instanceSupplier));
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize the {@link RootBeanDefinition} using the specified consumer.
|
||||
* @param bd a consumer for the bean definition
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public BeanDefinitionRegistrar customize(ThrowableConsumer<RootBeanDefinition> bd) {
|
||||
this.customizers.add(bd);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the {@link RootBeanDefinition} defined by this instance to
|
||||
* the specified bean factory.
|
||||
* @param beanFactory the bean factory to use
|
||||
*/
|
||||
public void register(DefaultListableBeanFactory beanFactory) {
|
||||
BeanDefinition beanDefinition = toBeanDefinition();
|
||||
Assert.state(this.beanName != null, () -> "Bean name not set. Could not register " + beanDefinition);
|
||||
logger.debug(LogMessage.format("Register bean definition with name '%s'", this.beanName));
|
||||
beanFactory.registerBeanDefinition(this.beanName, beanDefinition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link RootBeanDefinition} defined by this instance.
|
||||
* @return the bean definition
|
||||
*/
|
||||
public RootBeanDefinition toBeanDefinition() {
|
||||
try {
|
||||
this.beanDefinition = createBeanDefinition();
|
||||
return this.beanDefinition;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new FatalBeanException("Failed to create bean definition for bean with name '" + this.beanName + "'", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private RootBeanDefinition createBeanDefinition() {
|
||||
RootBeanDefinition bd = (RootBeanDefinition) BeanDefinitionBuilder
|
||||
.rootBeanDefinition(this.beanClass).getBeanDefinition();
|
||||
if (this.beanType != null) {
|
||||
bd.setTargetType(this.beanType);
|
||||
}
|
||||
if (this.instanceCreator instanceof Method) {
|
||||
bd.setResolvedFactoryMethod((Method) this.instanceCreator);
|
||||
}
|
||||
this.customizers.forEach(customizer -> customizer.accept(bd));
|
||||
return bd;
|
||||
}
|
||||
|
||||
private BeanInstanceContext createBeanInstanceContext() {
|
||||
String resolvedBeanName = this.beanName != null ? this.beanName : createInnerBeanName();
|
||||
return new BeanInstanceContext(resolvedBeanName, this.beanClass);
|
||||
}
|
||||
|
||||
private String createInnerBeanName() {
|
||||
return "(inner bean)" + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR +
|
||||
(this.beanDefinition != null ? ObjectUtils.getIdentityHexString(this.beanDefinition) : 0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BeanDefinition resolveBeanDefinition(DefaultListableBeanFactory beanFactory) {
|
||||
return this.beanDefinition;
|
||||
}
|
||||
|
||||
private static Constructor<?> getConstructor(Class<?> beanType, Class<?>... parameterTypes) {
|
||||
try {
|
||||
return beanType.getDeclaredConstructor(parameterTypes);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
String message = String.format("No constructor with type(s) [%s] found on %s",
|
||||
toCommaSeparatedNames(parameterTypes), beanType.getName());
|
||||
throw new IllegalArgumentException(message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static Method getMethod(Class<?> declaredType, String methodName, Class<?>... parameterTypes) {
|
||||
Method method = ReflectionUtils.findMethod(declaredType, methodName, parameterTypes);
|
||||
if (method == null) {
|
||||
String message = String.format("No method '%s' with type(s) [%s] found on %s", methodName,
|
||||
toCommaSeparatedNames(parameterTypes), declaredType.getName());
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
return MethodIntrospector.selectInvocableMethod(method, declaredType);
|
||||
}
|
||||
|
||||
private static String toCommaSeparatedNames(Class<?>... parameterTypes) {
|
||||
return Arrays.stream(parameterTypes).map(Class::getName).collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback interface used by instance suppliers that need to resolve
|
||||
* dependencies for the {@link Executable} used to create the instance
|
||||
* as well as any {@link Member} that should be handled by the context.
|
||||
*/
|
||||
public final class BeanInstanceContext {
|
||||
|
||||
private final String beanName;
|
||||
|
||||
private final Class<?> beanType;
|
||||
|
||||
private BeanInstanceContext(String beanName, Class<?> beanType) {
|
||||
this.beanName = beanName;
|
||||
this.beanType = beanType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean instance using the {@code factory}.
|
||||
* @param beanFactory the bean factory to use
|
||||
* @param factory a function that returns the bean instance based on
|
||||
* the resolved attributes required by its instance creator
|
||||
* @param <T> the type of the bean
|
||||
* @return the bean instance
|
||||
*/
|
||||
public <T> T create(DefaultListableBeanFactory beanFactory, ThrowableFunction<InjectedElementAttributes, T> factory) {
|
||||
return resolveInstanceCreator(BeanDefinitionRegistrar.this.instanceCreator).create(beanFactory, factory);
|
||||
}
|
||||
|
||||
private InjectedElementResolver resolveInstanceCreator(@Nullable Executable instanceCreator) {
|
||||
if (instanceCreator instanceof Method) {
|
||||
return new InjectedConstructionResolver(instanceCreator, instanceCreator.getDeclaringClass(), this.beanName,
|
||||
BeanDefinitionRegistrar.this::resolveBeanDefinition);
|
||||
}
|
||||
if (instanceCreator instanceof Constructor) {
|
||||
return new InjectedConstructionResolver(instanceCreator, this.beanType, this.beanName,
|
||||
BeanDefinitionRegistrar.this::resolveBeanDefinition);
|
||||
}
|
||||
throw new IllegalStateException("No factory method or constructor is set");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link InjectedElementResolver} for the specified field.
|
||||
* @param name the name of the field
|
||||
* @return a resolved for the specified field
|
||||
*/
|
||||
public InjectedElementResolver field(String name) {
|
||||
return new InjectedFieldResolver(getField(name), this.beanName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link InjectedElementResolver} for the specified bean method.
|
||||
* @param name the name of the method on the target bean
|
||||
* @param parameterTypes the method parameter types
|
||||
* @return a resolved for the specified bean method
|
||||
*/
|
||||
public InjectedElementResolver method(String name, Class<?>... parameterTypes) {
|
||||
return new InjectedMethodResolver(getMethod(this.beanType, name, parameterTypes), this.beanType, this.beanName);
|
||||
}
|
||||
|
||||
private Field getField(String fieldName) {
|
||||
Field field = ReflectionUtils.findField(this.beanType, fieldName);
|
||||
Assert.notNull(field, () -> "No field '" + fieldName + "' found on " + this.beanType.getName());
|
||||
return field;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Consumer} that allows to invoke code that throws a checked exception.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @param <T> the type of the input to the operation
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ThrowableConsumer<T> extends Consumer<T> {
|
||||
|
||||
void acceptWithException(T t) throws Exception;
|
||||
|
||||
@Override
|
||||
default void accept(T t) {
|
||||
try {
|
||||
acceptWithException(t);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new RuntimeException(ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Function} that allows to invoke code that throws a checked exception.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @param <T> the type of the input to the function
|
||||
* @param <R> the type of the result of the function
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ThrowableFunction<T, R> extends Function<T, R> {
|
||||
|
||||
R applyWithException(T t) throws Exception;
|
||||
|
||||
@Override
|
||||
default R apply(T t) {
|
||||
try {
|
||||
return applyWithException(t);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new RuntimeException(ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Supplier} that allows to invoke code that throws a checked exception.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @param <T> the type of results supplied by this supplier
|
||||
*/
|
||||
public interface ThrowableSupplier<T> extends Supplier<T> {
|
||||
|
||||
T getWithException() throws Exception;
|
||||
|
||||
@Override
|
||||
default T get() {
|
||||
try {
|
||||
return getWithException();
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new RuntimeException(ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,175 +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.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.TypeConverter;
|
||||
import org.springframework.beans.factory.InjectionPoint;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionValueResolver;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.MethodParameter;
|
||||
|
||||
/**
|
||||
* An {@link InjectedElementResolver} for an {@link Executable} that creates
|
||||
* a bean instance.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
|
||||
class InjectedConstructionResolver implements InjectedElementResolver {
|
||||
|
||||
private final Executable executable;
|
||||
|
||||
private final Class<?> targetType;
|
||||
|
||||
private final String beanName;
|
||||
|
||||
private final Function<DefaultListableBeanFactory, BeanDefinition> beanDefinitionResolver;
|
||||
|
||||
InjectedConstructionResolver(Executable executable, Class<?> targetType, String beanName,
|
||||
Function<DefaultListableBeanFactory, BeanDefinition> beanDefinitionResolver) {
|
||||
this.executable = executable;
|
||||
this.targetType = targetType;
|
||||
this.beanName = beanName;
|
||||
this.beanDefinitionResolver = beanDefinitionResolver;
|
||||
}
|
||||
|
||||
|
||||
Executable getExecutable() {
|
||||
return this.executable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory, boolean required) {
|
||||
int argumentCount = this.executable.getParameterCount();
|
||||
List<Object> arguments = new ArrayList<>();
|
||||
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
|
||||
TypeConverter typeConverter = beanFactory.getTypeConverter();
|
||||
ConstructorArgumentValues argumentValues = resolveArgumentValues(beanFactory);
|
||||
for (int i = 0; i < argumentCount; i++) {
|
||||
MethodParameter methodParam = createMethodParameter(i);
|
||||
ValueHolder valueHolder = argumentValues.getIndexedArgumentValue(i, null);
|
||||
if (valueHolder != null) {
|
||||
if (valueHolder.isConverted()) {
|
||||
arguments.add(valueHolder.getConvertedValue());
|
||||
}
|
||||
else {
|
||||
Object userValue = beanFactory.getTypeConverter()
|
||||
.convertIfNecessary(valueHolder.getValue(), methodParam.getParameterType());
|
||||
arguments.add(userValue);
|
||||
}
|
||||
}
|
||||
else {
|
||||
DependencyDescriptor depDescriptor = new DependencyDescriptor(methodParam, true);
|
||||
depDescriptor.setContainingClass(this.targetType);
|
||||
try {
|
||||
Object arg = resolveDependency(() -> beanFactory.resolveDependency(
|
||||
depDescriptor, this.beanName, autowiredBeans, typeConverter), methodParam.getParameterType());
|
||||
arguments.add(arg);
|
||||
}
|
||||
catch (BeansException ex) {
|
||||
throw new UnsatisfiedDependencyException(null, this.beanName, new InjectionPoint(methodParam), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new InjectedElementAttributes(arguments);
|
||||
}
|
||||
|
||||
private Object resolveDependency(Supplier<Object> resolvedDependency, Class<?> dependencyType) {
|
||||
try {
|
||||
return resolvedDependency.get();
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
// Single constructor or factory method -> let's return an empty array/collection
|
||||
// for e.g. a vararg or a non-null List/Set/Map parameter.
|
||||
if (dependencyType.isArray()) {
|
||||
return Array.newInstance(dependencyType.getComponentType(), 0);
|
||||
}
|
||||
else if (CollectionFactory.isApproximableCollectionType(dependencyType)) {
|
||||
return CollectionFactory.createCollection(dependencyType, 0);
|
||||
}
|
||||
else if (CollectionFactory.isApproximableMapType(dependencyType)) {
|
||||
return CollectionFactory.createMap(dependencyType, 0);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private ConstructorArgumentValues resolveArgumentValues(DefaultListableBeanFactory beanFactory) {
|
||||
ConstructorArgumentValues resolvedValues = new ConstructorArgumentValues();
|
||||
BeanDefinition beanDefinition = this.beanDefinitionResolver.apply(beanFactory);
|
||||
if (beanDefinition == null || !beanDefinition.hasConstructorArgumentValues()) {
|
||||
return resolvedValues;
|
||||
}
|
||||
ConstructorArgumentValues argumentValues = beanDefinition.getConstructorArgumentValues();
|
||||
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(beanFactory,
|
||||
this.beanName, beanDefinition);
|
||||
for (Map.Entry<Integer, ValueHolder> entry : argumentValues.getIndexedArgumentValues().entrySet()) {
|
||||
int index = entry.getKey();
|
||||
ValueHolder valueHolder = entry.getValue();
|
||||
if (valueHolder.isConverted()) {
|
||||
resolvedValues.addIndexedArgumentValue(index, valueHolder);
|
||||
}
|
||||
else {
|
||||
Object resolvedValue =
|
||||
valueResolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue());
|
||||
ValueHolder resolvedValueHolder =
|
||||
new ValueHolder(resolvedValue, valueHolder.getType(), valueHolder.getName());
|
||||
resolvedValueHolder.setSource(valueHolder);
|
||||
resolvedValues.addIndexedArgumentValue(index, resolvedValueHolder);
|
||||
}
|
||||
}
|
||||
return resolvedValues;
|
||||
}
|
||||
|
||||
private MethodParameter createMethodParameter(int index) {
|
||||
if (this.executable instanceof Constructor) {
|
||||
return new MethodParameter((Constructor<?>) this.executable, index);
|
||||
}
|
||||
else {
|
||||
return new MethodParameter((Method) this.executable, index);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringJoiner(", ", InjectedConstructionResolver.class.getSimpleName() + "[", "]")
|
||||
.add("executable=" + this.executable)
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,93 +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.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Resolved attributes of an injected element.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public class InjectedElementAttributes {
|
||||
|
||||
@Nullable
|
||||
private final List<Object> attributes;
|
||||
|
||||
|
||||
InjectedElementAttributes(@Nullable List<Object> attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specify if the attributes have been resolved.
|
||||
* @return the resolution of the injection
|
||||
*/
|
||||
public boolean isResolved() {
|
||||
return (this.attributes != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the specified {@linkplain Runnable task} only if this instance is
|
||||
* {@link #isResolved() resolved}.
|
||||
* @param task the task to invoke if attributes are available
|
||||
*/
|
||||
public void ifResolved(Runnable task) {
|
||||
if (isResolved()) {
|
||||
task.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the specified {@link Consumer} with the resolved attributes.
|
||||
* @param attributes the consumer to invoke if this instance is resolved
|
||||
*/
|
||||
public void ifResolved(BeanDefinitionRegistrar.ThrowableConsumer<InjectedElementAttributes> attributes) {
|
||||
ifResolved(() -> attributes.accept(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resolved attribute at the specified index.
|
||||
* @param index the attribute index
|
||||
* @param <T> the type of the attribute
|
||||
* @return the attribute
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(int index) {
|
||||
Assert.notNull(this.attributes, "Attributes must not be null");
|
||||
return (T) this.attributes.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resolved attribute at the specified index.
|
||||
* @param index the attribute index
|
||||
* @param type the attribute type
|
||||
* @param <T> the type of the attribute
|
||||
* @return the attribute
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(int index, Class<T> type) {
|
||||
Assert.notNull(this.attributes, "Attributes must not be null");
|
||||
return (T) this.attributes.get(index);
|
||||
}
|
||||
}
|
|
@ -1,75 +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 org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
|
||||
/**
|
||||
* Resolve the attributes of an injected element such as a {@code Constructor}
|
||||
* or a factory {@code Method}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface InjectedElementResolver {
|
||||
|
||||
/**
|
||||
* Resolve the attributes using the specified bean factory.
|
||||
* @param beanFactory the bean factory to use
|
||||
* @return the resolved attributes
|
||||
*/
|
||||
default InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory) {
|
||||
return resolve(beanFactory, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the attributes using the specified bean factory.
|
||||
* @param beanFactory the bean factory to use
|
||||
* @param required whether the injection point is mandatory
|
||||
* @return the resolved attributes
|
||||
*/
|
||||
InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory, boolean required);
|
||||
|
||||
/**
|
||||
* Invoke the specified consumer with the resolved
|
||||
* {@link InjectedElementAttributes attributes}.
|
||||
* @param beanFactory the bean factory to use to resolve the attributes
|
||||
* @param attributes a consumer of the resolved attributes
|
||||
*/
|
||||
default void invoke(DefaultListableBeanFactory beanFactory,
|
||||
BeanDefinitionRegistrar.ThrowableConsumer<InjectedElementAttributes> attributes) {
|
||||
|
||||
InjectedElementAttributes elements = resolve(beanFactory);
|
||||
attributes.accept(elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance based on the resolved
|
||||
* {@link InjectedElementAttributes attributes}.
|
||||
* @param beanFactory the bean factory to use to resolve the attributes
|
||||
* @param factory a factory to create the instance based on the resolved attributes
|
||||
* @param <T> the type of the instance
|
||||
* @return a new instance
|
||||
*/
|
||||
default <T> T create(DefaultListableBeanFactory beanFactory,
|
||||
BeanDefinitionRegistrar.ThrowableFunction<InjectedElementAttributes, T> factory) {
|
||||
|
||||
InjectedElementAttributes attributes = resolve(beanFactory);
|
||||
return factory.apply(attributes);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,71 +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 java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.TypeConverter;
|
||||
import org.springframework.beans.factory.InjectionPoint;
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
|
||||
/**
|
||||
* An {@link InjectedElementResolver} for a {@link Field}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class InjectedFieldResolver implements InjectedElementResolver {
|
||||
|
||||
private final Field field;
|
||||
|
||||
private final String beanName;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
* @param field the field to handle
|
||||
* @param beanName the name of the bean, or {@code null}
|
||||
*/
|
||||
InjectedFieldResolver(Field field, String beanName) {
|
||||
this.field = field;
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory, boolean required) {
|
||||
DependencyDescriptor desc = new DependencyDescriptor(this.field, required);
|
||||
desc.setContainingClass(this.field.getType());
|
||||
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
|
||||
TypeConverter typeConverter = beanFactory.getTypeConverter();
|
||||
try {
|
||||
Object value = beanFactory.resolveDependency(desc, this.beanName, autowiredBeanNames, typeConverter);
|
||||
if (value == null && !required) {
|
||||
return new InjectedElementAttributes(null);
|
||||
}
|
||||
return new InjectedElementAttributes(Collections.singletonList(value));
|
||||
}
|
||||
catch (BeansException ex) {
|
||||
throw new UnsatisfiedDependencyException(null, this.beanName, new InjectionPoint(this.field), ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,86 +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 java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.TypeConverter;
|
||||
import org.springframework.beans.factory.InjectionPoint;
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.core.MethodParameter;
|
||||
|
||||
/**
|
||||
* An {@link InjectedElementResolver} for a {@link Method}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class InjectedMethodResolver implements InjectedElementResolver {
|
||||
|
||||
private final Method method;
|
||||
|
||||
private final Class<?> target;
|
||||
|
||||
private final String beanName;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
* @param method the method to handle
|
||||
* @param target the type on which the method is declared
|
||||
* @param beanName the name of the bean, or {@code null}
|
||||
*/
|
||||
InjectedMethodResolver(Method method, Class<?> target, String beanName) {
|
||||
this.method = method;
|
||||
this.target = target;
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory, boolean required) {
|
||||
int argumentCount = this.method.getParameterCount();
|
||||
List<Object> arguments = new ArrayList<>();
|
||||
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
|
||||
TypeConverter typeConverter = beanFactory.getTypeConverter();
|
||||
for (int i = 0; i < argumentCount; i++) {
|
||||
MethodParameter methodParam = new MethodParameter(this.method, i);
|
||||
DependencyDescriptor depDescriptor = new DependencyDescriptor(methodParam, required);
|
||||
depDescriptor.setContainingClass(this.target);
|
||||
try {
|
||||
Object arg = beanFactory.resolveDependency(depDescriptor, this.beanName, autowiredBeans, typeConverter);
|
||||
if (arg == null && !required) {
|
||||
arguments = null;
|
||||
break;
|
||||
}
|
||||
arguments.add(arg);
|
||||
}
|
||||
catch (BeansException ex) {
|
||||
throw new UnsatisfiedDependencyException(null, this.beanName, new InjectionPoint(methodParam), ex);
|
||||
}
|
||||
}
|
||||
return new InjectedElementAttributes(arguments);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
/**
|
||||
* Classes used in generated code to ease bean registration.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.beans.factory.generator.config;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
|
@ -1,9 +0,0 @@
|
|||
/**
|
||||
* Support for generating code that represents the state of a bean factory.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.beans.factory.generator;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -51,9 +51,6 @@ import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
|||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.SingletonBeanRegistry;
|
||||
import org.springframework.beans.factory.generator.AotContributingBeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.generator.BeanFactoryContribution;
|
||||
import org.springframework.beans.factory.generator.BeanFactoryInitialization;
|
||||
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
|
||||
import org.springframework.beans.factory.parsing.PassThroughSourceExtractor;
|
||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||
|
@ -81,7 +78,6 @@ import org.springframework.core.type.MethodMetadata;
|
|||
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.CodeBlock.Builder;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.ParameterizedTypeName;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
@ -108,8 +104,8 @@ import org.springframework.util.ClassUtils;
|
|||
* @since 3.0
|
||||
*/
|
||||
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
|
||||
AotContributingBeanFactoryPostProcessor, BeanFactoryInitializationAotProcessor, PriorityOrdered,
|
||||
ResourceLoaderAware, ApplicationStartupAware, BeanClassLoaderAware, EnvironmentAware {
|
||||
BeanFactoryInitializationAotProcessor, PriorityOrdered, ResourceLoaderAware, ApplicationStartupAware,
|
||||
BeanClassLoaderAware, EnvironmentAware {
|
||||
|
||||
/**
|
||||
* A {@code BeanNameGenerator} using fully qualified class names as default bean names.
|
||||
|
@ -289,15 +285,10 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
|||
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanFactoryContribution contribute(ConfigurableListableBeanFactory beanFactory) {
|
||||
return (beanFactory.containsBean(IMPORT_REGISTRY_BEAN_NAME)
|
||||
? new ImportAwareBeanFactoryConfiguration(beanFactory) : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanFactoryInitializationAotContribution processAheadOfTime(
|
||||
ConfigurableListableBeanFactory beanFactory) {
|
||||
|
||||
return (beanFactory.containsBean(IMPORT_REGISTRY_BEAN_NAME)
|
||||
? new AotContribution(beanFactory) : null);
|
||||
}
|
||||
|
@ -518,56 +509,6 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
|||
}
|
||||
}
|
||||
|
||||
private static final class ImportAwareBeanFactoryConfiguration implements BeanFactoryContribution {
|
||||
|
||||
private final ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
private ImportAwareBeanFactoryConfiguration(ConfigurableListableBeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void applyTo(BeanFactoryInitialization initialization) {
|
||||
Map<String, String> mappings = buildImportAwareMappings();
|
||||
if (!mappings.isEmpty()) {
|
||||
MethodSpec method = initialization.generatedTypeContext().getMainGeneratedType()
|
||||
.addMethod(beanPostProcessorMethod(mappings));
|
||||
initialization.contribute(code -> code.addStatement("beanFactory.addBeanPostProcessor($N())", method));
|
||||
ResourceHints resourceHints = initialization.generatedTypeContext().runtimeHints().resources();
|
||||
mappings.forEach((target, importedFrom) -> resourceHints.registerType(
|
||||
TypeReference.of(importedFrom)));
|
||||
}
|
||||
}
|
||||
|
||||
private MethodSpec.Builder beanPostProcessorMethod(Map<String, String> mappings) {
|
||||
Builder code = CodeBlock.builder();
|
||||
code.addStatement("$T mappings = new $T<>()", ParameterizedTypeName.get(
|
||||
Map.class, String.class, String.class), HashMap.class);
|
||||
mappings.forEach((key, value) -> code.addStatement("mappings.put($S, $S)", key, value));
|
||||
code.addStatement("return new $T($L)", ImportAwareAotBeanPostProcessor.class, "mappings");
|
||||
return MethodSpec.methodBuilder("createImportAwareBeanPostProcessor")
|
||||
.returns(ImportAwareAotBeanPostProcessor.class)
|
||||
.addModifiers(Modifier.PRIVATE).addCode(code.build());
|
||||
}
|
||||
|
||||
private Map<String, String> buildImportAwareMappings() {
|
||||
ImportRegistry ir = this.beanFactory.getBean(IMPORT_REGISTRY_BEAN_NAME, ImportRegistry.class);
|
||||
Map<String, String> mappings = new LinkedHashMap<>();
|
||||
for (String name : this.beanFactory.getBeanDefinitionNames()) {
|
||||
Class<?> beanType = this.beanFactory.getType(name);
|
||||
if (beanType != null && ImportAware.class.isAssignableFrom(beanType)) {
|
||||
String type = ClassUtils.getUserClass(beanType).getName();
|
||||
AnnotationMetadata importingClassMetadata = ir.getImportingClassFor(type);
|
||||
if (importingClassMetadata != null) {
|
||||
mappings.put(type, importingClassMetadata.getClassName());
|
||||
}
|
||||
}
|
||||
}
|
||||
return mappings;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class AotContribution implements BeanFactoryInitializationAotContribution {
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
|||
|
||||
/**
|
||||
* Indicates that one or more {@link RuntimeHintsRegistrar} implementations should be processed.
|
||||
* <p>Unlike declaring {@link RuntimeHintsRegistrar} as {@code spring.factories},
|
||||
* <p>Unlike declaring {@link RuntimeHintsRegistrar} as {@code spring/aot.factories},
|
||||
* {@code @ImportRuntimeHints} allows for more flexible use cases where registrations are only
|
||||
* processed if the annotated configuration class or bean method is considered by the
|
||||
* application context.
|
||||
|
|
|
@ -1,175 +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.context.generator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aot.generator.GeneratedType;
|
||||
import org.springframework.aot.generator.GeneratedTypeContext;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.generator.AotContributingBeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.generator.AotContributingBeanPostProcessor;
|
||||
import org.springframework.beans.factory.generator.BeanDefinitionsContribution;
|
||||
import org.springframework.beans.factory.generator.BeanFactoryContribution;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.OrderComparator;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.ParameterizedTypeName;
|
||||
|
||||
/**
|
||||
* Process an {@link ApplicationContext} and its {@link BeanFactory} to generate
|
||||
* code that represents the state of the bean factory, as well as the necessary
|
||||
* hints that can be used at runtime in a constrained environment.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public class ApplicationContextAotGenerator {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ApplicationContextAotGenerator.class);
|
||||
|
||||
/**
|
||||
* Refresh the specified {@link GenericApplicationContext} and generate the
|
||||
* necessary code to restore the state of its {@link BeanFactory}, using the
|
||||
* specified {@link GeneratedTypeContext}.
|
||||
* @param applicationContext the application context to handle
|
||||
* @param generationContext the generation context to use
|
||||
*/
|
||||
public void generateApplicationContext(GenericApplicationContext applicationContext,
|
||||
GeneratedTypeContext generationContext) {
|
||||
applicationContext.refreshForAotProcessing();
|
||||
|
||||
DefaultListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory();
|
||||
List<BeanFactoryContribution> contributions = resolveBeanFactoryContributions(beanFactory);
|
||||
|
||||
filterBeanFactory(contributions, beanFactory);
|
||||
ApplicationContextInitialization applicationContextInitialization = new ApplicationContextInitialization(generationContext);
|
||||
applyContributions(contributions, applicationContextInitialization);
|
||||
|
||||
GeneratedType mainGeneratedType = generationContext.getMainGeneratedType();
|
||||
mainGeneratedType.customizeType(type -> type.addSuperinterface(ParameterizedTypeName.get(
|
||||
ApplicationContextInitializer.class, GenericApplicationContext.class)));
|
||||
mainGeneratedType.addMethod(initializeMethod(applicationContextInitialization.toCodeBlock()));
|
||||
}
|
||||
|
||||
private MethodSpec.Builder initializeMethod(CodeBlock methodBody) {
|
||||
MethodSpec.Builder method = MethodSpec.methodBuilder("initialize").addModifiers(Modifier.PUBLIC)
|
||||
.addParameter(GenericApplicationContext.class, "context").addAnnotation(Override.class);
|
||||
method.addCode(methodBody);
|
||||
return method;
|
||||
}
|
||||
|
||||
private void filterBeanFactory(List<BeanFactoryContribution> contributions, DefaultListableBeanFactory beanFactory) {
|
||||
BiPredicate<String, BeanDefinition> filter = Stream.concat(Stream.of(aotContributingExcludeFilter()),
|
||||
contributions.stream().map(BeanFactoryContribution::getBeanDefinitionExcludeFilter))
|
||||
.filter(Objects::nonNull).reduce((n, d) -> false, BiPredicate::or);
|
||||
for (String beanName : beanFactory.getBeanDefinitionNames()) {
|
||||
BeanDefinition bd = beanFactory.getMergedBeanDefinition(beanName);
|
||||
if (filter.test(beanName, bd)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Filtering out bean with name" + beanName + ": " + bd);
|
||||
}
|
||||
beanFactory.removeBeanDefinition(beanName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: is this right?
|
||||
private BiPredicate<String, BeanDefinition> aotContributingExcludeFilter() {
|
||||
return (beanName, beanDefinition) -> {
|
||||
Class<?> type = beanDefinition.getResolvableType().toClass();
|
||||
return AotContributingBeanFactoryPostProcessor.class.isAssignableFrom(type) ||
|
||||
AotContributingBeanPostProcessor.class.isAssignableFrom(type);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private void applyContributions(List<BeanFactoryContribution> contributions,
|
||||
ApplicationContextInitialization initialization) {
|
||||
for (BeanFactoryContribution contribution : contributions) {
|
||||
contribution.applyTo(initialization);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the {@link BeanFactoryContribution} available in the specified
|
||||
* bean factory. Infrastructure is contributed first, and bean definitions
|
||||
* registration last.
|
||||
* @param beanFactory the bean factory to process
|
||||
* @return the contribution to apply
|
||||
* @see InfrastructureContribution
|
||||
* @see BeanDefinitionsContribution
|
||||
*/
|
||||
private List<BeanFactoryContribution> resolveBeanFactoryContributions(DefaultListableBeanFactory beanFactory) {
|
||||
List<BeanFactoryContribution> contributions = new ArrayList<>();
|
||||
contributions.add(new InfrastructureContribution());
|
||||
List<AotContributingBeanFactoryPostProcessor> postProcessors = getAotContributingBeanFactoryPostProcessors(beanFactory);
|
||||
for (AotContributingBeanFactoryPostProcessor postProcessor : postProcessors) {
|
||||
BeanFactoryContribution contribution = postProcessor.contribute(beanFactory);
|
||||
if (contribution != null) {
|
||||
contributions.add(contribution);
|
||||
}
|
||||
}
|
||||
contributions.add(new BeanDefinitionsContribution(beanFactory));
|
||||
return contributions;
|
||||
}
|
||||
|
||||
private static List<AotContributingBeanFactoryPostProcessor> getAotContributingBeanFactoryPostProcessors(DefaultListableBeanFactory beanFactory) {
|
||||
String[] postProcessorNames = beanFactory.getBeanNamesForType(AotContributingBeanFactoryPostProcessor.class, true, false);
|
||||
List<AotContributingBeanFactoryPostProcessor> postProcessors = new ArrayList<>();
|
||||
for (String ppName : postProcessorNames) {
|
||||
postProcessors.add(beanFactory.getBean(ppName, AotContributingBeanFactoryPostProcessor.class));
|
||||
}
|
||||
postProcessors.addAll(SpringFactoriesLoader.loadFactories(AotContributingBeanFactoryPostProcessor.class,
|
||||
beanFactory.getBeanClassLoader()));
|
||||
sortPostProcessors(postProcessors, beanFactory);
|
||||
return postProcessors;
|
||||
}
|
||||
|
||||
private static void sortPostProcessors(List<?> postProcessors, ConfigurableListableBeanFactory beanFactory) {
|
||||
// Nothing to sort?
|
||||
if (postProcessors.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
Comparator<Object> comparatorToUse = null;
|
||||
if (beanFactory instanceof DefaultListableBeanFactory) {
|
||||
comparatorToUse = ((DefaultListableBeanFactory) beanFactory).getDependencyComparator();
|
||||
}
|
||||
if (comparatorToUse == null) {
|
||||
comparatorToUse = OrderComparator.INSTANCE;
|
||||
}
|
||||
postProcessors.sort(comparatorToUse);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,35 +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.context.generator;
|
||||
|
||||
import org.springframework.aot.generator.GeneratedTypeContext;
|
||||
import org.springframework.beans.factory.generator.BeanFactoryInitialization;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* The initialization of an {@link ApplicationContext}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 6.0
|
||||
*/
|
||||
public class ApplicationContextInitialization extends BeanFactoryInitialization {
|
||||
|
||||
public ApplicationContextInitialization(GeneratedTypeContext generatedTypeContext) {
|
||||
super(generatedTypeContext);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,43 +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.context.generator;
|
||||
|
||||
import org.springframework.beans.factory.generator.BeanFactoryContribution;
|
||||
import org.springframework.beans.factory.generator.BeanFactoryInitialization;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
|
||||
|
||||
/**
|
||||
* A {@link BeanFactoryContribution} that configures the low-level
|
||||
* infrastructure necessary to process an AOT context.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class InfrastructureContribution implements BeanFactoryContribution {
|
||||
|
||||
@Override
|
||||
public void applyTo(BeanFactoryInitialization initialization) {
|
||||
initialization.contribute(code -> {
|
||||
code.add("// infrastructure\n");
|
||||
code.addStatement("$T beanFactory = context.getDefaultListableBeanFactory()",
|
||||
DefaultListableBeanFactory.class);
|
||||
code.addStatement("beanFactory.setAutowireCandidateResolver(new $T())",
|
||||
ContextAnnotationAutowireCandidateResolver.class);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -1,92 +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.context.generator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.generator.AotContributingBeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.generator.BeanFactoryContribution;
|
||||
import org.springframework.beans.factory.generator.BeanFactoryInitialization;
|
||||
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* AOT {@code BeanFactoryPostProcessor} that processes {@link RuntimeHintsRegistrar} implementations
|
||||
* declared as {@code spring.factories} or using {@link ImportRuntimeHints @ImportRuntimeHints} annotated
|
||||
* configuration classes or bean methods.
|
||||
* <p>This processor is registered by default in the {@link ApplicationContextAotGenerator} as it is
|
||||
* only useful in an AOT context.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @see ApplicationContextAotGenerator
|
||||
*/
|
||||
class RuntimeHintsPostProcessor implements AotContributingBeanFactoryPostProcessor {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(RuntimeHintsPostProcessor.class);
|
||||
|
||||
@Override
|
||||
public BeanFactoryContribution contribute(ConfigurableListableBeanFactory beanFactory) {
|
||||
ClassLoader beanClassLoader = beanFactory.getBeanClassLoader();
|
||||
List<RuntimeHintsRegistrar> registrars =
|
||||
new ArrayList<>(SpringFactoriesLoader.loadFactories(RuntimeHintsRegistrar.class, beanClassLoader));
|
||||
Arrays.stream(beanFactory.getBeanNamesForAnnotation(ImportRuntimeHints.class)).forEach(beanDefinitionName -> {
|
||||
ImportRuntimeHints importRuntimeHints = beanFactory.findAnnotationOnBean(beanDefinitionName, ImportRuntimeHints.class);
|
||||
if (importRuntimeHints != null) {
|
||||
Class<? extends RuntimeHintsRegistrar>[] registrarClasses = importRuntimeHints.value();
|
||||
for (Class<? extends RuntimeHintsRegistrar> registrarClass : registrarClasses) {
|
||||
logger.trace(LogMessage.format("Loaded [%s] registrar from annotated bean [%s]", registrarClass.getCanonicalName(), beanDefinitionName));
|
||||
RuntimeHintsRegistrar registrar = BeanUtils.instantiateClass(registrarClass);
|
||||
registrars.add(registrar);
|
||||
}
|
||||
}
|
||||
});
|
||||
return new RuntimeHintsRegistrarContribution(registrars, beanClassLoader);
|
||||
}
|
||||
|
||||
|
||||
static class RuntimeHintsRegistrarContribution implements BeanFactoryContribution {
|
||||
|
||||
private final List<RuntimeHintsRegistrar> registrars;
|
||||
|
||||
@Nullable
|
||||
private final ClassLoader beanClassLoader;
|
||||
|
||||
RuntimeHintsRegistrarContribution(List<RuntimeHintsRegistrar> registrars, @Nullable ClassLoader beanClassLoader) {
|
||||
this.registrars = registrars;
|
||||
this.beanClassLoader = beanClassLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(BeanFactoryInitialization initialization) {
|
||||
this.registrars.forEach(registrar -> {
|
||||
logger.trace(LogMessage.format("Processing RuntimeHints contribution from [%s]", registrar.getClass().getCanonicalName()));
|
||||
registrar.registerHints(initialization.generatedTypeContext().runtimeHints(), this.beanClassLoader);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
/**
|
||||
* Support for generating code that represents the state of an application
|
||||
* context.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.context.generator;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
|
@ -1,115 +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.context.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generator.DefaultGeneratedTypeContext;
|
||||
import org.springframework.aot.generator.GeneratedType;
|
||||
import org.springframework.aot.generator.GeneratedTypeContext;
|
||||
import org.springframework.beans.factory.generator.BeanFactoryContribution;
|
||||
import org.springframework.beans.factory.generator.BeanFactoryInitialization;
|
||||
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.context.testfixture.context.generator.annotation.ImportConfiguration;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.javapoet.support.CodeSnippet;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@code ImportAwareBeanFactoryConfiguration}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class ImportAwareBeanFactoryContributionTests {
|
||||
|
||||
@Test
|
||||
void contributeWithImportAwareConfigurationRegistersBeanPostProcessor() {
|
||||
BeanFactoryContribution contribution = createContribution(ImportConfiguration.class);
|
||||
assertThat(contribution).isNotNull();
|
||||
BeanFactoryInitialization initialization = new BeanFactoryInitialization(createGenerationContext());
|
||||
contribution.applyTo(initialization);
|
||||
assertThat(CodeSnippet.of(initialization.toCodeBlock()).getSnippet()).isEqualTo("""
|
||||
beanFactory.addBeanPostProcessor(createImportAwareBeanPostProcessor());
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributeWithImportAwareConfigurationCreateMappingsMethod() {
|
||||
BeanFactoryContribution contribution = createContribution(ImportConfiguration.class);
|
||||
assertThat(contribution).isNotNull();
|
||||
GeneratedTypeContext generationContext = createGenerationContext();
|
||||
contribution.applyTo(new BeanFactoryInitialization(generationContext));
|
||||
assertThat(codeOf(generationContext.getMainGeneratedType())).contains("""
|
||||
private ImportAwareAotBeanPostProcessor createImportAwareBeanPostProcessor() {
|
||||
Map<String, String> mappings = new HashMap<>();
|
||||
mappings.put("org.springframework.context.testfixture.context.generator.annotation.ImportAwareConfiguration", "org.springframework.context.testfixture.context.generator.annotation.ImportConfiguration");
|
||||
return new ImportAwareAotBeanPostProcessor(mappings);
|
||||
}
|
||||
""");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributeWithImportAwareConfigurationRegisterBytecodeResourceHint() {
|
||||
BeanFactoryContribution contribution = createContribution(ImportConfiguration.class);
|
||||
assertThat(contribution).isNotNull();
|
||||
GeneratedTypeContext generationContext = createGenerationContext();
|
||||
contribution.applyTo(new BeanFactoryInitialization(generationContext));
|
||||
assertThat(generationContext.runtimeHints().resources().resourcePatterns())
|
||||
.singleElement().satisfies(resourceHint -> assertThat(resourceHint.getIncludes()).containsOnly(
|
||||
"org/springframework/context/testfixture/context/generator/annotation/ImportConfiguration.class"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributeWithNoImportAwareConfigurationReturnsNull() {
|
||||
assertThat(createContribution(SimpleConfiguration.class)).isNull();
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private BeanFactoryContribution createContribution(Class<?> type) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerBeanDefinition("configuration", new RootBeanDefinition(type));
|
||||
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
|
||||
pp.postProcessBeanFactory(beanFactory);
|
||||
return pp.contribute(beanFactory);
|
||||
}
|
||||
|
||||
private GeneratedTypeContext createGenerationContext() {
|
||||
return new DefaultGeneratedTypeContext("com.example", packageName ->
|
||||
GeneratedType.of(ClassName.get(packageName, "Test")));
|
||||
}
|
||||
|
||||
private String codeOf(GeneratedType type) {
|
||||
try {
|
||||
StringWriter out = new StringWriter();
|
||||
type.toJavaFile().writeTo(out);
|
||||
return out.toString();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,410 +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.context.generator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generator.DefaultGeneratedTypeContext;
|
||||
import org.springframework.aot.generator.GeneratedType;
|
||||
import org.springframework.aot.generator.GeneratedTypeContext;
|
||||
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.annotation.AutowiredAnnotationBeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.generator.AotContributingBeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.generator.AotContributingBeanPostProcessor;
|
||||
import org.springframework.beans.factory.generator.BeanFactoryContribution;
|
||||
import org.springframework.beans.factory.generator.BeanFactoryInitialization;
|
||||
import org.springframework.beans.factory.generator.BeanInstantiationContribution;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.annotation.AnnotationConfigUtils;
|
||||
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.context.testfixture.context.generator.SimpleComponent;
|
||||
import org.springframework.context.testfixture.context.generator.annotation.AutowiredComponent;
|
||||
import org.springframework.context.testfixture.context.generator.annotation.InitDestroyComponent;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.JavaFile;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link ApplicationContextAotGenerator}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ApplicationContextAotGeneratorTests {
|
||||
|
||||
private static final ClassName MAIN_GENERATED_TYPE = ClassName.get("com.example", "Test");
|
||||
|
||||
@Test
|
||||
void generateApplicationContextWithSimpleBean() {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.registerBeanDefinition("test", new RootBeanDefinition(SimpleComponent.class));
|
||||
compile(context, toFreshApplicationContext(GenericApplicationContext::new, aotContext -> {
|
||||
assertThat(aotContext.getBeanDefinitionNames()).containsOnly("test");
|
||||
assertThat(aotContext.getBean("test")).isInstanceOf(SimpleComponent.class);
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateApplicationContextWithAutowiring() {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.registerBeanDefinition(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME,
|
||||
BeanDefinitionBuilder.rootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
|
||||
context.registerBeanDefinition("autowiredComponent", new RootBeanDefinition(AutowiredComponent.class));
|
||||
context.registerBeanDefinition("number", BeanDefinitionBuilder.rootBeanDefinition(Integer.class, "valueOf")
|
||||
.addConstructorArgValue("42").getBeanDefinition());
|
||||
compile(context, toFreshApplicationContext(GenericApplicationContext::new, aotContext -> {
|
||||
assertThat(aotContext.getBeanDefinitionNames()).containsOnly("autowiredComponent", "number");
|
||||
AutowiredComponent bean = aotContext.getBean(AutowiredComponent.class);
|
||||
assertThat(bean.getEnvironment()).isSameAs(aotContext.getEnvironment());
|
||||
assertThat(bean.getCounter()).isEqualTo(42);
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateApplicationContextWithInitDestroyMethods() {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.registerBeanDefinition(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME,
|
||||
BeanDefinitionBuilder.rootBeanDefinition(CommonAnnotationBeanPostProcessor.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
|
||||
context.registerBeanDefinition("initDestroyComponent", new RootBeanDefinition(InitDestroyComponent.class));
|
||||
compile(context, toFreshApplicationContext(GenericApplicationContext::new, aotContext -> {
|
||||
assertThat(aotContext.getBeanDefinitionNames()).containsOnly("initDestroyComponent");
|
||||
InitDestroyComponent bean = aotContext.getBean(InitDestroyComponent.class);
|
||||
assertThat(bean.events).containsExactly("init");
|
||||
aotContext.close();
|
||||
assertThat(bean.events).containsExactly("init", "destroy");
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateApplicationContextWithMultipleInitDestroyMethods() {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.registerBeanDefinition(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME,
|
||||
BeanDefinitionBuilder.rootBeanDefinition(CommonAnnotationBeanPostProcessor.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyComponent.class);
|
||||
beanDefinition.setInitMethodName("customInit");
|
||||
beanDefinition.setDestroyMethodName("customDestroy");
|
||||
context.registerBeanDefinition("initDestroyComponent", beanDefinition);
|
||||
compile(context, toFreshApplicationContext(GenericApplicationContext::new, aotContext -> {
|
||||
assertThat(aotContext.getBeanDefinitionNames()).containsOnly("initDestroyComponent");
|
||||
InitDestroyComponent bean = aotContext.getBean(InitDestroyComponent.class);
|
||||
assertThat(bean.events).containsExactly("customInit", "init");
|
||||
aotContext.close();
|
||||
assertThat(bean.events).containsExactly("customInit", "init", "customDestroy", "destroy");
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateApplicationContextWitNoContributors() {
|
||||
GeneratedTypeContext generationContext = createGenerationContext();
|
||||
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
|
||||
generator.generateApplicationContext(new GenericApplicationContext(), generationContext);
|
||||
assertThat(write(generationContext.getMainGeneratedType())).contains("""
|
||||
public class Test implements ApplicationContextInitializer<GenericApplicationContext> {
|
||||
@Override
|
||||
public void initialize(GenericApplicationContext context) {
|
||||
// infrastructure
|
||||
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
|
||||
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateApplicationContextLoadsBeanFactoryContributors() {
|
||||
GeneratedTypeContext generationContext = createGenerationContext();
|
||||
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
|
||||
GenericApplicationContext applicationContext = new GenericApplicationContext();
|
||||
applicationContext.setClassLoader(
|
||||
new TestSpringFactoriesClassLoader("bean-factory-contributors.factories"));
|
||||
generator.generateApplicationContext(applicationContext, generationContext);
|
||||
assertThat(write(generationContext.getMainGeneratedType())).contains("""
|
||||
public class Test implements ApplicationContextInitializer<GenericApplicationContext> {
|
||||
@Override
|
||||
public void initialize(GenericApplicationContext context) {
|
||||
// infrastructure
|
||||
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
|
||||
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
|
||||
// Test
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateApplicationContextApplyContributionAsIsWithNewLineAtTheEnd() {
|
||||
GenericApplicationContext applicationContext = new GenericApplicationContext();
|
||||
registerAotContributingBeanDefinition(applicationContext, "bpp", code -> code.add("// Hello"));
|
||||
GeneratedTypeContext generationContext = createGenerationContext();
|
||||
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
|
||||
generator.generateApplicationContext(applicationContext, generationContext);
|
||||
assertThat(write(generationContext.getMainGeneratedType())).contains("""
|
||||
public class Test implements ApplicationContextInitializer<GenericApplicationContext> {
|
||||
@Override
|
||||
public void initialize(GenericApplicationContext context) {
|
||||
// infrastructure
|
||||
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
|
||||
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
|
||||
// Hello
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateApplicationContextApplyMultipleContributionAsIsWithNewLineAtTheEnd() {
|
||||
GeneratedTypeContext generationContext = createGenerationContext();
|
||||
GenericApplicationContext applicationContext = new GenericApplicationContext();
|
||||
registerAotContributingBeanDefinition(applicationContext, "bpp", code -> code.add("// Hello"));
|
||||
registerAotContributingBeanDefinition(applicationContext, "bpp2", code -> code.add("// World"));
|
||||
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
|
||||
generator.generateApplicationContext(applicationContext, generationContext);
|
||||
assertThat(write(generationContext.getMainGeneratedType())).contains("""
|
||||
public class Test implements ApplicationContextInitializer<GenericApplicationContext> {
|
||||
@Override
|
||||
public void initialize(GenericApplicationContext context) {
|
||||
// infrastructure
|
||||
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
|
||||
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
|
||||
// Hello
|
||||
// World
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateApplicationContextExcludeAotContributingBeanFactoryPostProcessorByDefault() {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.registerBeanDefinition("test", new RootBeanDefinition(NoOpAotContributingBeanFactoryPostProcessor.class));
|
||||
compile(context, toFreshApplicationContext(GenericApplicationContext::new, aotContext ->
|
||||
assertThat(aotContext.getBeanDefinitionNames()).isEmpty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateApplicationContextExcludeAotContributingBeanPostProcessorByDefault() {
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.registerBeanDefinition("test", new RootBeanDefinition(NoOpAotContributingBeanPostProcessor.class));
|
||||
compile(context, toFreshApplicationContext(GenericApplicationContext::new, aotContext ->
|
||||
assertThat(aotContext.getBeanDefinitionNames()).isEmpty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateApplicationContextInvokeExcludePredicateInOrder() {
|
||||
GeneratedTypeContext generationContext = createGenerationContext();
|
||||
GenericApplicationContext applicationContext = new GenericApplicationContext();
|
||||
DefaultListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory();
|
||||
BiPredicate<String, BeanDefinition> excludeFilter = mockExcludeFilter();
|
||||
given(excludeFilter.test(eq("bean1"), any(BeanDefinition.class))).willReturn(Boolean.FALSE);
|
||||
given(excludeFilter.test(eq("bean2"), any(BeanDefinition.class))).willReturn(Boolean.TRUE);
|
||||
applicationContext.registerBeanDefinition("bean2", new RootBeanDefinition(SimpleComponent.class));
|
||||
applicationContext.registerBeanDefinition("bean1", new RootBeanDefinition(SimpleComponent.class));
|
||||
registerAotContributingBeanDefinition(applicationContext, "bpp", code -> {}, excludeFilter);
|
||||
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
|
||||
generator.generateApplicationContext(applicationContext, generationContext);
|
||||
assertThat(write(generationContext.getMainGeneratedType()))
|
||||
.doesNotContain("bean2").doesNotContain("bpp")
|
||||
.contains("BeanDefinitionRegistrar.of(\"bean1\", SimpleComponent.class)");
|
||||
verify(excludeFilter).test(eq("bean2"), any(BeanDefinition.class));
|
||||
verify(excludeFilter).test("bean1", beanFactory.getMergedBeanDefinition("bean1"));
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private BiPredicate<String, BeanDefinition> mockExcludeFilter() {
|
||||
return mock(BiPredicate.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void compile(GenericApplicationContext applicationContext, Consumer<ApplicationContextInitializer> initializer) {
|
||||
DefaultGeneratedTypeContext generationContext = createGenerationContext();
|
||||
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
|
||||
generator.generateApplicationContext(applicationContext, generationContext);
|
||||
SourceFiles sourceFiles = SourceFiles.none();
|
||||
for (JavaFile javaFile : generationContext.toJavaFiles()) {
|
||||
sourceFiles = sourceFiles.and(SourceFile.of((javaFile::writeTo)));
|
||||
}
|
||||
TestCompiler.forSystem().withSources(sourceFiles).compile(compiled -> {
|
||||
ApplicationContextInitializer instance = compiled.getInstance(ApplicationContextInitializer.class, MAIN_GENERATED_TYPE.canonicalName());
|
||||
initializer.accept(instance);
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private <T extends GenericApplicationContext> Consumer<ApplicationContextInitializer> toFreshApplicationContext(
|
||||
Supplier<T> applicationContextFactory, Consumer<T> context) {
|
||||
return applicationContextInitializer -> {
|
||||
T applicationContext = applicationContextFactory.get();
|
||||
applicationContextInitializer.initialize(applicationContext);
|
||||
applicationContext.refresh();
|
||||
context.accept(applicationContext);
|
||||
};
|
||||
}
|
||||
|
||||
private DefaultGeneratedTypeContext createGenerationContext() {
|
||||
return new DefaultGeneratedTypeContext(MAIN_GENERATED_TYPE.packageName(), packageName ->
|
||||
GeneratedType.of(ClassName.get(packageName, MAIN_GENERATED_TYPE.simpleName())));
|
||||
}
|
||||
|
||||
private String write(GeneratedType generatedType) {
|
||||
try {
|
||||
StringWriter out = new StringWriter();
|
||||
generatedType.toJavaFile().writeTo(out);
|
||||
return out.toString();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Failed to write " + generatedType, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerAotContributingBeanDefinition(GenericApplicationContext context, String name,
|
||||
Consumer<CodeBlock.Builder> code) {
|
||||
registerAotContributingBeanDefinition(context, name, code,
|
||||
(beanName, beanDefinition) -> name.equals(beanName));
|
||||
}
|
||||
|
||||
private void registerAotContributingBeanDefinition(GenericApplicationContext context, String name,
|
||||
Consumer<CodeBlock.Builder> code, BiPredicate<String, BeanDefinition> excludeFilter) {
|
||||
BeanFactoryContribution contribution = new TestBeanFactoryContribution(
|
||||
initialization -> initialization.contribute(code), excludeFilter);
|
||||
context.registerBeanDefinition(name, BeanDefinitionBuilder.rootBeanDefinition(
|
||||
TestAotContributingBeanFactoryPostProcessor.class, () ->
|
||||
new TestAotContributingBeanFactoryPostProcessor(contribution)).getBeanDefinition());
|
||||
}
|
||||
|
||||
|
||||
static class TestAotContributingBeanFactoryPostProcessor implements AotContributingBeanFactoryPostProcessor {
|
||||
|
||||
@Nullable
|
||||
private final BeanFactoryContribution beanFactoryContribution;
|
||||
|
||||
TestAotContributingBeanFactoryPostProcessor(@Nullable BeanFactoryContribution beanFactoryContribution) {
|
||||
this.beanFactoryContribution = beanFactoryContribution;
|
||||
}
|
||||
|
||||
TestAotContributingBeanFactoryPostProcessor() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanFactoryContribution contribute(ConfigurableListableBeanFactory beanFactory) {
|
||||
return this.beanFactoryContribution;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class TextAotContributingBeanFactoryPostProcessor implements AotContributingBeanFactoryPostProcessor {
|
||||
|
||||
@Override
|
||||
public BeanFactoryContribution contribute(ConfigurableListableBeanFactory beanFactory) {
|
||||
return initialization -> initialization.contribute(code -> code.add("// Test\n"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class NoOpAotContributingBeanFactoryPostProcessor implements AotContributingBeanFactoryPostProcessor {
|
||||
|
||||
@Override
|
||||
public BeanFactoryContribution contribute(ConfigurableListableBeanFactory beanFactory) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static class NoOpAotContributingBeanPostProcessor implements AotContributingBeanPostProcessor {
|
||||
|
||||
@Override
|
||||
public BeanInstantiationContribution contribute(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class TestBeanFactoryContribution implements BeanFactoryContribution {
|
||||
|
||||
private final Consumer<BeanFactoryInitialization> contribution;
|
||||
|
||||
private final BiPredicate<String, BeanDefinition> excludeFilter;
|
||||
|
||||
public TestBeanFactoryContribution(Consumer<BeanFactoryInitialization> contribution,
|
||||
BiPredicate<String, BeanDefinition> excludeFilter) {
|
||||
this.contribution = contribution;
|
||||
this.excludeFilter = excludeFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(BeanFactoryInitialization initialization) {
|
||||
this.contribution.accept(initialization);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiPredicate<String, BeanDefinition> getBeanDefinitionExcludeFilter() {
|
||||
return this.excludeFilter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class TestSpringFactoriesClassLoader extends ClassLoader {
|
||||
|
||||
private final String factoriesName;
|
||||
|
||||
TestSpringFactoriesClassLoader(String factoriesName) {
|
||||
super(RuntimeHintsPostProcessorTests.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/context/generator/aot/" + this.factoriesName);
|
||||
}
|
||||
return super.getResources(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,61 +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.context.generator;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generator.DefaultGeneratedTypeContext;
|
||||
import org.springframework.aot.generator.GeneratedType;
|
||||
import org.springframework.aot.generator.GeneratedTypeContext;
|
||||
import org.springframework.beans.factory.generator.BeanFactoryInitialization;
|
||||
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.javapoet.support.CodeSnippet;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link InfrastructureContribution}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class InfrastructureContributionTests {
|
||||
|
||||
@Test
|
||||
void contributeInfrastructure() {
|
||||
CodeSnippet codeSnippet = contribute(createGenerationContext());
|
||||
assertThat(codeSnippet.getSnippet()).isEqualTo("""
|
||||
// infrastructure
|
||||
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
|
||||
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
|
||||
""");
|
||||
assertThat(codeSnippet.hasImport(ContextAnnotationAutowireCandidateResolver.class)).isTrue();
|
||||
}
|
||||
|
||||
private CodeSnippet contribute(GeneratedTypeContext generationContext) {
|
||||
InfrastructureContribution contribution = new InfrastructureContribution();
|
||||
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")));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,183 +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.context.generator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generator.DefaultGeneratedTypeContext;
|
||||
import org.springframework.aot.generator.GeneratedType;
|
||||
import org.springframework.aot.hint.ResourceBundleHint;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.beans.BeanInstantiationException;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.annotation.AnnotationConfigUtils;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link RuntimeHintsPostProcessor}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class RuntimeHintsPostProcessorTests {
|
||||
|
||||
private DefaultGeneratedTypeContext generationContext;
|
||||
|
||||
private ApplicationContextAotGenerator generator;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.generationContext = createGenerationContext();
|
||||
this.generator = new ApplicationContextAotGenerator();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProcessRegistrarOnConfiguration() {
|
||||
GenericApplicationContext applicationContext = createContext(ConfigurationWithHints.class);
|
||||
this.generator.generateApplicationContext(applicationContext, this.generationContext);
|
||||
assertThatSampleRegistrarContributed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProcessRegistrarOnBeanMethod() {
|
||||
GenericApplicationContext applicationContext = createContext(ConfigurationWithBeanDeclaringHints.class);
|
||||
this.generator.generateApplicationContext(applicationContext, this.generationContext);
|
||||
assertThatSampleRegistrarContributed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProcessRegistrarInSpringFactory() {
|
||||
GenericApplicationContext applicationContext = createContext();
|
||||
applicationContext.setClassLoader(new TestSpringFactoriesClassLoader("test.factories"));
|
||||
this.generator.generateApplicationContext(applicationContext, this.generationContext);
|
||||
assertThatSampleRegistrarContributed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectRuntimeHintsRegistrarWithoutDefaultConstructor() {
|
||||
GenericApplicationContext applicationContext = createContext(ConfigurationWithIllegalRegistrar.class);
|
||||
assertThatThrownBy(() -> this.generator.generateApplicationContext(applicationContext, this.generationContext))
|
||||
.isInstanceOf(BeanInstantiationException.class);
|
||||
}
|
||||
|
||||
private void assertThatSampleRegistrarContributed() {
|
||||
Stream<ResourceBundleHint> bundleHints = this.generationContext.runtimeHints().resources().resourceBundles();
|
||||
assertThat(bundleHints).anyMatch(bundleHint -> "sample".equals(bundleHint.getBaseName()));
|
||||
}
|
||||
|
||||
private GenericApplicationContext createContext(Class<?>... configClasses) {
|
||||
GenericApplicationContext applicationContext = new GenericApplicationContext();
|
||||
AnnotationConfigUtils.registerAnnotationConfigProcessors(applicationContext);
|
||||
for (Class<?> configClass : configClasses) {
|
||||
applicationContext.registerBeanDefinition(configClass.getSimpleName(), new RootBeanDefinition(configClass));
|
||||
}
|
||||
applicationContext.registerBeanDefinition("runtimeHintsPostProcessor",
|
||||
BeanDefinitionBuilder.rootBeanDefinition(RuntimeHintsPostProcessor.class, RuntimeHintsPostProcessor::new).getBeanDefinition());
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
private DefaultGeneratedTypeContext createGenerationContext() {
|
||||
ClassName mainGeneratedType = ClassName.get("com.example", "Test");
|
||||
return new DefaultGeneratedTypeContext(mainGeneratedType.packageName(), packageName ->
|
||||
GeneratedType.of(ClassName.get(packageName, mainGeneratedType.simpleName())));
|
||||
}
|
||||
|
||||
|
||||
@ImportRuntimeHints(SampleRuntimeHintsRegistrar.class)
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ConfigurationWithHints {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ConfigurationWithBeanDeclaringHints {
|
||||
|
||||
@Bean
|
||||
@ImportRuntimeHints(SampleRuntimeHintsRegistrar.class)
|
||||
SampleBean sampleBean() {
|
||||
return new SampleBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class SampleRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
hints.resources().registerResourceBundle("sample");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class SampleBean {
|
||||
|
||||
}
|
||||
|
||||
@ImportRuntimeHints(IllegalRuntimeHintsRegistrar.class)
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ConfigurationWithIllegalRegistrar {
|
||||
|
||||
}
|
||||
|
||||
public static class IllegalRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
|
||||
|
||||
public IllegalRuntimeHintsRegistrar(String arg) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
hints.resources().registerResourceBundle("sample");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
static class TestSpringFactoriesClassLoader extends ClassLoader {
|
||||
|
||||
private final String factoriesName;
|
||||
|
||||
TestSpringFactoriesClassLoader(String factoriesName) {
|
||||
super(RuntimeHintsPostProcessorTests.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/context/generator/runtimehints/" + this.factoriesName);
|
||||
}
|
||||
return super.getResources(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
org.springframework.beans.factory.generator.AotContributingBeanFactoryPostProcessor= \
|
||||
org.springframework.context.generator.ApplicationContextAotGeneratorTests.NoOpAotContributingBeanFactoryPostProcessor, \
|
||||
org.springframework.context.generator.ApplicationContextAotGeneratorTests.TextAotContributingBeanFactoryPostProcessor
|
|
@ -1,2 +0,0 @@
|
|||
org.springframework.aot.hint.RuntimeHintsRegistrar= \
|
||||
org.springframework.context.generator.RuntimeHintsPostProcessorTests.SampleRuntimeHintsRegistrar
|
|
@ -1,53 +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.aot.generator;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.javapoet.support.MultiStatement;
|
||||
|
||||
/**
|
||||
* A code contribution that gathers the code, the {@linkplain RuntimeHints
|
||||
* runtime hints}, and the {@linkplain ProtectedElement protected elements}
|
||||
* that are necessary to execute it.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface CodeContribution {
|
||||
|
||||
/**
|
||||
* Return the {@linkplain MultiStatement statements} that can be used to
|
||||
* append code.
|
||||
* @return the statements instance to use to contribute code
|
||||
*/
|
||||
MultiStatement statements();
|
||||
|
||||
/**
|
||||
* Return the {@linkplain RuntimeHints hints} to use to register
|
||||
* potential optimizations for contributed code.
|
||||
* @return the runtime hints
|
||||
*/
|
||||
RuntimeHints runtimeHints();
|
||||
|
||||
/**
|
||||
* Return the {@linkplain ProtectedAccess protected access} to use to
|
||||
* analyze any privileged access, if necessary.
|
||||
* @return the protected access
|
||||
*/
|
||||
ProtectedAccess protectedAccess();
|
||||
|
||||
}
|
|
@ -1,68 +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.aot.generator;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.javapoet.support.MultiStatement;
|
||||
|
||||
/**
|
||||
* A default {@link CodeContribution} implementation.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public class DefaultCodeContribution implements CodeContribution {
|
||||
|
||||
private final MultiStatement statements;
|
||||
|
||||
private final RuntimeHints runtimeHints;
|
||||
|
||||
private final ProtectedAccess protectedAccess;
|
||||
|
||||
|
||||
protected DefaultCodeContribution(MultiStatement statements, RuntimeHints runtimeHints,
|
||||
ProtectedAccess protectedAccess) {
|
||||
|
||||
this.statements = statements;
|
||||
this.runtimeHints = runtimeHints;
|
||||
this.protectedAccess = protectedAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance with the {@link RuntimeHints} instance to use.
|
||||
* @param runtimeHints the runtime hints instance to use
|
||||
*/
|
||||
public DefaultCodeContribution(RuntimeHints runtimeHints) {
|
||||
this(new MultiStatement(), runtimeHints, new ProtectedAccess());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiStatement statements() {
|
||||
return this.statements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeHints runtimeHints() {
|
||||
return this.runtimeHints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtectedAccess protectedAccess() {
|
||||
return this.protectedAccess;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,92 +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.aot.generator;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.javapoet.JavaFile;
|
||||
|
||||
/**
|
||||
* Default {@link GeneratedTypeContext} implementation.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public class DefaultGeneratedTypeContext implements GeneratedTypeContext {
|
||||
|
||||
private final String packageName;
|
||||
|
||||
private final RuntimeHints runtimeHints;
|
||||
|
||||
private final Function<String, GeneratedType> generatedTypeFactory;
|
||||
|
||||
private final Map<String, GeneratedType> generatedTypes;
|
||||
|
||||
/**
|
||||
* Create a context targeting the specified package name and using the specified
|
||||
* factory to create a {@link GeneratedType} per requested package name.
|
||||
* @param packageName the main package name
|
||||
* @param generatedTypeFactory the factory to use to create a {@link GeneratedType}
|
||||
* based on a package name.
|
||||
*/
|
||||
public DefaultGeneratedTypeContext(String packageName, Function<String, GeneratedType> generatedTypeFactory) {
|
||||
this.packageName = packageName;
|
||||
this.runtimeHints = new RuntimeHints();
|
||||
this.generatedTypeFactory = generatedTypeFactory;
|
||||
this.generatedTypes = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeHints runtimeHints() {
|
||||
return this.runtimeHints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeneratedType getGeneratedType(String packageName) {
|
||||
return this.generatedTypes.computeIfAbsent(packageName, this.generatedTypeFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeneratedType getMainGeneratedType() {
|
||||
return getGeneratedType(this.packageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify if a {@link GeneratedType} for the specified package name is registered.
|
||||
* @param packageName the package name to use
|
||||
* @return {@code true} if a type is registered for that package
|
||||
*/
|
||||
public boolean hasGeneratedType(String packageName) {
|
||||
return this.generatedTypes.containsKey(packageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of {@link JavaFile} of known generated type.
|
||||
* @return the java files of bootstrap classes in this instance
|
||||
*/
|
||||
public List<JavaFile> toJavaFiles() {
|
||||
return this.generatedTypes.values().stream()
|
||||
.map(GeneratedType::toJavaFile)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,125 +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.aot.generator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.javapoet.JavaFile;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.TypeSpec;
|
||||
|
||||
/**
|
||||
* Wrapper for a generated {@linkplain TypeSpec type}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public class GeneratedType {
|
||||
|
||||
private final ClassName className;
|
||||
|
||||
private final TypeSpec.Builder type;
|
||||
|
||||
private final List<MethodSpec> methods;
|
||||
|
||||
GeneratedType(ClassName className, Consumer<TypeSpec.Builder> type) {
|
||||
this.className = className;
|
||||
this.type = TypeSpec.classBuilder(className);
|
||||
type.accept(this.type);
|
||||
this.methods = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance for the specified {@link ClassName}, customizing the type with
|
||||
* the specified {@link Consumer consumer callback}.
|
||||
* @param className the class name
|
||||
* @param type a callback to customize the type, i.e. to change default modifiers
|
||||
* @return a new {@link GeneratedType}
|
||||
*/
|
||||
public static GeneratedType of(ClassName className, Consumer<TypeSpec.Builder> type) {
|
||||
return new GeneratedType(className, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance for the specified {@link ClassName}, as a {@code public} type.
|
||||
* @param className the class name
|
||||
* @return a new {@link GeneratedType}
|
||||
*/
|
||||
public static GeneratedType of(ClassName className) {
|
||||
return of(className, type -> type.addModifiers(Modifier.PUBLIC));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link ClassName} of this instance.
|
||||
* @return the class name
|
||||
*/
|
||||
public ClassName getClassName() {
|
||||
return this.className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize the type of this instance.
|
||||
* @param type the consumer of the type builder
|
||||
* @return this for method chaining
|
||||
*/
|
||||
public GeneratedType customizeType(Consumer<TypeSpec.Builder> type) {
|
||||
type.accept(this.type);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a method using the state of the specified {@link MethodSpec.Builder},
|
||||
* updating the name of the method if a similar method already exists.
|
||||
* @param method a method builder representing the method to add
|
||||
* @return the added method
|
||||
*/
|
||||
public MethodSpec addMethod(MethodSpec.Builder method) {
|
||||
MethodSpec methodToAdd = createUniqueNameIfNecessary(method.build());
|
||||
this.methods.add(methodToAdd);
|
||||
return methodToAdd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link JavaFile} with the state of this instance.
|
||||
* @return a java file
|
||||
*/
|
||||
public JavaFile toJavaFile() {
|
||||
return JavaFile.builder(this.className.packageName(),
|
||||
this.type.addMethods(this.methods).build()).indent("\t").build();
|
||||
}
|
||||
|
||||
private MethodSpec createUniqueNameIfNecessary(MethodSpec method) {
|
||||
List<MethodSpec> candidates = this.methods.stream().filter(isSimilar(method)).toList();
|
||||
if (candidates.isEmpty()) {
|
||||
return method;
|
||||
}
|
||||
MethodSpec updatedMethod = method.toBuilder().setName(method.name + "_").build();
|
||||
return createUniqueNameIfNecessary(updatedMethod);
|
||||
}
|
||||
|
||||
private Predicate<MethodSpec> isSimilar(MethodSpec method) {
|
||||
return candidate -> method.name.equals(candidate.name)
|
||||
&& method.parameters.size() == candidate.parameters.size();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,52 +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.aot.generator;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
|
||||
/**
|
||||
* Context passed to object that can generate code, giving access to a main
|
||||
* {@link GeneratedType} as well as to a {@link GeneratedType} in a given
|
||||
* package if privileged access is required.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface GeneratedTypeContext {
|
||||
|
||||
/**
|
||||
* Return the {@link RuntimeHints} instance to use to contribute hints for
|
||||
* generated types.
|
||||
* @return the runtime hints
|
||||
*/
|
||||
RuntimeHints runtimeHints();
|
||||
|
||||
/**
|
||||
* Return a {@link GeneratedType} for the specified package. If it does not
|
||||
* exist, it is created.
|
||||
* @param packageName the package name to use
|
||||
* @return a generated type
|
||||
*/
|
||||
GeneratedType getGeneratedType(String packageName);
|
||||
|
||||
/**
|
||||
* Return the main {@link GeneratedType}.
|
||||
* @return the generated type for the target package
|
||||
*/
|
||||
GeneratedType getMainGeneratedType();
|
||||
|
||||
}
|
|
@ -1,60 +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.aot.generator;
|
||||
|
||||
import org.springframework.aot.hint.AbstractTypeReference;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link TypeReference} for a generated {@linkplain ClassName type}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class GeneratedTypeReference extends AbstractTypeReference {
|
||||
|
||||
private final ClassName className;
|
||||
|
||||
private GeneratedTypeReference(ClassName className) {
|
||||
super(className.packageName(), className.simpleName(), safeCreate(className.enclosingClassName()));
|
||||
this.className = className;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static GeneratedTypeReference safeCreate(@Nullable ClassName className) {
|
||||
return (className != null ? new GeneratedTypeReference(className) : null);
|
||||
}
|
||||
|
||||
public static GeneratedTypeReference of(ClassName className) {
|
||||
Assert.notNull(className, "ClassName must not be null");
|
||||
return new GeneratedTypeReference(className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCanonicalName() {
|
||||
return this.className.canonicalName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isPrimitive() {
|
||||
return this.className.isPrimitive();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,281 +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.aot.generator;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Gather the need of non-public access and determine the privileged package
|
||||
* to use, if necessary.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public class ProtectedAccess {
|
||||
|
||||
private final List<ProtectedElement> elements;
|
||||
|
||||
public ProtectedAccess() {
|
||||
this.elements = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether the protected elements registered in this instance are
|
||||
* accessible from the specified package name.
|
||||
* @param packageName the target package name
|
||||
* @return {@code true} if the registered access can be safely used from
|
||||
* the specified package name
|
||||
*/
|
||||
public boolean isAccessible(String packageName) {
|
||||
return getProtectedElements(packageName).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the privileged package name to use for the specified package
|
||||
* name, or {@code null} if none is required.
|
||||
* @param packageName the target package name to use
|
||||
* @return the privileged package name to use, or {@code null}
|
||||
* @throws ProtectedAccessException if a single privileged package cannot
|
||||
* be identified
|
||||
* @see #isAccessible(String)
|
||||
*/
|
||||
@Nullable
|
||||
public String getPrivilegedPackageName(String packageName) throws ProtectedAccessException {
|
||||
List<ProtectedElement> protectedElements = getProtectedElements(packageName);
|
||||
if (protectedElements.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
List<String> packageNames = protectedElements.stream()
|
||||
.map(element -> element.getType().getPackageName())
|
||||
.distinct().toList();
|
||||
if (packageNames.size() == 1) {
|
||||
return packageNames.get(0);
|
||||
}
|
||||
throw new ProtectedAccessException("Multiple packages require a privileged access: "
|
||||
+ packageNames, protectedElements);
|
||||
}
|
||||
|
||||
private List<ProtectedElement> getProtectedElements(String packageName) {
|
||||
List<ProtectedElement> matches = new ArrayList<>();
|
||||
for (ProtectedElement element : this.elements) {
|
||||
if (!element.getType().getPackage().getName().equals(packageName)) {
|
||||
matches.add(element);
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze the specified {@linkplain ResolvableType type}, including its
|
||||
* full type signature.
|
||||
* @param type the type to analyze
|
||||
*/
|
||||
public void analyze(ResolvableType type) {
|
||||
Class<?> protectedType = isProtected(type);
|
||||
if (protectedType != null) {
|
||||
registerProtectedType(protectedType, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze accessing the specified {@link Member} using the specified
|
||||
* {@link Options options}.
|
||||
* @param member the member to analyze
|
||||
* @param options the options to use
|
||||
*/
|
||||
public void analyze(Member member, Options options) {
|
||||
if (isProtected(member.getDeclaringClass())) {
|
||||
registerProtectedType(member.getDeclaringClass(), member);
|
||||
}
|
||||
if (isProtected(member.getModifiers()) && !options.useReflection.apply(member)) {
|
||||
registerProtectedType(member.getDeclaringClass(), member);
|
||||
}
|
||||
if (member instanceof Field field) {
|
||||
ResolvableType fieldType = ResolvableType.forField(field);
|
||||
Class<?> protectedType = isProtected(fieldType);
|
||||
if (protectedType != null && options.assignReturnType.apply(field)) {
|
||||
registerProtectedType(protectedType, field);
|
||||
}
|
||||
}
|
||||
else if (member instanceof Constructor<?> constructor) {
|
||||
analyzeParameterTypes(constructor, i ->
|
||||
ResolvableType.forConstructorParameter(constructor, i));
|
||||
}
|
||||
else if (member instanceof Method method) {
|
||||
ResolvableType returnType = ResolvableType.forMethodReturnType(method);
|
||||
Class<?> protectedType = isProtected(returnType);
|
||||
if (protectedType != null && options.assignReturnType.apply(method)) {
|
||||
registerProtectedType(protectedType, method);
|
||||
}
|
||||
analyzeParameterTypes(method, i -> ResolvableType.forMethodParameter(method, i));
|
||||
}
|
||||
}
|
||||
|
||||
private void analyzeParameterTypes(Executable executable, Function<Integer,
|
||||
ResolvableType> parameterTypeFactory) {
|
||||
|
||||
for (int i = 0; i < executable.getParameters().length; i++) {
|
||||
ResolvableType parameterType = parameterTypeFactory.apply(i);
|
||||
Class<?> protectedType = isProtected(parameterType);
|
||||
if (protectedType != null) {
|
||||
registerProtectedType(protectedType, executable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Class<?> isProtected(ResolvableType resolvableType) {
|
||||
return isProtected(new HashSet<>(), resolvableType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Class<?> isProtected(Set<ResolvableType> seen, ResolvableType target) {
|
||||
if (seen.contains(target)) {
|
||||
return null;
|
||||
}
|
||||
seen.add(target);
|
||||
ResolvableType nonProxyTarget = target.as(ClassUtils.getUserClass(target.toClass()));
|
||||
Class<?> rawClass = nonProxyTarget.toClass();
|
||||
if (isProtected(rawClass)) {
|
||||
return rawClass;
|
||||
}
|
||||
Class<?> declaringClass = rawClass.getDeclaringClass();
|
||||
if (declaringClass != null) {
|
||||
if (isProtected(declaringClass)) {
|
||||
return declaringClass;
|
||||
}
|
||||
}
|
||||
if (nonProxyTarget.hasGenerics()) {
|
||||
for (ResolvableType generic : nonProxyTarget.getGenerics()) {
|
||||
return isProtected(seen, generic);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isProtected(Class<?> type) {
|
||||
Class<?> candidate = ClassUtils.getUserClass(type);
|
||||
return isProtected(candidate.getModifiers());
|
||||
}
|
||||
|
||||
private boolean isProtected(int modifiers) {
|
||||
return !Modifier.isPublic(modifiers);
|
||||
}
|
||||
|
||||
private void registerProtectedType(Class<?> type, @Nullable Member member) {
|
||||
this.elements.add(ProtectedElement.of(type, member));
|
||||
}
|
||||
|
||||
/**
|
||||
* Options to use to analyze if invoking a {@link Member} requires
|
||||
* privileged access.
|
||||
*/
|
||||
public static final class Options {
|
||||
|
||||
private final Function<Member, Boolean> assignReturnType;
|
||||
|
||||
private final Function<Member, Boolean> useReflection;
|
||||
|
||||
|
||||
private Options(Builder builder) {
|
||||
this.assignReturnType = builder.assignReturnType;
|
||||
this.useReflection = builder.useReflection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a {@link Builder} with default options, that is use
|
||||
* reflection if the member is private and does not assign the
|
||||
* return type.
|
||||
* @return an options builder
|
||||
*/
|
||||
public static Builder defaults() {
|
||||
return new Builder(member -> false,
|
||||
member -> Modifier.isPrivate(member.getModifiers()));
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private Function<Member, Boolean> assignReturnType;
|
||||
|
||||
private Function<Member, Boolean> useReflection;
|
||||
|
||||
private Builder(Function<Member, Boolean> assignReturnType,
|
||||
Function<Member, Boolean> useReflection) {
|
||||
this.assignReturnType = assignReturnType;
|
||||
this.useReflection = useReflection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify if the return type is assigned so that its type can be
|
||||
* analyzed if necessary.
|
||||
* @param assignReturnType whether the return type is assigned
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder assignReturnType(boolean assignReturnType) {
|
||||
return assignReturnType(member -> assignReturnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a function that determines whether the return type is
|
||||
* assigned so that its type can be analyzed.
|
||||
* @param assignReturnType whether the return type is assigned
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder assignReturnType(Function<Member, Boolean> assignReturnType) {
|
||||
this.assignReturnType = assignReturnType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a function that determines whether reflection can be
|
||||
* used for a given {@link Member}.
|
||||
* @param useReflection whether reflection can be used
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder useReflection(Function<Member, Boolean> useReflection) {
|
||||
this.useReflection = useReflection;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an {@link Options} instance based on the state of this
|
||||
* builder.
|
||||
* @return a new options instance
|
||||
*/
|
||||
public Options build() {
|
||||
return new Options(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,45 +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.aot.generator;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Thrown when a code block requires privileged access on multiple packages.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class ProtectedAccessException extends RuntimeException {
|
||||
|
||||
private final List<ProtectedElement> protectedElements;
|
||||
|
||||
public ProtectedAccessException(String message, List<ProtectedElement> protectedElements) {
|
||||
super(message);
|
||||
this.protectedElements = protectedElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@linkplain ProtectedElement protected elements}.
|
||||
* @return the protected access
|
||||
*/
|
||||
public List<ProtectedElement> getProtectedElements() {
|
||||
return this.protectedElements;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,68 +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.aot.generator;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A {@link Member} that is non-public, with the related type.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class ProtectedElement {
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
@Nullable
|
||||
private final Member target;
|
||||
|
||||
|
||||
private ProtectedElement(Class<?> type, @Nullable Member member) {
|
||||
this.type = type;
|
||||
this.target = member;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Class type} that is non-public. For a plain
|
||||
* protected {@link Member member} access, the type of the declaring class
|
||||
* is used. Otherwise, the type in the member signature, such as a parameter
|
||||
* type for an executable, or the return type of a field is used. If the
|
||||
* type is generic, the type that is protected in the generic signature is
|
||||
* used.
|
||||
* @return the type that is not public
|
||||
*/
|
||||
public Class<?> getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Member} that is not publicly accessible.
|
||||
* @return the member
|
||||
*/
|
||||
@Nullable
|
||||
public Member getMember() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
static ProtectedElement of(Class<?> type, @Nullable Member member) {
|
||||
return new ProtectedElement(type, member);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,68 +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.aot.generator;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.support.MultiCodeBlock;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Code generator for {@link ResolvableType}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class ResolvableTypeGenerator {
|
||||
|
||||
/**
|
||||
* Generate a type signature for the specified {@link ResolvableType}.
|
||||
* @param target the type to generate
|
||||
* @return the representation of that type
|
||||
*/
|
||||
public CodeBlock generateTypeFor(ResolvableType target) {
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
generate(code, target, false);
|
||||
return code.build();
|
||||
}
|
||||
|
||||
private void generate(CodeBlock.Builder code, ResolvableType target, boolean forceResolvableType) {
|
||||
Class<?> type = ClassUtils.getUserClass(target.toClass());
|
||||
if (!target.hasGenerics()) {
|
||||
if (forceResolvableType) {
|
||||
code.add("$T.forClass($T.class)", ResolvableType.class, type);
|
||||
}
|
||||
else {
|
||||
code.add("$T.class", type);
|
||||
}
|
||||
}
|
||||
else {
|
||||
code.add("$T.forClassWithGenerics($T.class, ", ResolvableType.class, type);
|
||||
ResolvableType[] generics = target.getGenerics();
|
||||
boolean hasGenericParameter = Arrays.stream(generics).anyMatch(ResolvableType::hasGenerics);
|
||||
MultiCodeBlock multi = new MultiCodeBlock();
|
||||
for (int i = 0; i < generics.length; i++) {
|
||||
ResolvableType parameter = target.getGeneric(i);
|
||||
multi.add(parameterCode -> generate(parameterCode, parameter, hasGenericParameter));
|
||||
}
|
||||
code.add(multi.join(", ")).add(")");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
/**
|
||||
* Support classes for components that contribute generated code equivalent
|
||||
* to a runtime behavior.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.aot.generator;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
|
@ -22,7 +22,7 @@ import org.springframework.lang.Nullable;
|
|||
* Contract for registering {@link RuntimeHints} in a static fashion.
|
||||
* <p>Implementations will contribute hints without any knowledge of the application context
|
||||
* and can only use the given {@link ClassLoader} to conditionally contribute hints.
|
||||
* <p>{@code RuntimeHintsRegistrar} can be declared as {@code spring.factories} entries;
|
||||
* <p>{@code RuntimeHintsRegistrar} can be declared as {@code spring/aot.factories} entries;
|
||||
* the registrar will be processed as soon as its declaration is found in the classpath.
|
||||
* A standard no-arg constructor is required for implementations.
|
||||
*
|
||||
|
|
|
@ -1,188 +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.javapoet.support;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.JavaFile;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.TypeSpec;
|
||||
|
||||
/**
|
||||
* A code snippet using tabs indentation that is fully processed by JavaPoet so
|
||||
* that imports are resolved.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class CodeSnippet {
|
||||
|
||||
private static final String START_SNIPPET = "// start-snippet\n";
|
||||
|
||||
private static final String END_SNIPPET = "// end-snippet";
|
||||
|
||||
private final String fileContent;
|
||||
|
||||
private final String snippet;
|
||||
|
||||
|
||||
CodeSnippet(String fileContent, String snippet) {
|
||||
this.fileContent = fileContent;
|
||||
this.snippet = snippet;
|
||||
}
|
||||
|
||||
|
||||
String getFileContent() {
|
||||
return this.fileContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the rendered code snippet.
|
||||
* @return a code snippet where imports have been resolved
|
||||
*/
|
||||
public String getSnippet() {
|
||||
return this.snippet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify if an import statement for the specified type is present.
|
||||
* @param type the type to check
|
||||
* @return true if this type has an import statement, false otherwise
|
||||
*/
|
||||
public boolean hasImport(Class<?> type) {
|
||||
return hasImport(type.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify if an import statement for the specified class name is present.
|
||||
* @param className the name of the class to check
|
||||
* @return true if this type has an import statement, false otherwise
|
||||
*/
|
||||
public boolean hasImport(String className) {
|
||||
return getFileContent().lines().anyMatch(candidate ->
|
||||
candidate.equals(String.format("import %s;", className)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link CodeSnippet} where the specified number of indentations
|
||||
* have been removed.
|
||||
* @param indent the number of indent to remove
|
||||
* @return a CodeSnippet instance with the number of indentations removed
|
||||
*/
|
||||
public CodeSnippet removeIndent(int indent) {
|
||||
return new CodeSnippet(this.fileContent, this.snippet.lines().map(line ->
|
||||
removeIndent(line, indent)).collect(Collectors.joining("\n")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link CodeSnippet} using the specified code.
|
||||
* @param code the code snippet
|
||||
* @return a {@link CodeSnippet} instance
|
||||
*/
|
||||
public static CodeSnippet of(CodeBlock code) {
|
||||
return new Builder().build(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the specified code and return a fully-processed code snippet
|
||||
* as a String.
|
||||
* @param code a consumer to use to generate the code snippet
|
||||
* @return a resolved code snippet
|
||||
*/
|
||||
public static String process(Consumer<CodeBlock.Builder> code) {
|
||||
CodeBlock.Builder body = CodeBlock.builder();
|
||||
code.accept(body);
|
||||
return process(body.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the specified {@link CodeBlock code} and return a
|
||||
* fully-processed code snippet as a String.
|
||||
* @param code the code snippet
|
||||
* @return a resolved code snippet
|
||||
*/
|
||||
public static String process(CodeBlock code) {
|
||||
return of(code).getSnippet();
|
||||
}
|
||||
|
||||
private String removeIndent(String line, int indent) {
|
||||
for (int i = 0; i < indent; i++) {
|
||||
if (line.startsWith("\t")) {
|
||||
line = line.substring(1);
|
||||
}
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
private static final class Builder {
|
||||
|
||||
private static final String INDENT = "\t";
|
||||
|
||||
private static final String SNIPPET_INDENT = INDENT + INDENT;
|
||||
|
||||
public CodeSnippet build(CodeBlock code) {
|
||||
MethodSpec.Builder method = MethodSpec.methodBuilder("test")
|
||||
.addModifiers(Modifier.PUBLIC);
|
||||
CodeBlock.Builder body = CodeBlock.builder();
|
||||
body.add(START_SNIPPET);
|
||||
body.add(code);
|
||||
body.add(END_SNIPPET);
|
||||
method.addCode(body.build());
|
||||
String fileContent = write(createTestJavaFile(method.build()));
|
||||
String snippet = isolateGeneratedContent(fileContent);
|
||||
return new CodeSnippet(fileContent, snippet);
|
||||
}
|
||||
|
||||
private String isolateGeneratedContent(String javaFile) {
|
||||
int start = javaFile.indexOf(START_SNIPPET);
|
||||
String tmp = javaFile.substring(start + START_SNIPPET.length());
|
||||
int end = tmp.indexOf(END_SNIPPET);
|
||||
tmp = tmp.substring(0, end);
|
||||
// Remove indent
|
||||
return tmp.lines().map(line -> {
|
||||
if (!line.startsWith(SNIPPET_INDENT)) {
|
||||
throw new IllegalStateException("Missing indent for " + line);
|
||||
}
|
||||
return line.substring(SNIPPET_INDENT.length());
|
||||
}).collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
private JavaFile createTestJavaFile(MethodSpec method) {
|
||||
return JavaFile.builder("example", TypeSpec.classBuilder("Test")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addMethod(method).build()).indent(INDENT).build();
|
||||
}
|
||||
|
||||
private String write(JavaFile file) {
|
||||
try {
|
||||
StringWriter out = new StringWriter();
|
||||
file.writeTo(out);
|
||||
return out.toString();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Failed to write " + file, ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,82 +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.javapoet.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.CodeBlock.Builder;
|
||||
|
||||
|
||||
/**
|
||||
* A {@link CodeBlock} wrapper for joining multiple blocks.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public class MultiCodeBlock {
|
||||
|
||||
private final List<CodeBlock> codeBlocks = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Add the specified {@link CodeBlock}.
|
||||
* @param code the code block to add
|
||||
*/
|
||||
public void add(CodeBlock code) {
|
||||
if (code.isEmpty()) {
|
||||
throw new IllegalArgumentException("Could not add empty CodeBlock");
|
||||
}
|
||||
this.codeBlocks.add(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link CodeBlock} using the specified callback.
|
||||
* @param code the callback to use
|
||||
*/
|
||||
public void add(Consumer<Builder> code) {
|
||||
Builder builder = CodeBlock.builder();
|
||||
code.accept(builder);
|
||||
add(builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a code block using the specified formatted String and the specified
|
||||
* arguments.
|
||||
* @param code the code
|
||||
* @param arguments the arguments
|
||||
* @see Builder#add(String, Object...)
|
||||
*/
|
||||
public void add(String code, Object... arguments) {
|
||||
add(CodeBlock.of(code, arguments));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link CodeBlock} that joins the different blocks registered in
|
||||
* this instance with the specified delimiter.
|
||||
* @param delimiter the delimiter to use (not {@literal null})
|
||||
* @return a {@link CodeBlock} joining the blocks of this instance with the
|
||||
* specified {@code delimiter}
|
||||
* @see CodeBlock#join(Iterable, String)
|
||||
*/
|
||||
public CodeBlock join(String delimiter) {
|
||||
return CodeBlock.join(this.codeBlocks, delimiter);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,232 +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.javapoet.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.CodeBlock.Builder;
|
||||
|
||||
|
||||
/**
|
||||
* A {@link CodeBlock} wrapper for multiple statements.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class MultiStatement {
|
||||
|
||||
private final List<Statement> statements = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Specify if this instance is empty.
|
||||
* @return {@code true} if no statement is registered, {@code false} otherwise
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return this.statements.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the statements defined in the specified multi statement to this instance.
|
||||
* @param multiStatement the statements to add
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public MultiStatement add(MultiStatement multiStatement) {
|
||||
this.statements.addAll(multiStatement.statements);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified {@link CodeBlock codeblock} rendered as-is.
|
||||
* @param codeBlock the code block to add
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @see #addStatement(CodeBlock) to add a code block that represents
|
||||
* a statement
|
||||
*/
|
||||
public MultiStatement add(CodeBlock codeBlock) {
|
||||
this.statements.add(Statement.of(codeBlock));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link CodeBlock} rendered as-is using the specified callback.
|
||||
* @param code the callback to use
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @see #addStatement(CodeBlock) to add a code block that represents
|
||||
* a statement
|
||||
*/
|
||||
public MultiStatement add(Consumer<Builder> code) {
|
||||
CodeBlock.Builder builder = CodeBlock.builder();
|
||||
code.accept(builder);
|
||||
add(builder.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a statement.
|
||||
* @param statement the statement to add
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public MultiStatement addStatement(CodeBlock statement) {
|
||||
this.statements.add(Statement.ofStatement(statement));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a statement using the specified callback.
|
||||
* @param code the callback to use
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public MultiStatement addStatement(Consumer<Builder> code) {
|
||||
CodeBlock.Builder builder = CodeBlock.builder();
|
||||
code.accept(builder);
|
||||
return addStatement(builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a statement using the specified formatted String and the specified
|
||||
* arguments.
|
||||
* @param code the code of the statement
|
||||
* @param args the arguments for placeholders
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @see CodeBlock#of(String, Object...)
|
||||
*/
|
||||
public MultiStatement addStatement(String code, Object... args) {
|
||||
return addStatement(CodeBlock.of(code, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the statements produced from the {@code itemGenerator} applied on the specified
|
||||
* items.
|
||||
* @param items the items to handle, each item is represented as a statement
|
||||
* @param itemGenerator the item generator
|
||||
* @param <T> the type of the item
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public <T> MultiStatement addAll(Iterable<T> items, Function<T, CodeBlock> itemGenerator) {
|
||||
items.forEach(element -> addStatement(itemGenerator.apply(element)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link CodeBlock} that applies all the {@code statements} of
|
||||
* this instance.
|
||||
* @return the code block
|
||||
*/
|
||||
public CodeBlock toCodeBlock() {
|
||||
Builder code = CodeBlock.builder();
|
||||
this.statements.forEach(statement -> statement.add(code));
|
||||
return code.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link CodeBlock} that applies all the {@code statements} of this
|
||||
* instance. If only one statement is available, it is not completed using the
|
||||
* {@code ;} termination so that it can be used in the context of a lambda.
|
||||
* @return the body of the lambda
|
||||
*/
|
||||
public CodeBlock toLambdaBody() {
|
||||
Builder code = CodeBlock.builder();
|
||||
for (int i = 0; i < this.statements.size(); i++) {
|
||||
Statement statement = this.statements.get(i);
|
||||
statement.contribute(code, this.isMulti(), i == this.statements.size() - 1);
|
||||
}
|
||||
return code.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link CodeBlock} that applies all the {@code statements} of this
|
||||
* instance in the context of a lambda.
|
||||
* @param lambdaParameter the parameter(s) of the lambda, must end with {@code ->}
|
||||
* @return a lambda whose body is generated from the statements of this instance
|
||||
*/
|
||||
public CodeBlock toLambda(CodeBlock lambdaParameter) {
|
||||
Builder code = CodeBlock.builder();
|
||||
code.add(lambdaParameter);
|
||||
if (isMulti()) {
|
||||
code.beginControlFlow("");
|
||||
}
|
||||
else {
|
||||
code.add(" ");
|
||||
}
|
||||
code.add(toLambdaBody());
|
||||
if (isMulti()) {
|
||||
code.add("\n").unindent().add("}");
|
||||
}
|
||||
return code.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link CodeBlock} that applies all the {@code statements} of this
|
||||
* instance in the context of a lambda.
|
||||
* @param lambdaParameter the parameter(s) of the lambda, must end with {@code ->}
|
||||
* @return a lambda whose body is generated from the statements of this instance
|
||||
*/
|
||||
public CodeBlock toLambda(String lambdaParameter) {
|
||||
return toLambda(CodeBlock.of(lambdaParameter));
|
||||
}
|
||||
|
||||
private boolean isMulti() {
|
||||
return this.statements.size() > 1;
|
||||
}
|
||||
|
||||
|
||||
private static class Statement {
|
||||
|
||||
private final CodeBlock codeBlock;
|
||||
|
||||
private final boolean addStatementTermination;
|
||||
|
||||
Statement(CodeBlock codeBlock, boolean addStatementTermination) {
|
||||
this.codeBlock = codeBlock;
|
||||
this.addStatementTermination = addStatementTermination;
|
||||
}
|
||||
|
||||
void add(CodeBlock.Builder code) {
|
||||
code.add(this.codeBlock);
|
||||
if (this.addStatementTermination) {
|
||||
code.add(";\n");
|
||||
}
|
||||
}
|
||||
|
||||
void contribute(CodeBlock.Builder code, boolean multi, boolean isLastStatement) {
|
||||
code.add(this.codeBlock);
|
||||
if (this.addStatementTermination) {
|
||||
if (!isLastStatement) {
|
||||
code.add(";\n");
|
||||
}
|
||||
else if (multi) {
|
||||
code.add(";");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Statement ofStatement(CodeBlock codeBlock) {
|
||||
return new Statement(codeBlock, true);
|
||||
}
|
||||
|
||||
static Statement of(CodeBlock codeBlock) {
|
||||
return new Statement(codeBlock, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
/**
|
||||
* Support classes for JavaPoet usage.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.javapoet.support;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
|
@ -1,46 +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.aot.generator;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultCodeContribution}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class DefaultCodeContributionTests {
|
||||
|
||||
@Test
|
||||
void newCodeContributionIsEmpty() {
|
||||
CodeContribution contribution = new DefaultCodeContribution(new RuntimeHints());
|
||||
assertThat(contribution.statements().isEmpty()).isTrue();
|
||||
assertThat(contribution.protectedAccess().isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void codeContributionReusesRuntimeHints() {
|
||||
RuntimeHints runtimeHints = new RuntimeHints();
|
||||
CodeContribution contribution = new DefaultCodeContribution(runtimeHints);
|
||||
assertThat(contribution.runtimeHints()).isSameAs(runtimeHints);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,100 +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.aot.generator;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.javapoet.ClassName;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultGeneratedTypeContext}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class DefaultGeneratedTypeContextTests {
|
||||
|
||||
@Test
|
||||
void runtimeHints() {
|
||||
DefaultGeneratedTypeContext context = createComAcmeContext();
|
||||
assertThat(context.runtimeHints()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getGeneratedTypeMatchesGetMainGeneratedTypeForMainPackage() {
|
||||
DefaultGeneratedTypeContext context = createComAcmeContext();
|
||||
assertThat(context.getMainGeneratedType().getClassName()).isEqualTo(ClassName.get("com.acme", "Main"));
|
||||
assertThat(context.getGeneratedType("com.acme")).isSameAs(context.getMainGeneratedType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMainGeneratedTypeIsLazilyCreated() {
|
||||
DefaultGeneratedTypeContext context = createComAcmeContext();
|
||||
assertThat(context.hasGeneratedType("com.acme")).isFalse();
|
||||
context.getMainGeneratedType();
|
||||
assertThat(context.hasGeneratedType("com.acme")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getGeneratedTypeRegisterInstance() {
|
||||
DefaultGeneratedTypeContext context = createComAcmeContext();
|
||||
assertThat(context.hasGeneratedType("com.example")).isFalse();
|
||||
GeneratedType generatedType = context.getGeneratedType("com.example");
|
||||
assertThat(generatedType).isNotNull();
|
||||
assertThat(generatedType.getClassName().simpleName()).isEqualTo("Main");
|
||||
assertThat(context.hasGeneratedType("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getGeneratedTypeReuseInstance() {
|
||||
DefaultGeneratedTypeContext context = createComAcmeContext();
|
||||
GeneratedType generatedType = context.getGeneratedType("com.example");
|
||||
assertThat(generatedType.getClassName().packageName()).isEqualTo("com.example");
|
||||
assertThat(context.getGeneratedType("com.example")).isSameAs(generatedType);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toJavaFilesWithNoTypeIsEmpty() {
|
||||
DefaultGeneratedTypeContext writerContext = createComAcmeContext();
|
||||
assertThat(writerContext.toJavaFiles()).hasSize(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toJavaFilesWithDefaultTypeIsAddedLazily() {
|
||||
DefaultGeneratedTypeContext writerContext = createComAcmeContext();
|
||||
writerContext.getMainGeneratedType();
|
||||
assertThat(writerContext.toJavaFiles()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toJavaFilesWithDefaultTypeAndAdditionaTypes() {
|
||||
DefaultGeneratedTypeContext writerContext = createComAcmeContext();
|
||||
writerContext.getGeneratedType("com.example");
|
||||
writerContext.getGeneratedType("com.another");
|
||||
writerContext.getGeneratedType("com.another.another");
|
||||
assertThat(writerContext.toJavaFiles()).hasSize(3);
|
||||
}
|
||||
|
||||
private DefaultGeneratedTypeContext createComAcmeContext() {
|
||||
return new DefaultGeneratedTypeContext("com.acme", packageName ->
|
||||
GeneratedType.of(ClassName.get(packageName, "Main"), type -> type.addModifiers(Modifier.PUBLIC)));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,82 +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.aot.generator;
|
||||
|
||||
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.aot.hint.TypeReference;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link GeneratedTypeReference}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class GeneratedTypeReferenceTests {
|
||||
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("reflectionTargetNames")
|
||||
void hasSuitableReflectionTargetName(TypeReference typeReference, String binaryName) {
|
||||
assertThat(typeReference.getName()).isEqualTo(binaryName);
|
||||
}
|
||||
|
||||
static Stream<Arguments> reflectionTargetNames() {
|
||||
return Stream.of(
|
||||
Arguments.of(GeneratedTypeReference.of(ClassName.get("com.example", "Test")), "com.example.Test"),
|
||||
Arguments.of(GeneratedTypeReference.of(ClassName.get("com.example", "Test", "Inner")), "com.example.Test$Inner"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithClassName() {
|
||||
GeneratedTypeReference typeReference = GeneratedTypeReference.of(
|
||||
ClassName.get("com.example", "Test"));
|
||||
assertThat(typeReference.getPackageName()).isEqualTo("com.example");
|
||||
assertThat(typeReference.getSimpleName()).isEqualTo("Test");
|
||||
assertThat(typeReference.getCanonicalName()).isEqualTo("com.example.Test");
|
||||
assertThat(typeReference.getEnclosingType()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithClassNameAndParent() {
|
||||
GeneratedTypeReference typeReference = GeneratedTypeReference.of(
|
||||
ClassName.get("com.example", "Test").nestedClass("Nested"));
|
||||
assertThat(typeReference.getPackageName()).isEqualTo("com.example");
|
||||
assertThat(typeReference.getSimpleName()).isEqualTo("Nested");
|
||||
assertThat(typeReference.getCanonicalName()).isEqualTo("com.example.Test.Nested");
|
||||
assertThat(typeReference.getEnclosingType()).satisfies(parentTypeReference -> {
|
||||
assertThat(parentTypeReference.getPackageName()).isEqualTo("com.example");
|
||||
assertThat(parentTypeReference.getSimpleName()).isEqualTo("Test");
|
||||
assertThat(parentTypeReference.getCanonicalName()).isEqualTo("com.example.Test");
|
||||
assertThat(parentTypeReference.getEnclosingType()).isNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsWithIdenticalCanonicalNameIsTrue() {
|
||||
assertThat(GeneratedTypeReference.of(ClassName.get("java.lang", "String")))
|
||||
.isEqualTo(TypeReference.of(String.class));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,129 +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.aot.generator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.FieldSpec;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.TypeName;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link GeneratedType}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class GeneratedTypeTests {
|
||||
|
||||
private static final ClassName TEST_CLASS_NAME = ClassName.get("com.acme", "Test");
|
||||
|
||||
@Test
|
||||
void className() {
|
||||
GeneratedType generatedType = new GeneratedType(TEST_CLASS_NAME,
|
||||
type -> type.addModifiers(Modifier.STATIC));
|
||||
assertThat(generatedType.getClassName()).isEqualTo(TEST_CLASS_NAME);
|
||||
assertThat(generateCode(generatedType)).contains("static class Test {");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithCustomField() {
|
||||
GeneratedType generatedType = new GeneratedType(TEST_CLASS_NAME,
|
||||
type -> type.addField(FieldSpec.builder(TypeName.BOOLEAN, "enabled").build()));
|
||||
assertThat(generateCode(generatedType)).contains("boolean enabled;");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizeType() {
|
||||
GeneratedType generatedType = createTestGeneratedType();
|
||||
generatedType.customizeType(type -> type.addJavadoc("Test javadoc."))
|
||||
.customizeType(type -> type.addJavadoc(" Another test javadoc"));
|
||||
assertThat(generateCode(generatedType)).containsSequence(
|
||||
"/**\n",
|
||||
" * Test javadoc. Another test javadoc\n",
|
||||
" */");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addMethod() {
|
||||
GeneratedType generatedType = createTestGeneratedType();
|
||||
generatedType.addMethod(MethodSpec.methodBuilder("test").returns(Integer.class)
|
||||
.addCode(CodeBlock.of("return 42;")));
|
||||
assertThat(generateCode(generatedType)).containsSequence(
|
||||
"\tInteger test() {\n",
|
||||
"\t\treturn 42;\n",
|
||||
"\t}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addMultipleMethods() {
|
||||
GeneratedType generatedType = createTestGeneratedType();
|
||||
generatedType.addMethod(MethodSpec.methodBuilder("first"));
|
||||
generatedType.addMethod(MethodSpec.methodBuilder("second"));
|
||||
assertThat(generateCode(generatedType))
|
||||
.containsSequence("\tvoid first() {\n", "\t}")
|
||||
.containsSequence("\tvoid second() {\n", "\t}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addSimilarMethodGenerateUniqueNames() {
|
||||
GeneratedType generatedType = createTestGeneratedType();
|
||||
MethodSpec firstMethod = generatedType.addMethod(MethodSpec.methodBuilder("test"));
|
||||
MethodSpec secondMethod = generatedType.addMethod(MethodSpec.methodBuilder("test"));
|
||||
MethodSpec thirdMethod = generatedType.addMethod(MethodSpec.methodBuilder("test"));
|
||||
assertThat(firstMethod.name).isEqualTo("test");
|
||||
assertThat(secondMethod.name).isEqualTo("test_");
|
||||
assertThat(thirdMethod.name).isEqualTo("test__");
|
||||
assertThat(generateCode(generatedType))
|
||||
.containsSequence("\tvoid test() {\n", "\t}")
|
||||
.containsSequence("\tvoid test_() {\n", "\t}")
|
||||
.containsSequence("\tvoid test__() {\n", "\t}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addMethodWithSameNameAndDifferentArgumentsDoesNotChangeName() {
|
||||
GeneratedType generatedType = createTestGeneratedType();
|
||||
generatedType.addMethod(MethodSpec.methodBuilder("test"));
|
||||
MethodSpec secondMethod = generatedType.addMethod(MethodSpec.methodBuilder("test")
|
||||
.addParameter(String.class, "param"));
|
||||
assertThat(secondMethod.name).isEqualTo("test");
|
||||
}
|
||||
|
||||
private GeneratedType createTestGeneratedType() {
|
||||
return GeneratedType.of(TEST_CLASS_NAME);
|
||||
}
|
||||
|
||||
private String generateCode(GeneratedType generatedType) {
|
||||
try {
|
||||
StringWriter out = new StringWriter();
|
||||
generatedType.toJavaFile().writeTo(out);
|
||||
return out.toString();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,273 +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.aot.generator;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generator.ProtectedAccess.Options;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.testfixture.aot.generator.visibility.ProtectedGenericParameter;
|
||||
import org.springframework.core.testfixture.aot.generator.visibility.ProtectedParameter;
|
||||
import org.springframework.core.testfixture.aot.generator.visibility.PublicFactoryBean;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link ProtectedAccess}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ProtectedAccessTests {
|
||||
|
||||
public static final Options DEFAULT_OPTIONS = Options.defaults().build();
|
||||
|
||||
private final ProtectedAccess protectedAccess = new ProtectedAccess();
|
||||
|
||||
@Test
|
||||
void analyzeWithPublicConstructor() throws NoSuchMethodException {
|
||||
this.protectedAccess.analyze(PublicClass.class.getConstructor(), DEFAULT_OPTIONS);
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateConstructor() {
|
||||
this.protectedAccess.analyze(ProtectedAccessor.class.getDeclaredConstructors()[0],
|
||||
DEFAULT_OPTIONS);
|
||||
assertPrivilegedAccess(ProtectedAccessor.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateConstructorAndReflectionEnabled() {
|
||||
Constructor<?> constructor = ProtectedAccessor.class.getDeclaredConstructors()[0];
|
||||
this.protectedAccess.analyze(constructor,
|
||||
Options.defaults().useReflection(member -> member.equals(constructor)).build());
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateClass() {
|
||||
this.protectedAccess.analyze(ProtectedClass.class.getDeclaredConstructors()[0], DEFAULT_OPTIONS);
|
||||
assertPrivilegedAccess(ProtectedClass.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateDeclaringType() {
|
||||
this.protectedAccess.analyze(method(ProtectedClass.class, "stringBean"), DEFAULT_OPTIONS);
|
||||
assertPrivilegedAccess(ProtectedClass.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateConstructorParameter() {
|
||||
this.protectedAccess.analyze(ProtectedParameter.class.getConstructors()[0], DEFAULT_OPTIONS);
|
||||
assertPrivilegedAccess(ProtectedParameter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateConstructorGenericParameter() {
|
||||
this.protectedAccess.analyze(ProtectedGenericParameter.class.getConstructors()[0], DEFAULT_OPTIONS);
|
||||
assertPrivilegedAccess(ProtectedParameter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateMethod() {
|
||||
this.protectedAccess.analyze(method(PublicClass.class, "getProtectedMethod"), DEFAULT_OPTIONS);
|
||||
assertPrivilegedAccess(PublicClass.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateMethodAndReflectionEnabled() {
|
||||
this.protectedAccess.analyze(method(PublicClass.class, "getProtectedMethod"),
|
||||
Options.defaults().useReflection(member -> !Modifier.isPublic(member.getModifiers())).build());
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateMethodReturnType() {
|
||||
this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedReturnType"), DEFAULT_OPTIONS);
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateMethodReturnTypeAndAssignReturnTypeFunction() {
|
||||
this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedReturnType"),
|
||||
Options.defaults().assignReturnType(member -> false).build());
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateMethodReturnTypeAndAssignReturnType() {
|
||||
this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedReturnType"),
|
||||
Options.defaults().assignReturnType(true).build());
|
||||
assertPrivilegedAccess(ProtectedAccessor.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateMethodParameter() {
|
||||
this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedParameter",
|
||||
ProtectedClass.class), DEFAULT_OPTIONS);
|
||||
assertPrivilegedAccess(ProtectedAccessor.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateField() {
|
||||
this.protectedAccess.analyze(field(PublicClass.class, "protectedField"), DEFAULT_OPTIONS);
|
||||
assertPrivilegedAccess(PublicClass.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateFieldAndReflectionEnabled() {
|
||||
this.protectedAccess.analyze(field(PublicClass.class, "protectedField"),
|
||||
Options.defaults().useReflection(member -> true).build());
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPublicFieldAndProtectedType() {
|
||||
this.protectedAccess.analyze(field(PublicClass.class, "protectedClassField"), DEFAULT_OPTIONS);
|
||||
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPublicFieldAndProtectedTypeAssigned() {
|
||||
this.protectedAccess.analyze(field(PublicClass.class, "protectedClassField"),
|
||||
Options.defaults().assignReturnType(true).build());
|
||||
assertPrivilegedAccess(ProtectedClass.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithPackagePrivateGenericArgument() {
|
||||
this.protectedAccess.analyze(method(PublicFactoryBean.class, "protectedTypeFactoryBean"),
|
||||
Options.defaults().assignReturnType(true).build());
|
||||
assertPrivilegedAccess(PublicFactoryBean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeTypeWithProtectedGenericArgument() {
|
||||
this.protectedAccess.analyze(PublicFactoryBean.resolveToProtectedGenericParameter());
|
||||
assertPrivilegedAccess(PublicFactoryBean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWithRecursiveType() {
|
||||
assertThat(this.protectedAccess.isProtected(ResolvableType.forClassWithGenerics(
|
||||
SelfReference.class, SelfReference.class))).isEqualTo(SelfReference.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getProtectedPackageWithPublicAccess() throws NoSuchMethodException {
|
||||
this.protectedAccess.analyze(PublicClass.class.getConstructor(), DEFAULT_OPTIONS);
|
||||
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getProtectedPackageWithProtectedAccessInOnePackage() {
|
||||
this.protectedAccess.analyze(method(PublicFactoryBean.class, "protectedTypeFactoryBean"),
|
||||
Options.defaults().assignReturnType(true).build());
|
||||
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example"))
|
||||
.isEqualTo(PublicFactoryBean.class.getPackageName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getProtectedPackageWithProtectedAccessInSeveralPackages() {
|
||||
Method protectedMethodFirstPackage = method(PublicFactoryBean.class, "protectedTypeFactoryBean");
|
||||
Method protectedMethodSecondPackage = method(ProtectedAccessor.class, "methodWithProtectedParameter",
|
||||
ProtectedClass.class);
|
||||
this.protectedAccess.analyze(protectedMethodFirstPackage,
|
||||
Options.defaults().assignReturnType(true).build());
|
||||
this.protectedAccess.analyze(protectedMethodSecondPackage, DEFAULT_OPTIONS);
|
||||
assertThatThrownBy(() -> this.protectedAccess.getPrivilegedPackageName("com.example"))
|
||||
.isInstanceOfSatisfying(ProtectedAccessException.class, ex ->
|
||||
assertThat(ex.getProtectedElements().stream().map(ProtectedElement::getMember))
|
||||
.containsOnly(protectedMethodFirstPackage, protectedMethodSecondPackage));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private static Method method(Class<?> type, String name, Class<?>... parameterTypes) {
|
||||
Method method = ReflectionUtils.findMethod(type, name, parameterTypes);
|
||||
assertThat(method).isNotNull();
|
||||
return method;
|
||||
}
|
||||
|
||||
private static Field field(Class<?> type, String name) {
|
||||
Field field = ReflectionUtils.findField(type, name);
|
||||
assertThat(field).isNotNull();
|
||||
return field;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class PublicClass {
|
||||
|
||||
String protectedField;
|
||||
|
||||
public ProtectedClass protectedClassField;
|
||||
|
||||
String getProtectedMethod() {
|
||||
return this.protectedField;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class ProtectedAccessor {
|
||||
|
||||
ProtectedAccessor() {
|
||||
}
|
||||
|
||||
public String methodWithProtectedParameter(ProtectedClass type) {
|
||||
return "test";
|
||||
}
|
||||
|
||||
public ProtectedClass methodWithProtectedReturnType() {
|
||||
return new ProtectedClass();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class ProtectedClass {
|
||||
|
||||
public ProtectedClass() {
|
||||
}
|
||||
|
||||
public String stringBean() {
|
||||
return "public";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class SelfReference<T extends SelfReference<T>> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
T getThis() {
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,62 +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.aot.generator;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.javapoet.support.CodeSnippet;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ResolvableTypeGenerator}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ResolvableTypeGeneratorTests {
|
||||
|
||||
@Test
|
||||
void generateTypeForResolvableTypeWithGenericParameter() {
|
||||
assertThat(generateTypeFor(
|
||||
ResolvableType.forClassWithGenerics(Function.class,
|
||||
ResolvableType.forClassWithGenerics(Supplier.class, String.class),
|
||||
ResolvableType.forClassWithGenerics(Supplier.class, Integer.class))))
|
||||
.isEqualTo("ResolvableType.forClassWithGenerics(Function.class, "
|
||||
+ "ResolvableType.forClassWithGenerics(Supplier.class, String.class), "
|
||||
+ "ResolvableType.forClassWithGenerics(Supplier.class, Integer.class))");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateTypeForResolvableTypeWithMixedParameter() {
|
||||
assertThat(generateTypeFor(
|
||||
ResolvableType.forClassWithGenerics(Function.class,
|
||||
ResolvableType.forClassWithGenerics(Supplier.class, String.class),
|
||||
ResolvableType.forClass(Integer.class))))
|
||||
.isEqualTo("ResolvableType.forClassWithGenerics(Function.class, "
|
||||
+ "ResolvableType.forClassWithGenerics(Supplier.class, String.class), "
|
||||
+ "ResolvableType.forClass(Integer.class))");
|
||||
}
|
||||
|
||||
private String generateTypeFor(ResolvableType type) {
|
||||
return CodeSnippet.process(new ResolvableTypeGenerator().generateTypeFor(type));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,75 +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.javapoet.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link CodeSnippet}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class CodeSnippetTests {
|
||||
|
||||
@Test
|
||||
void snippetUsesTabs() {
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
code.beginControlFlow("if (condition)");
|
||||
code.addStatement("bean.doThis()");
|
||||
code.endControlFlow();
|
||||
CodeSnippet codeSnippet = CodeSnippet.of(code.build());
|
||||
assertThat(codeSnippet.getSnippet()).isEqualTo("""
|
||||
if (condition) {
|
||||
bean.doThis();
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void snippetResolvesImports() {
|
||||
CodeSnippet codeSnippet = CodeSnippet.of(
|
||||
CodeBlock.of("$T list = new $T<>()", List.class, ArrayList.class));
|
||||
assertThat(codeSnippet.getSnippet()).isEqualTo("List list = new ArrayList<>()");
|
||||
assertThat(codeSnippet.hasImport(List.class)).isTrue();
|
||||
assertThat(codeSnippet.hasImport(ArrayList.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeIndent() {
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
code.beginControlFlow("if (condition)");
|
||||
code.addStatement("doStuff()");
|
||||
code.endControlFlow();
|
||||
CodeSnippet snippet = CodeSnippet.of(code.build());
|
||||
assertThat(snippet.getSnippet().lines()).contains("\tdoStuff();");
|
||||
assertThat(snippet.removeIndent(1).getSnippet().lines()).contains("doStuff();");
|
||||
}
|
||||
|
||||
@Test
|
||||
void processProvidesSnippet() {
|
||||
assertThat(CodeSnippet.process(code -> code.add("$T list;", List.class)))
|
||||
.isEqualTo("List list;");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,61 +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.javapoet.support;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link MultiCodeBlock}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class MultiCodeBlockTests {
|
||||
|
||||
@Test
|
||||
void joinWithNoElement() {
|
||||
MultiCodeBlock multi = new MultiCodeBlock();
|
||||
assertThat(multi.join(", ").isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void joinWithEmptyElement() {
|
||||
MultiCodeBlock multi = new MultiCodeBlock();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> multi.add(CodeBlock.builder().build()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void joinWithSingleElement() {
|
||||
MultiCodeBlock multi = new MultiCodeBlock();
|
||||
multi.add(CodeBlock.of("$S", "Hello"));
|
||||
assertThat(multi.join(", ")).hasToString("\"Hello\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void joinWithSeveralElement() {
|
||||
MultiCodeBlock multi = new MultiCodeBlock();
|
||||
multi.add(CodeBlock.of("$S", "Hello"));
|
||||
multi.add(code -> code.add("42"));
|
||||
multi.add("null");
|
||||
assertThat(multi.join(", ")).hasToString("\"Hello\", 42, null");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,166 +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.javapoet.support;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link MultiStatement}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class MultiStatementTests {
|
||||
|
||||
@Test
|
||||
void isEmptyWithNoStatement() {
|
||||
assertThat(new MultiStatement().isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmptyWithStatement() {
|
||||
MultiStatement statements = new MultiStatement();
|
||||
statements.addStatement(CodeBlock.of("int i = 0"));
|
||||
assertThat(statements.isEmpty()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleStatementCodeBlock() {
|
||||
MultiStatement statements = new MultiStatement();
|
||||
statements.addStatement("field.method($S)", "hello");
|
||||
CodeBlock codeBlock = statements.toCodeBlock();
|
||||
assertThat(codeBlock.toString()).isEqualTo("""
|
||||
field.method("hello");
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiStatementsCodeBlock() {
|
||||
MultiStatement statements = new MultiStatement();
|
||||
statements.addStatement("field.method($S)", "hello");
|
||||
statements.addStatement("field.another($S)", "test");
|
||||
CodeBlock codeBlock = statements.toCodeBlock();
|
||||
assertThat(codeBlock.toString()).isEqualTo("""
|
||||
field.method("hello");
|
||||
field.another("test");
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleStatementLambdaBody() {
|
||||
MultiStatement statements = new MultiStatement();
|
||||
statements.addStatement("field.method($S)", "hello");
|
||||
CodeBlock codeBlock = statements.toLambdaBody();
|
||||
assertThat(codeBlock.toString()).isEqualTo("field.method(\"hello\")");
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleStatementWithCallbackLambdaBody() {
|
||||
MultiStatement statements = new MultiStatement();
|
||||
statements.addStatement(code -> code.add("field.method($S)", "hello"));
|
||||
CodeBlock codeBlock = statements.toLambdaBody();
|
||||
assertThat(codeBlock.toString()).isEqualTo("field.method(\"hello\")");
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleStatementWithCodeBlockLambdaBody() {
|
||||
MultiStatement statements = new MultiStatement();
|
||||
statements.addStatement(CodeBlock.of("field.method($S)", "hello"));
|
||||
CodeBlock codeBlock = statements.toLambdaBody();
|
||||
assertThat(codeBlock.toString()).isEqualTo("field.method(\"hello\")");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiStatementsLambdaBody() {
|
||||
MultiStatement statements = new MultiStatement();
|
||||
statements.addStatement("field.method($S)", "hello");
|
||||
statements.addStatement("field.anotherMethod($S)", "hello");
|
||||
CodeBlock codeBlock = statements.toLambdaBody();
|
||||
assertThat(codeBlock.toString()).isEqualTo("""
|
||||
field.method("hello");
|
||||
field.anotherMethod("hello");""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiStatementsWithCodeBlockRenderedAsIsLambdaBody() {
|
||||
MultiStatement statements = new MultiStatement();
|
||||
statements.addStatement("field.method($S)", "hello");
|
||||
statements.add(CodeBlock.of(("// Hello\n")));
|
||||
statements.add(code -> code.add("// World\n"));
|
||||
statements.addStatement("field.anotherMethod($S)", "hello");
|
||||
CodeBlock codeBlock = statements.toLambdaBody();
|
||||
assertThat(codeBlock.toString()).isEqualTo("""
|
||||
field.method("hello");
|
||||
// Hello
|
||||
// World
|
||||
field.anotherMethod("hello");""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleStatementWithLambda() {
|
||||
MultiStatement statements = new MultiStatement();
|
||||
statements.addStatement("field.method($S)", "hello");
|
||||
CodeBlock codeBlock = statements.toLambda(CodeBlock.of("() ->"));
|
||||
assertThat(codeBlock.toString()).isEqualTo("() -> field.method(\"hello\")");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiStatementsWithLambda() {
|
||||
MultiStatement statements = new MultiStatement();
|
||||
statements.addStatement("field.method($S)", "hello");
|
||||
statements.addStatement("field.anotherMethod($S)", "hello");
|
||||
CodeBlock codeBlock = statements.toLambda(CodeBlock.of("() ->"));
|
||||
assertThat(codeBlock.toString().lines()).containsExactly(
|
||||
"() -> {",
|
||||
" field.method(\"hello\");",
|
||||
" field.anotherMethod(\"hello\");",
|
||||
"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiStatementsWithAddAllAndLambda() {
|
||||
MultiStatement statements = new MultiStatement();
|
||||
statements.addAll(List.of(0, 1, 2),
|
||||
index -> CodeBlock.of("field[$L] = $S", index, "hello"));
|
||||
CodeBlock codeBlock = statements.toLambda("() ->");
|
||||
assertThat(codeBlock.toString().lines()).containsExactly(
|
||||
"() -> {",
|
||||
" field[0] = \"hello\";",
|
||||
" field[1] = \"hello\";",
|
||||
" field[2] = \"hello\";",
|
||||
"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addWithAnotherMultiStatement() {
|
||||
MultiStatement statements = new MultiStatement();
|
||||
statements.addStatement(CodeBlock.of("test.invoke()"));
|
||||
MultiStatement another = new MultiStatement();
|
||||
another.addStatement(CodeBlock.of("test.another()"));
|
||||
statements.add(another);
|
||||
assertThat(statements.toCodeBlock().toString()).isEqualTo("""
|
||||
test.invoke();
|
||||
test.another();
|
||||
""");
|
||||
}
|
||||
|
||||
}
|
|
@ -19,7 +19,6 @@ package org.springframework.orm.jpa.support;
|
|||
import java.beans.PropertyDescriptor;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
@ -46,8 +45,6 @@ import org.springframework.aot.generate.GenerationContext;
|
|||
import org.springframework.aot.generate.MethodGenerator;
|
||||
import org.springframework.aot.generate.MethodNameGenerator;
|
||||
import org.springframework.aot.generate.MethodReference;
|
||||
import org.springframework.aot.generator.CodeContribution;
|
||||
import org.springframework.aot.generator.ProtectedAccess.Options;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.PropertyValues;
|
||||
|
@ -66,9 +63,6 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
|||
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.NamedBeanHolder;
|
||||
import org.springframework.beans.factory.generator.AotContributingBeanPostProcessor;
|
||||
import org.springframework.beans.factory.generator.BeanFieldGenerator;
|
||||
import org.springframework.beans.factory.generator.BeanInstantiationContribution;
|
||||
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
|
@ -81,7 +75,6 @@ import org.springframework.javapoet.CodeBlock;
|
|||
import org.springframework.javapoet.JavaFile;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.TypeSpec;
|
||||
import org.springframework.javapoet.support.MultiStatement;
|
||||
import org.springframework.jndi.JndiLocatorDelegate;
|
||||
import org.springframework.jndi.JndiTemplate;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
@ -198,9 +191,8 @@ import org.springframework.util.StringUtils;
|
|||
* @see jakarta.persistence.PersistenceContext
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class PersistenceAnnotationBeanPostProcessor
|
||||
implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor,
|
||||
MergedBeanDefinitionPostProcessor, AotContributingBeanPostProcessor, BeanRegistrationAotProcessor,
|
||||
public class PersistenceAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor,
|
||||
DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, BeanRegistrationAotProcessor,
|
||||
PriorityOrdered, BeanFactoryAware, Serializable {
|
||||
|
||||
@Nullable
|
||||
|
@ -365,16 +357,6 @@ public class PersistenceAnnotationBeanPostProcessor
|
|||
findInjectionMetadata(beanDefinition, beanType, beanName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanInstantiationContribution contribute(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
|
||||
InjectionMetadata metadata = findInjectionMetadata(beanDefinition, beanType, beanName);
|
||||
Collection<InjectedElement> injectedElements = metadata.getInjectedElements();
|
||||
if (!CollectionUtils.isEmpty(injectedElements)) {
|
||||
return new PersistenceAnnotationBeanInstantiationContribution(injectedElements);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||
Class<?> beanClass = registeredBean.getBeanClass();
|
||||
|
@ -783,68 +765,6 @@ public class PersistenceAnnotationBeanPostProcessor
|
|||
}
|
||||
}
|
||||
|
||||
private static final class PersistenceAnnotationBeanInstantiationContribution implements BeanInstantiationContribution {
|
||||
|
||||
private static final BeanFieldGenerator fieldGenerator = new BeanFieldGenerator();
|
||||
|
||||
private final Collection<PersistenceElement> injectedElements;
|
||||
|
||||
private PersistenceAnnotationBeanInstantiationContribution(Collection<InjectedElement> injectedElements) {
|
||||
this.injectedElements = injectedElements.stream()
|
||||
.filter(obj -> obj instanceof PersistenceElement)
|
||||
.map(PersistenceElement.class::cast).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(CodeContribution contribution) {
|
||||
this.injectedElements.forEach(element -> {
|
||||
Member member = element.getMember();
|
||||
analyzeMember(contribution, member);
|
||||
injectElement(contribution, element);
|
||||
});
|
||||
}
|
||||
|
||||
private void analyzeMember(CodeContribution contribution, Member member) {
|
||||
if (member instanceof Method) {
|
||||
contribution.protectedAccess().analyze(member, Options.defaults().build());
|
||||
}
|
||||
else if (member instanceof Field field) {
|
||||
contribution.protectedAccess().analyze(member, BeanFieldGenerator.FIELD_OPTIONS);
|
||||
if (Modifier.isPrivate(field.getModifiers())) {
|
||||
contribution.runtimeHints().reflection().registerField(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void injectElement(CodeContribution contribution, PersistenceElement element) {
|
||||
MultiStatement statements = contribution.statements();
|
||||
statements.addStatement("$T entityManagerFactory = $T.findEntityManagerFactory(beanFactory, $S)",
|
||||
EntityManagerFactory.class, EntityManagerFactoryUtils.class, element.unitName);
|
||||
boolean requireEntityManager = (element.type != null);
|
||||
if (requireEntityManager) {
|
||||
Properties persistenceProperties = element.properties;
|
||||
boolean hasPersistenceProperties = persistenceProperties != null && !persistenceProperties.isEmpty();
|
||||
if (hasPersistenceProperties) {
|
||||
statements.addStatement("$T persistenceProperties = new Properties()", Properties.class);
|
||||
persistenceProperties.stringPropertyNames().stream().sorted(String::compareTo).forEach(propertyName ->
|
||||
statements.addStatement("persistenceProperties.put($S, $S)",
|
||||
propertyName, persistenceProperties.getProperty(propertyName)));
|
||||
}
|
||||
statements.addStatement("$T entityManager = $T.createSharedEntityManager(entityManagerFactory, $L, $L)",
|
||||
EntityManager.class, SharedEntityManagerCreator.class, (hasPersistenceProperties) ? "persistenceProperties" : null, element.synchronizedWithTransaction);
|
||||
}
|
||||
Member member = element.getMember();
|
||||
CodeBlock value = (requireEntityManager) ? CodeBlock.of("entityManager") : CodeBlock.of("entityManagerFactory");
|
||||
if (member instanceof Field field) {
|
||||
statements.add(fieldGenerator.generateSetValue("bean", field, value));
|
||||
}
|
||||
else {
|
||||
statements.addStatement("bean.$L($L)", member.getName(), value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class AotContribution implements BeanRegistrationAotContribution {
|
||||
|
||||
|
|
|
@ -1,227 +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.orm.jpa.support;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import jakarta.persistence.PersistenceProperty;
|
||||
import jakarta.persistence.PersistenceUnit;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generator.CodeContribution;
|
||||
import org.springframework.aot.generator.DefaultCodeContribution;
|
||||
import org.springframework.aot.generator.DefaultGeneratedTypeContext;
|
||||
import org.springframework.aot.generator.GeneratedType;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.aot.test.generator.compile.CompileWithTargetClassAccess;
|
||||
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.generator.BeanInstantiationContribution;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.generator.ApplicationContextAotGenerator;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.javapoet.JavaFile;
|
||||
import org.springframework.javapoet.support.CodeSnippet;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link PersistenceAnnotationBeanPostProcessor}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class PersistenceAnnotationBeanPostProcessorTests {
|
||||
|
||||
@Test
|
||||
void contributeForPersistenceUnitOnPublicField() {
|
||||
CodeContribution contribution = contribute(DefaultPersistenceUnitField.class);
|
||||
assertThat(contribution).isNotNull();
|
||||
assertThat(contribution.runtimeHints().reflection().typeHints()).isEmpty();
|
||||
assertThat(CodeSnippet.process(contribution.statements().toCodeBlock())).isEqualTo("""
|
||||
EntityManagerFactory entityManagerFactory = EntityManagerFactoryUtils.findEntityManagerFactory(beanFactory, "");
|
||||
bean.emf = entityManagerFactory;
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributeForPersistenceUnitOnPublicSetter() {
|
||||
CodeContribution contribution = contribute(DefaultPersistenceUnitMethod.class);
|
||||
assertThat(contribution).isNotNull();
|
||||
assertThat(contribution.runtimeHints().reflection().typeHints()).isEmpty();
|
||||
assertThat(CodeSnippet.process(contribution.statements().toCodeBlock())).isEqualTo("""
|
||||
EntityManagerFactory entityManagerFactory = EntityManagerFactoryUtils.findEntityManagerFactory(beanFactory, "");
|
||||
bean.setEmf(entityManagerFactory);
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributeForPersistenceUnitWithCustomUnitOnPublicSetter() {
|
||||
CodeContribution contribution = contribute(CustomUnitNamePublicPersistenceUnitMethod.class);
|
||||
assertThat(contribution).isNotNull();
|
||||
assertThat(contribution.runtimeHints().reflection().typeHints()).isEmpty();
|
||||
assertThat(CodeSnippet.process(contribution.statements().toCodeBlock())).isEqualTo("""
|
||||
EntityManagerFactory entityManagerFactory = EntityManagerFactoryUtils.findEntityManagerFactory(beanFactory, "custom");
|
||||
bean.setEmf(entityManagerFactory);
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributeForPersistenceContextOnPrivateField() {
|
||||
CodeContribution contribution = contribute(DefaultPersistenceContextField.class);
|
||||
assertThat(contribution).isNotNull();
|
||||
assertThat(contribution.runtimeHints().reflection().typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(DefaultPersistenceContextField.class));
|
||||
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint -> {
|
||||
assertThat(fieldHint.getName()).isEqualTo("entityManager");
|
||||
assertThat(fieldHint.isAllowWrite()).isTrue();
|
||||
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse();
|
||||
});
|
||||
});
|
||||
assertThat(CodeSnippet.process(contribution.statements().toCodeBlock())).isEqualTo("""
|
||||
EntityManagerFactory entityManagerFactory = EntityManagerFactoryUtils.findEntityManagerFactory(beanFactory, "");
|
||||
EntityManager entityManager = SharedEntityManagerCreator.createSharedEntityManager(entityManagerFactory, null, true);
|
||||
Field entityManagerField = ReflectionUtils.findField(PersistenceAnnotationBeanPostProcessorTests.DefaultPersistenceContextField.class, "entityManager");
|
||||
ReflectionUtils.makeAccessible(entityManagerField);
|
||||
ReflectionUtils.setField(entityManagerField, bean, entityManager);
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributeForPersistenceContextWithCustomPropertiesOnMethod() {
|
||||
CodeContribution contribution = contribute(CustomPropertiesPersistenceContextMethod.class);
|
||||
assertThat(contribution).isNotNull();
|
||||
assertThat(contribution.runtimeHints().reflection().typeHints()).isEmpty();
|
||||
assertThat(CodeSnippet.process(contribution.statements().toCodeBlock())).isEqualTo("""
|
||||
EntityManagerFactory entityManagerFactory = EntityManagerFactoryUtils.findEntityManagerFactory(beanFactory, "");
|
||||
Properties persistenceProperties = new Properties();
|
||||
persistenceProperties.put("jpa.test", "value");
|
||||
persistenceProperties.put("jpa.test2", "value2");
|
||||
EntityManager entityManager = SharedEntityManagerCreator.createSharedEntityManager(entityManagerFactory, persistenceProperties, true);
|
||||
bean.setEntityManager(entityManager);
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
@CompileWithTargetClassAccess(classes = DefaultPersistenceUnitField.class)
|
||||
void generateEntityManagerFactoryInjection() {
|
||||
GenericApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.registerBeanDefinition("test", new RootBeanDefinition(DefaultPersistenceUnitField.class));
|
||||
|
||||
EntityManagerFactory entityManagerFactory = mock(EntityManagerFactory.class);
|
||||
compile(context, toFreshApplicationContext(() -> {
|
||||
GenericApplicationContext ctx = new GenericApplicationContext();
|
||||
ctx.getDefaultListableBeanFactory().registerSingleton("myEmf", entityManagerFactory);
|
||||
return ctx;
|
||||
}, aotContext -> assertThat(aotContext.getBean("test")).hasFieldOrPropertyWithValue("emf", entityManagerFactory)));
|
||||
}
|
||||
|
||||
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<?> beanType) {
|
||||
PersistenceAnnotationBeanPostProcessor bpp = new PersistenceAnnotationBeanPostProcessor();
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
|
||||
return bpp.contribute(beanDefinition, beanType, "test");
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void compile(GenericApplicationContext applicationContext, Consumer<ApplicationContextInitializer> initializer) {
|
||||
DefaultGeneratedTypeContext generationContext = new DefaultGeneratedTypeContext("com.example",
|
||||
packageName -> GeneratedType.of(ClassName.get(packageName, "Test")));
|
||||
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
|
||||
generator.generateApplicationContext(applicationContext, generationContext);
|
||||
SourceFiles sourceFiles = SourceFiles.none();
|
||||
for (JavaFile javaFile : generationContext.toJavaFiles()) {
|
||||
sourceFiles = sourceFiles.and(SourceFile.of((javaFile::writeTo)));
|
||||
}
|
||||
TestCompiler.forSystem().withSources(sourceFiles).compile(compiled -> {
|
||||
ApplicationContextInitializer instance = compiled.getInstance(ApplicationContextInitializer.class, "com.example.Test");
|
||||
initializer.accept(instance);
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private <T extends GenericApplicationContext> Consumer<ApplicationContextInitializer> toFreshApplicationContext(
|
||||
Supplier<T> applicationContextFactory, Consumer<T> context) {
|
||||
return applicationContextInitializer -> {
|
||||
T applicationContext = applicationContextFactory.get();
|
||||
applicationContextInitializer.initialize(applicationContext);
|
||||
applicationContext.refresh();
|
||||
context.accept(applicationContext);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
static class DefaultPersistenceUnitField {
|
||||
|
||||
@PersistenceUnit
|
||||
public EntityManagerFactory emf;
|
||||
|
||||
}
|
||||
|
||||
static class DefaultPersistenceUnitMethod {
|
||||
|
||||
@PersistenceUnit
|
||||
public void setEmf(EntityManagerFactory emf) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class CustomUnitNamePublicPersistenceUnitMethod {
|
||||
|
||||
@PersistenceUnit(unitName = "custom")
|
||||
public void setEmf(EntityManagerFactory emf) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class DefaultPersistenceContextField {
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager entityManager;
|
||||
|
||||
}
|
||||
|
||||
static class CustomPropertiesPersistenceContextMethod {
|
||||
|
||||
@PersistenceContext(properties = {
|
||||
@PersistenceProperty(name = "jpa.test", value = "value"),
|
||||
@PersistenceProperty(name = "jpa.test2", value = "value2") })
|
||||
public void setEntityManager(EntityManager entityManager) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue