Complete refactor of AOT concepts

Remove the AOT code that now has an alternative API.

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

View File

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

View File

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

View File

@ -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.

View File

@ -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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,255 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,170 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.annotation;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generator.CodeContribution;
import org.springframework.aot.generator.DefaultCodeContribution;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessorTests.ResourceInjectionBean;
import org.springframework.beans.factory.generator.BeanInstantiationContribution;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.core.env.Environment;
import org.springframework.javapoet.support.CodeSnippet;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for code contribution of {@link AutowiredAnnotationBeanPostProcessor}.
*
* @author Stephane Nicoll
*/
class AutowiredAnnotationBeanInstantiationContributionTests {
@Test
void contributeWithPackageProtectedFieldInjection() {
CodeContribution contribution = contribute(PackageProtectedFieldInjectionSample.class);
assertThat(CodeSnippet.process(contribution.statements().toLambdaBody())).isEqualTo("""
instanceContext.field("environment")
.invoke(beanFactory, (attributes) -> bean.environment = attributes.get(0))""");
assertThat(contribution.runtimeHints().reflection().typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(PackageProtectedFieldInjectionSample.class));
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint -> {
assertThat(fieldHint.getName()).isEqualTo("environment");
assertThat(fieldHint.isAllowWrite()).isTrue();
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse();
});
});
assertThat(contribution.protectedAccess().getPrivilegedPackageName("com.example"))
.isEqualTo(PackageProtectedFieldInjectionSample.class.getPackageName());
}
@Test
void contributeWithPrivateFieldInjection() {
CodeContribution contribution = contribute(PrivateFieldInjectionSample.class);
assertThat(CodeSnippet.process(contribution.statements().toLambdaBody())).isEqualTo("""
instanceContext.field("environment")
.invoke(beanFactory, (attributes) -> {
Field environmentField = ReflectionUtils.findField(AutowiredAnnotationBeanInstantiationContributionTests.PrivateFieldInjectionSample.class, "environment");
ReflectionUtils.makeAccessible(environmentField);
ReflectionUtils.setField(environmentField, bean, attributes.get(0));
})""");
assertThat(contribution.runtimeHints().reflection().typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(PrivateFieldInjectionSample.class));
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint -> {
assertThat(fieldHint.getName()).isEqualTo("environment");
assertThat(fieldHint.isAllowWrite()).isTrue();
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse();
});
});
assertThat(contribution.protectedAccess().isAccessible("com.example")).isTrue();
}
@Test
void contributeWithPublicMethodInjection() {
CodeContribution contribution = contribute(PublicMethodInjectionSample.class);
assertThat(CodeSnippet.process(contribution.statements().toLambdaBody())).isEqualTo("""
instanceContext.method("setTestBean", TestBean.class)
.invoke(beanFactory, (attributes) -> bean.setTestBean(attributes.get(0)))""");
assertThat(contribution.runtimeHints().reflection().typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(PublicMethodInjectionSample.class));
assertThat(typeHint.methods()).singleElement().satisfies(methodHint -> {
assertThat(methodHint.getName()).isEqualTo("setTestBean");
assertThat(methodHint.getModes()).contains(ExecutableMode.INTROSPECT);
});
});
assertThat(contribution.protectedAccess().isAccessible("com.example")).isTrue();
}
@Test
void contributeWithInjectionPoints() {
CodeContribution contribution = contribute(ResourceInjectionBean.class);
assertThat(CodeSnippet.process(contribution.statements().toLambdaBody())).isEqualTo("""
instanceContext.field("testBean")
.resolve(beanFactory, false).ifResolved((attributes) -> {
Field testBeanField = ReflectionUtils.findField(AutowiredAnnotationBeanPostProcessorTests.ResourceInjectionBean.class, "testBean");
ReflectionUtils.makeAccessible(testBeanField);
ReflectionUtils.setField(testBeanField, bean, attributes.get(0));
});
instanceContext.method("setTestBean2", TestBean.class)
.invoke(beanFactory, (attributes) -> bean.setTestBean2(attributes.get(0)));""");
assertThat(contribution.runtimeHints().reflection().typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint ->
assertThat(fieldHint.getName()).isEqualTo("testBean"));
assertThat(typeHint.methods()).singleElement().satisfies(methodHint ->
assertThat(methodHint.getName()).isEqualTo("setTestBean2"));
});
assertThat(contribution.protectedAccess().isAccessible("com.example")).isTrue();
}
@Test
void contributeWithoutInjectionPoints() {
BeanInstantiationContribution contributor = createContribution(String.class);
assertThat(contributor).isNull();
}
private DefaultCodeContribution contribute(Class<?> type) {
BeanInstantiationContribution contributor = createContribution(type);
assertThat(contributor).isNotNull();
DefaultCodeContribution contribution = new DefaultCodeContribution(new RuntimeHints());
contributor.applyTo(contribution);
return contribution;
}
@Nullable
private BeanInstantiationContribution createContribution(Class<?> type) {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
RootBeanDefinition beanDefinition = new RootBeanDefinition(type);
return bpp.contribute(beanDefinition, type, "test");
}
public static class PackageProtectedFieldInjectionSample {
@Autowired
Environment environment;
}
public static class PrivateFieldInjectionSample {
@Autowired
@SuppressWarnings("unused")
private Environment environment;
}
public static class PublicMethodInjectionSample {
@Autowired
public void setTestBean(TestBean testBean) {
}
public void setUnrelated(String unrelated) {
}
}
}

View File

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

View File

@ -1,216 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;
import java.util.function.BiPredicate;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.BDDMockito;
import org.mockito.Mockito;
import org.springframework.aot.generator.DefaultGeneratedTypeContext;
import org.springframework.aot.generator.GeneratedType;
import org.springframework.aot.generator.GeneratedTypeContext;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.support.CodeSnippet;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link BeanDefinitionsContribution}.
*
* @author Stephane Nicoll
*/
class BeanDefinitionsContributionTests {
@Test
void loadContributorWithConstructorArgumentOnBeanFactory() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.setBeanClassLoader(new TestSpringFactoriesClassLoader(
"bean-registration-contribution-provider-constructor.factories"));
BeanDefinitionsContribution contribution = new BeanDefinitionsContribution(beanFactory);
assertThat(contribution).extracting("contributionProviders").asList()
.anySatisfy(provider -> assertThat(provider).isInstanceOfSatisfying(TestConstructorBeanRegistrationContributionProvider.class,
testProvider -> assertThat(testProvider.beanFactory).isSameAs(beanFactory)))
.anySatisfy(provider -> assertThat(provider).isInstanceOf(DefaultBeanRegistrationContributionProvider.class))
.hasSize(2);
}
@Test
void contributeThrowsContributionNotFoundIfNoContributionIsAvailable() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition("test", new RootBeanDefinition());
BeanDefinitionsContribution contribution = new BeanDefinitionsContribution(beanFactory,
List.of(Mockito.mock(BeanRegistrationContributionProvider.class)));
BeanFactoryInitialization initialization = new BeanFactoryInitialization(createGenerationContext());
assertThatThrownBy(() -> contribution.applyTo(initialization))
.isInstanceOfSatisfying(BeanRegistrationContributionNotFoundException.class, ex -> {
assertThat(ex.getBeanName()).isEqualTo("test");
assertThat(ex.getBeanDefinition()).isSameAs(beanFactory.getMergedBeanDefinition("test"));
});
}
@Test
void contributeThrowsBeanRegistrationExceptionIfContributionThrowsException() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition("test", new RootBeanDefinition());
BeanFactoryContribution testContribution = Mockito.mock(BeanFactoryContribution.class);
IllegalStateException testException = new IllegalStateException();
BDDMockito.willThrow(testException).given(testContribution).applyTo(ArgumentMatchers.any(BeanFactoryInitialization.class));
BeanDefinitionsContribution contribution = new BeanDefinitionsContribution(beanFactory,
List.of(new TestBeanRegistrationContributionProvider("test", testContribution)));
BeanFactoryInitialization initialization = new BeanFactoryInitialization(createGenerationContext());
assertThatThrownBy(() -> contribution.applyTo(initialization))
.isInstanceOfSatisfying(BeanDefinitionGenerationException.class, ex -> {
assertThat(ex.getBeanName()).isEqualTo("test");
assertThat(ex.getBeanDefinition()).isSameAs(beanFactory.getMergedBeanDefinition("test"));
assertThat(ex.getCause()).isEqualTo(testException);
});
}
@Test
void contributeGeneratesBeanDefinitionsInOrder() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition("counter", BeanDefinitionBuilder
.rootBeanDefinition(Integer.class, "valueOf").addConstructorArgValue(42).getBeanDefinition());
beanFactory.registerBeanDefinition("name", BeanDefinitionBuilder
.rootBeanDefinition(String.class).addConstructorArgValue("Hello").getBeanDefinition());
CodeSnippet code = contribute(beanFactory, createGenerationContext());
assertThat(code.getSnippet()).isEqualTo("""
BeanDefinitionRegistrar.of("counter", Integer.class).withFactoryMethod(Integer.class, "valueOf", int.class)
.instanceSupplier((instanceContext) -> instanceContext.create(beanFactory, (attributes) -> Integer.valueOf(attributes.get(0, int.class)))).customize((bd) -> bd.getConstructorArgumentValues().addIndexedArgumentValue(0, 42)).register(beanFactory);
BeanDefinitionRegistrar.of("name", String.class).withConstructor(String.class)
.instanceSupplier((instanceContext) -> instanceContext.create(beanFactory, (attributes) -> new String(attributes.get(0, String.class)))).customize((bd) -> bd.getConstructorArgumentValues().addIndexedArgumentValue(0, "Hello")).register(beanFactory);
""");
}
@Test
void getBeanDefinitionWithNoUnderlyingContributorReturnFalseByDefault() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
BiPredicate<String, BeanDefinition> excludeFilter = new BeanDefinitionsContribution(beanFactory)
.getBeanDefinitionExcludeFilter();
assertThat(excludeFilter.test("foo", new RootBeanDefinition())).isFalse();
}
@Test
@SuppressWarnings("unchecked")
void getBeanDefinitionExcludeFilterWrapsUnderlyingFilter() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition("bean1", new RootBeanDefinition());
beanFactory.registerBeanDefinition("bean2", new RootBeanDefinition());
BiPredicate<String, BeanDefinition> excludeFilter1 = Mockito.mock(BiPredicate.class);
BDDMockito.given(excludeFilter1.test(ArgumentMatchers.eq("bean1"), ArgumentMatchers.any(BeanDefinition.class))).willReturn(Boolean.TRUE);
BDDMockito.given(excludeFilter1.test(ArgumentMatchers.eq("bean2"), ArgumentMatchers.any(BeanDefinition.class))).willReturn(Boolean.FALSE);
BiPredicate<String, BeanDefinition> excludeFilter2 = Mockito.mock(BiPredicate.class);
BDDMockito.given(excludeFilter2.test(ArgumentMatchers.eq("bean2"), ArgumentMatchers.any(BeanDefinition.class))).willReturn(Boolean.TRUE);
BiPredicate<String, BeanDefinition> excludeFilter = new BeanDefinitionsContribution(beanFactory, List.of(
new TestBeanRegistrationContributionProvider("bean1", mockExcludeFilter(excludeFilter1)),
new TestBeanRegistrationContributionProvider("bean2", mockExcludeFilter(excludeFilter2)))
).getBeanDefinitionExcludeFilter();
assertThat(excludeFilter.test("bean2", new RootBeanDefinition())).isTrue();
Mockito.verify(excludeFilter1).test(ArgumentMatchers.eq("bean2"), ArgumentMatchers.any(BeanDefinition.class));
Mockito.verify(excludeFilter2).test(ArgumentMatchers.eq("bean2"), ArgumentMatchers.any(BeanDefinition.class));
assertThat(excludeFilter.test("bean1", new RootBeanDefinition())).isTrue();
Mockito.verify(excludeFilter1).test(ArgumentMatchers.eq("bean1"), ArgumentMatchers.any(BeanDefinition.class));
Mockito.verifyNoMoreInteractions(excludeFilter2);
}
private CodeSnippet contribute(DefaultListableBeanFactory beanFactory, GeneratedTypeContext generationContext) {
BeanDefinitionsContribution contribution = new BeanDefinitionsContribution(beanFactory);
BeanFactoryInitialization initialization = new BeanFactoryInitialization(generationContext);
contribution.applyTo(initialization);
return CodeSnippet.of(initialization.toCodeBlock());
}
private GeneratedTypeContext createGenerationContext() {
return new DefaultGeneratedTypeContext("com.example", packageName ->
GeneratedType.of(ClassName.get(packageName, "Test")));
}
private BeanFactoryContribution mockExcludeFilter(BiPredicate<String, BeanDefinition> excludeFilter) {
BeanFactoryContribution contribution = Mockito.mock(BeanFactoryContribution.class);
BDDMockito.given(contribution.getBeanDefinitionExcludeFilter()).willReturn(excludeFilter);
return contribution;
}
static class TestBeanRegistrationContributionProvider implements BeanRegistrationContributionProvider {
private final String beanName;
private final BeanFactoryContribution contribution;
public TestBeanRegistrationContributionProvider(String beanName, BeanFactoryContribution contribution) {
this.beanName = beanName;
this.contribution = contribution;
}
@Override
public BeanFactoryContribution getContributionFor(String beanName, RootBeanDefinition beanDefinition) {
return (beanName.equals(this.beanName) ? this.contribution : null);
}
}
static class TestConstructorBeanRegistrationContributionProvider implements BeanRegistrationContributionProvider {
private final ConfigurableListableBeanFactory beanFactory;
TestConstructorBeanRegistrationContributionProvider(ConfigurableListableBeanFactory beanFactory) {
Assert.notNull(beanFactory, "BeanFactory must not be null");
this.beanFactory = beanFactory;
}
@Nullable
@Override
public BeanFactoryContribution getContributionFor(String beanName, RootBeanDefinition beanDefinition) {
return null;
}
}
static class TestSpringFactoriesClassLoader extends ClassLoader {
private final String factoriesName;
TestSpringFactoriesClassLoader(String factoriesName) {
super(BeanDefinitionsContributionTests.class.getClassLoader());
this.factoriesName = factoriesName;
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
if ("META-INF/spring.factories".equals(name)) {
return super.getResources("org/springframework/beans/factory/generator/" + this.factoriesName);
}
return super.getResources(name);
}
}
}

View File

@ -1,79 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import java.lang.reflect.Field;
import org.junit.jupiter.api.Test;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.support.CodeSnippet;
import org.springframework.javapoet.support.MultiStatement;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link BeanFieldGenerator}.
*
* @author Stephane Nicoll
*/
class BeanFieldGeneratorTests {
private final BeanFieldGenerator generator = new BeanFieldGenerator();
@Test
void generateSetFieldWithPublicField() {
MultiStatement statement = this.generator.generateSetValue("bean",
field(SampleBean.class, "one"), CodeBlock.of("$S", "test"));
assertThat(CodeSnippet.process(statement.toCodeBlock())).isEqualTo("""
bean.one = "test";
""");
}
@Test
void generateSetFieldWithPrivateField() {
MultiStatement statement = this.generator.generateSetValue("example",
field(SampleBean.class, "two"), CodeBlock.of("42"));
CodeSnippet code = CodeSnippet.of(statement.toCodeBlock());
assertThat(code.getSnippet()).isEqualTo("""
Field twoField = ReflectionUtils.findField(BeanFieldGeneratorTests.SampleBean.class, "two");
ReflectionUtils.makeAccessible(twoField);
ReflectionUtils.setField(twoField, example, 42);
""");
assertThat(code.hasImport(ReflectionUtils.class)).isTrue();
assertThat(code.hasImport(BeanFieldGeneratorTests.class)).isTrue();
}
private Field field(Class<?> type, String name) {
Field field = ReflectionUtils.findField(type, name);
assertThat(field).isNotNull();
return field;
}
public static class SampleBean {
public String one;
@SuppressWarnings("unused")
private int two;
}
}

View File

@ -1,267 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedSet;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.ResourceLoader;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.support.CodeSnippet;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link BeanParameterGenerator}.
*
* @author Stephane Nicoll
*/
class BeanParameterGeneratorTests {
private final BeanParameterGenerator generator = new BeanParameterGenerator();
@Test
void generateCharArray() {
char[] value = new char[] { 'v', 'a', 'l', 'u', 'e' };
assertThat(generate(value, ResolvableType.forArrayComponent(ResolvableType.forClass(char.class))))
.isEqualTo("new char[] { 'v', 'a', 'l', 'u', 'e' }");
}
@Test
void generateStringArray() {
String[] value = new String[] { "a", "test" };
assertThat(generate(value, ResolvableType.forArrayComponent(ResolvableType.forClass(String.class))))
.isEqualTo("new String[] { \"a\", \"test\" }");
}
@Test
void generateStringList() {
List<String> value = List.of("a", "test");
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(List.class, String.class));
assertThat(code.getSnippet()).isEqualTo(
"List.of(\"a\", \"test\")");
assertThat(code.hasImport(List.class)).isTrue();
}
@Test
void generateStringManagedList() {
ManagedList<String> value = ManagedList.of("a", "test");
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(List.class, String.class));
assertThat(code.getSnippet()).isEqualTo(
"ManagedList.of(\"a\", \"test\")");
assertThat(code.hasImport(ManagedList.class)).isTrue();
}
@Test
void generateEmptyList() {
List<String> value = List.of();
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(List.class, String.class));
assertThat(code.getSnippet()).isEqualTo("Collections.emptyList()");
assertThat(code.hasImport(Collections.class)).isTrue();
}
@Test
void generateStringSet() {
Set<String> value = Set.of("a", "test");
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(Set.class, String.class));
assertThat(code.getSnippet()).startsWith("Set.of(").contains("a").contains("test");
assertThat(code.hasImport(Set.class)).isTrue();
}
@Test
void generateStringManagedSet() {
Set<String> value = ManagedSet.of("a", "test");
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(Set.class, String.class));
assertThat(code.getSnippet()).isEqualTo(
"ManagedSet.of(\"a\", \"test\")");
assertThat(code.hasImport(ManagedSet.class)).isTrue();
}
@Test
void generateEmptySet() {
Set<String> value = Set.of();
CodeSnippet code = codeSnippet(value, ResolvableType.forClassWithGenerics(Set.class, String.class));
assertThat(code.getSnippet()).isEqualTo("Collections.emptySet()");
assertThat(code.hasImport(Collections.class)).isTrue();
}
@Test
void generateMap() {
Map<String, Object> value = new LinkedHashMap<>();
value.put("name", "Hello");
value.put("counter", 42);
assertThat(generate(value)).isEqualTo("Map.of(\"name\", \"Hello\", \"counter\", 42)");
}
@Test
void generateMapWithEnum() {
Map<String, Object> value = new HashMap<>();
value.put("unit", ChronoUnit.DAYS);
assertThat(generate(value)).isEqualTo("Map.of(\"unit\", ChronoUnit.DAYS)");
}
@Test
void generateEmptyMap() {
assertThat(generate(Map.of())).isEqualTo("Map.of()");
}
@Test
void generateString() {
assertThat(generate("test", ResolvableType.forClass(String.class))).isEqualTo("\"test\"");
}
@Test
void generateCharEscapeBackslash() {
assertThat(generate('\\', ResolvableType.forType(char.class))).isEqualTo("'\\\\'");
}
@ParameterizedTest
@MethodSource("primitiveValues")
void generatePrimitiveValue(Object value, String parameter) {
assertThat(generate(value, ResolvableType.forClass(value.getClass()))).isEqualTo(parameter);
}
private static Stream<Arguments> primitiveValues() {
return Stream.of(Arguments.of((short) 0, "0"), Arguments.of((1), "1"), Arguments.of(2L, "2"),
Arguments.of(2.5d, "2.5"), Arguments.of(2.7f, "2.7"), Arguments.of('c', "'c'"),
Arguments.of((byte) 1, "1"), Arguments.of(true, "true"));
}
@Test
void generateEnum() {
assertThat(generate(ChronoUnit.DAYS, ResolvableType.forClass(ChronoUnit.class)))
.isEqualTo("ChronoUnit.DAYS");
}
@Test
void generateClass() {
assertThat(generate(Integer.class, ResolvableType.forClass(Class.class)))
.isEqualTo("Integer.class");
}
@Test
void generateResolvableType() {
ResolvableType type = ResolvableType.forClassWithGenerics(Consumer.class, Integer.class);
assertThat(generate(type, type))
.isEqualTo("ResolvableType.forClassWithGenerics(Consumer.class, Integer.class)");
}
@Test
void generateExecutableParameterTypesWithConstructor() {
Constructor<?> constructor = TestSample.class.getDeclaredConstructors()[0];
assertThat(CodeSnippet.process(this.generator.generateExecutableParameterTypes(constructor)))
.isEqualTo("String.class, ResourceLoader.class");
}
@Test
void generateExecutableParameterTypesWithNoArgConstructor() {
Constructor<?> constructor = BeanParameterGeneratorTests.class.getDeclaredConstructors()[0];
assertThat(CodeSnippet.process(this.generator.generateExecutableParameterTypes(constructor)))
.isEmpty();
}
@Test
void generateExecutableParameterTypesWithMethod() {
Method method = ReflectionUtils.findMethod(TestSample.class, "createBean", String.class, Integer.class);
assertThat(CodeSnippet.process(this.generator.generateExecutableParameterTypes(method)))
.isEqualTo("String.class, Integer.class");
}
@Test
void generateNull() {
assertThat(generate(null)).isEqualTo("null");
}
@Test
void generateBeanReference() {
BeanReference beanReference = mock(BeanReference.class);
given(beanReference.getBeanName()).willReturn("testBean");
assertThat(generate(beanReference)).isEqualTo("new RuntimeBeanReference(\"testBean\")");
}
@Test
void generateBeanDefinitionCallsConsumer() {
BeanParameterGenerator customGenerator = new BeanParameterGenerator(
beanDefinition -> CodeBlock.of("test"));
assertThat(CodeSnippet.process(customGenerator.generateParameterValue(
new RootBeanDefinition()))).isEqualTo("test");
}
@Test
void generateBeanDefinitionWithoutConsumerFails() {
BeanParameterGenerator customGenerator = new BeanParameterGenerator();
assertThatIllegalStateException().isThrownBy(() -> customGenerator
.generateParameterValue(new RootBeanDefinition()));
}
@Test
void generateUnsupportedParameter() {
assertThatIllegalArgumentException().isThrownBy(() -> generate(new StringWriter()))
.withMessageContaining(StringWriter.class.getName());
}
private String generate(@Nullable Object value) {
return CodeSnippet.process(this.generator.generateParameterValue(value));
}
private String generate(Object value, ResolvableType resolvableType) {
return codeSnippet(value, resolvableType).getSnippet();
}
private CodeSnippet codeSnippet(Object value, ResolvableType resolvableType) {
return CodeSnippet.of(this.generator.generateParameterValue(value, () -> resolvableType));
}
@SuppressWarnings("unused")
static class TestSample {
public TestSample(String test, ResourceLoader resourceLoader) {
}
String createBean(String name, Integer counter) {
return "test";
}
}
}

View File

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

View File

@ -1,255 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generator.CodeContribution;
import org.springframework.aot.hint.ExecutableHint;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeHint;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.EnvironmentAwareComponent;
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.NoDependencyComponent;
import org.springframework.beans.testfixture.beans.factory.generator.SimpleConfiguration;
import org.springframework.beans.testfixture.beans.factory.generator.factory.NumberHolderFactoryBean;
import org.springframework.beans.testfixture.beans.factory.generator.factory.SampleFactory;
import org.springframework.beans.testfixture.beans.factory.generator.injection.InjectionComponent;
import org.springframework.beans.testfixture.beans.factory.generator.visibility.ProtectedConstructorComponent;
import org.springframework.beans.testfixture.beans.factory.generator.visibility.ProtectedFactoryMethod;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.support.CodeSnippet;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DefaultBeanInstantiationGenerator}.
*
* @author Stephane Nicoll
*/
class DefaultBeanInstantiationGeneratorTests {
@Test
void generateUsingDefaultConstructorUsesMethodReference() {
CodeContribution contribution = generate(SimpleConfiguration.class.getDeclaredConstructors()[0]);
assertThat(code(contribution)).isEqualTo("SimpleConfiguration::new");
assertThat(reflectionHints(contribution, SimpleConfiguration.class)).isNull();
}
@Test
void generateUsingConstructorWithoutParameterAndMultipleCandidatesDoesNotUseMethodReference() throws NoSuchMethodException {
CodeContribution contribution = generate(TestBean.class.getConstructor());
assertThat(code(contribution)).isEqualTo("() -> new TestBean()");
assertThat(reflectionHints(contribution, TestBean.class)).isNull();
}
@Test
void generateUsingConstructorWithParameter() {
Constructor<?> constructor = InjectionComponent.class.getDeclaredConstructors()[0];
CodeContribution contribution = generate(constructor);
assertThat(code(contribution).lines()).containsOnly(
"(instanceContext) -> instanceContext.create(beanFactory, (attributes) -> "
+ "new InjectionComponent(attributes.get(0)))");
assertThat(reflectionHints(contribution, InjectionComponent.class))
.satisfies(hasSingleQueryConstructor(constructor));
}
@Test
void generateUsingConstructorWithInnerClassAndNoExtraArg() {
CodeContribution contribution = generate(NoDependencyComponent.class.getDeclaredConstructors()[0]);
assertThat(code(contribution).lines()).containsOnly(
"() -> beanFactory.getBean(InnerComponentConfiguration.class).new NoDependencyComponent()");
assertThat(reflectionHints(contribution, NoDependencyComponent.class)).isNull();
}
@Test
void generateUsingConstructorWithInnerClassAndExtraArg() {
Constructor<?> constructor = EnvironmentAwareComponent.class.getDeclaredConstructors()[0];
CodeContribution contribution = generate(constructor);
assertThat(code(contribution).lines()).containsOnly(
"(instanceContext) -> instanceContext.create(beanFactory, (attributes) -> "
+ "beanFactory.getBean(InnerComponentConfiguration.class).new EnvironmentAwareComponent(attributes.get(1)))");
assertThat(reflectionHints(contribution, EnvironmentAwareComponent.class))
.satisfies(hasSingleQueryConstructor(constructor));
}
@Test
void generateUsingConstructorOfTypeWithGeneric() {
CodeContribution contribution = generate(NumberHolderFactoryBean.class.getDeclaredConstructors()[0]);
assertThat(code(contribution)).isEqualTo("NumberHolderFactoryBean::new");
assertThat(reflectionHints(contribution, NumberHolderFactoryBean.class)).isNull();
}
@Test
void generateUsingNoArgConstructorAndContributionsDoesNotUseMethodReference() {
CodeContribution contribution = generate(SimpleConfiguration.class.getDeclaredConstructors()[0],
contrib -> contrib.statements().add(CodeBlock.of("// hello\n")),
contrib -> {});
assertThat(code(contribution)).isEqualTo("""
(instanceContext) -> {
SimpleConfiguration bean = new SimpleConfiguration();
// hello
return bean;
}""");
}
@Test
void generateUsingContributionsRegisterHints() {
CodeContribution contribution = generate(SimpleConfiguration.class.getDeclaredConstructors()[0],
contrib -> {
contrib.statements().add(CodeBlock.of("// hello\n"));
contrib.runtimeHints().resources().registerPattern("com/example/*.properties");
},
contrib -> contrib.runtimeHints().reflection().registerType(TypeReference.of(String.class),
hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)));
assertThat(code(contribution)).isEqualTo("""
(instanceContext) -> {
SimpleConfiguration bean = new SimpleConfiguration();
// hello
return bean;
}""");
assertThat(contribution.runtimeHints().resources().resourcePatterns()).singleElement().satisfies(hint ->
assertThat(hint.getIncludes()).containsOnly("com/example/*.properties"));
assertThat(contribution.runtimeHints().reflection().getTypeHint(String.class)).satisfies(hint -> {
assertThat(hint.getType()).isEqualTo(TypeReference.of(String.class));
assertThat(hint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_PUBLIC_METHODS);
});
}
@Test
void generateUsingMethodWithNoArg() {
Method method = method(SimpleConfiguration.class, "stringBean");
CodeContribution contribution = generate(method);
assertThat(code(contribution)).isEqualTo("() -> beanFactory.getBean(SimpleConfiguration.class).stringBean()");
assertThat(reflectionHints(contribution, SimpleConfiguration.class))
.satisfies(hasSingleQueryMethod(method));
}
@Test
void generateUsingStaticMethodWithNoArg() {
Method method = method(SampleFactory.class, "integerBean");
CodeContribution contribution = generate(method);
assertThat(code(contribution)).isEqualTo("() -> SampleFactory.integerBean()");
assertThat(reflectionHints(contribution, SampleFactory.class))
.satisfies(hasSingleQueryMethod(method));
}
@Test
void generateUsingMethodWithArg() {
Method method = method(SampleFactory.class, "create", Number.class, String.class);
CodeContribution contribution = generate(method);
assertThat(code(contribution)).isEqualTo("(instanceContext) -> instanceContext.create(beanFactory, (attributes) -> "
+ "SampleFactory.create(attributes.get(0), attributes.get(1)))");
assertThat(reflectionHints(contribution, SampleFactory.class))
.satisfies(hasSingleQueryMethod(method));
}
@Test
void generateUsingMethodAndContributions() {
CodeContribution contribution = generate(method(SimpleConfiguration.class, "stringBean"),
contrib -> {
contrib.statements().add(CodeBlock.of("// hello\n"));
contrib.runtimeHints().resources().registerPattern("com/example/*.properties");
},
contrib -> contrib.runtimeHints().reflection().registerType(TypeReference.of(String.class),
hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS)));
assertThat(code(contribution)).isEqualTo("""
(instanceContext) -> {
String bean = beanFactory.getBean(SimpleConfiguration.class).stringBean();
// hello
return bean;
}""");
assertThat(contribution.runtimeHints().resources().resourcePatterns()).singleElement().satisfies(hint ->
assertThat(hint.getIncludes()).containsOnly("com/example/*.properties"));
assertThat(contribution.runtimeHints().reflection().getTypeHint(String.class)).satisfies(hint -> {
assertThat(hint.getType()).isEqualTo(TypeReference.of(String.class));
assertThat(hint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_PUBLIC_METHODS);
});
}
@Test
void generateUsingProtectedConstructorRegistersProtectedAccess() {
CodeContribution contribution = generate(ProtectedConstructorComponent.class.getDeclaredConstructors()[0]);
assertThat(contribution.protectedAccess().isAccessible("com.example")).isFalse();
assertThat(contribution.protectedAccess().getPrivilegedPackageName("com.example"))
.isEqualTo(ProtectedConstructorComponent.class.getPackageName());
}
@Test
void generateUsingProtectedMethodRegistersProtectedAccess() {
CodeContribution contribution = generate(method(ProtectedFactoryMethod.class, "testBean", Integer.class));
assertThat(contribution.protectedAccess().isAccessible("com.example")).isFalse();
assertThat(contribution.protectedAccess().getPrivilegedPackageName("com.example"))
.isEqualTo(ProtectedFactoryMethod.class.getPackageName());
}
private String code(CodeContribution contribution) {
return CodeSnippet.process(contribution.statements().toLambdaBody());
}
@Nullable
private TypeHint reflectionHints(CodeContribution contribution, Class<?> type) {
return contribution.runtimeHints().reflection().getTypeHint(type);
}
private Consumer<TypeHint> hasSingleQueryConstructor(Constructor<?> constructor) {
return typeHint -> assertThat(typeHint.constructors()).singleElement()
.satisfies(match(constructor, "<init>", ExecutableMode.INTROSPECT));
}
private Consumer<TypeHint> hasSingleQueryMethod(Method method) {
return typeHint -> assertThat(typeHint.methods()).singleElement()
.satisfies(match(method, method.getName(), ExecutableMode.INTROSPECT));
}
private Consumer<ExecutableHint> match(Executable executable, String name, ExecutableMode... modes) {
return hint -> {
assertThat(hint.getName()).isEqualTo(name);
assertThat(hint.getParameterTypes()).hasSameSizeAs(executable.getParameterTypes());
for (int i = 0; i < hint.getParameterTypes().size(); i++) {
assertThat(hint.getParameterTypes().get(i))
.isEqualTo(TypeReference.of(executable.getParameterTypes()[i]));
}
assertThat(hint.getModes()).containsOnly(modes);
};
}
private CodeContribution generate(Executable executable,
BeanInstantiationContribution... beanInstantiationContributions) {
DefaultBeanInstantiationGenerator generator = new DefaultBeanInstantiationGenerator(executable,
Arrays.asList(beanInstantiationContributions));
return generator.generateBeanInstantiation(new RuntimeHints());
}
private static Method method(Class<?> type, String methodName, Class<?>... parameterTypes) {
Method method = ReflectionUtils.findMethod(type, methodName, parameterTypes);
assertThat(method).isNotNull();
return method;
}
}

View File

@ -1,67 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.factory.generator.SimpleConfiguration;
import org.springframework.core.Ordered;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests for {@link DefaultBeanRegistrationContributionProvider}.
*
* @author Stephane Nicoll
*/
class DefaultBeanRegistrationContributionProviderTests {
@Test
void aotContributingBeanPostProcessorsAreIncluded() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AotContributingBeanPostProcessor first = mockNoOpPostProcessor(-1);
AotContributingBeanPostProcessor second = mockNoOpPostProcessor(5);
beanFactory.registerBeanDefinition("second", BeanDefinitionBuilder.rootBeanDefinition(
AotContributingBeanPostProcessor.class, () -> second).getBeanDefinition());
beanFactory.registerBeanDefinition("first", BeanDefinitionBuilder.rootBeanDefinition(
AotContributingBeanPostProcessor.class, () -> first).getBeanDefinition());
RootBeanDefinition beanDefinition = new RootBeanDefinition(SimpleConfiguration.class);
new DefaultBeanRegistrationContributionProvider(beanFactory).getContributionFor(
"test", beanDefinition);
verify((Ordered) second).getOrder();
verify((Ordered) first).getOrder();
verify(first).contribute(beanDefinition, SimpleConfiguration.class, "test");
verify(second).contribute(beanDefinition, SimpleConfiguration.class, "test");
verifyNoMoreInteractions(first, second);
}
private AotContributingBeanPostProcessor mockNoOpPostProcessor(int order) {
AotContributingBeanPostProcessor postProcessor = mock(AotContributingBeanPostProcessor.class);
given(postProcessor.contribute(any(), any(), any())).willReturn(null);
given(postProcessor.getOrder()).willReturn(order);
return postProcessor;
}
}

View File

@ -1,336 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generator.ProtectedAccess;
import org.springframework.aot.generator.ProtectedAccess.Options;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.generator.InjectionGeneratorTests.SimpleConstructorBean.InnerClass;
import org.springframework.beans.testfixture.beans.factory.generator.factory.SampleFactory;
import org.springframework.javapoet.support.CodeSnippet;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link InjectionGenerator}.
*
* @author Stephane Nicoll
*/
class InjectionGeneratorTests {
private final ProtectedAccess protectedAccess = new ProtectedAccess();
@Test
void generateInstantiationForConstructorWithNoArgUseShortcut() {
Constructor<?> constructor = SimpleBean.class.getDeclaredConstructors()[0];
assertThat(generateInstantiation(constructor).lines())
.containsExactly("new InjectionGeneratorTests.SimpleBean()");
}
@Test
void generateInstantiationForConstructorWithNonGenericParameter() {
Constructor<?> constructor = SimpleConstructorBean.class.getDeclaredConstructors()[0];
assertThat(generateInstantiation(constructor).lines()).containsExactly(
"instanceContext.create(beanFactory, (attributes) -> new InjectionGeneratorTests.SimpleConstructorBean(attributes.get(0), attributes.get(1)))");
}
@Test
void generateInstantiationForConstructorWithGenericParameter() {
Constructor<?> constructor = GenericConstructorBean.class.getDeclaredConstructors()[0];
assertThat(generateInstantiation(constructor).lines()).containsExactly(
"instanceContext.create(beanFactory, (attributes) -> new InjectionGeneratorTests.GenericConstructorBean(attributes.get(0)))");
}
@Test
void generateInstantiationForAmbiguousConstructor() throws Exception {
Constructor<?> constructor = AmbiguousConstructorBean.class.getDeclaredConstructor(String.class, Number.class);
assertThat(generateInstantiation(constructor).lines()).containsExactly(
"instanceContext.create(beanFactory, (attributes) -> new InjectionGeneratorTests.AmbiguousConstructorBean(attributes.get(0, String.class), attributes.get(1, Number.class)))");
}
@Test
void generateInstantiationForConstructorInInnerClass() {
Constructor<?> constructor = InnerClass.class.getDeclaredConstructors()[0];
assertThat(generateInstantiation(constructor).lines()).containsExactly(
"beanFactory.getBean(InjectionGeneratorTests.SimpleConstructorBean.class).new InnerClass()");
}
@Test
void generateInstantiationForMethodWithNoArgUseShortcut() {
assertThat(generateInstantiation(method(SimpleBean.class, "name")).lines()).containsExactly(
"beanFactory.getBean(InjectionGeneratorTests.SimpleBean.class).name()");
}
@Test
void generateInstantiationForStaticMethodWithNoArgUseShortcut() {
assertThat(generateInstantiation(method(SimpleBean.class, "number")).lines()).containsExactly(
"InjectionGeneratorTests.SimpleBean.number()");
}
@Test
void generateInstantiationForMethodWithNonGenericParameter() {
assertThat(generateInstantiation(method(SampleBean.class, "source", Integer.class)).lines()).containsExactly(
"instanceContext.create(beanFactory, (attributes) -> beanFactory.getBean(InjectionGeneratorTests.SampleBean.class).source(attributes.get(0)))");
}
@Test
void generateInstantiationForStaticMethodWithNonGenericParameter() {
assertThat(generateInstantiation(method(SampleBean.class, "staticSource", Integer.class)).lines()).containsExactly(
"instanceContext.create(beanFactory, (attributes) -> InjectionGeneratorTests.SampleBean.staticSource(attributes.get(0)))");
}
@Test
void generateInstantiationForMethodWithGenericParameters() {
assertThat(generateInstantiation(method(SampleBean.class, "sourceWithProvider", ObjectProvider.class)).lines()).containsExactly(
"instanceContext.create(beanFactory, (attributes) -> beanFactory.getBean(InjectionGeneratorTests.SampleBean.class).sourceWithProvider(attributes.get(0)))");
}
@Test
void generateInstantiationForAmbiguousMethod() {
assertThat(generateInstantiation(method(SampleFactory.class, "create", String.class)).lines()).containsExactly(
"instanceContext.create(beanFactory, (attributes) -> SampleFactory.create(attributes.get(0, String.class)))");
}
@Test
void generateInjectionForUnsupportedMember() {
assertThatIllegalArgumentException().isThrownBy(() -> generateInjection(mock(Member.class), false));
}
@Test
void generateInjectionForNonRequiredMethodWithNonGenericParameters() {
Method method = method(SampleBean.class, "sourceAndCounter", String.class, Integer.class);
assertThat(generateInjection(method, false)).isEqualTo("""
instanceContext.method("sourceAndCounter", String.class, Integer.class)
.resolve(beanFactory, false).ifResolved((attributes) -> bean.sourceAndCounter(attributes.get(0), attributes.get(1)))""");
}
@Test
void generateInjectionForRequiredMethodWithGenericParameter() {
Method method = method(SampleBean.class, "nameAndCounter", String.class, ObjectProvider.class);
assertThat(generateInjection(method, true)).isEqualTo("""
instanceContext.method("nameAndCounter", String.class, ObjectProvider.class)
.invoke(beanFactory, (attributes) -> bean.nameAndCounter(attributes.get(0), attributes.get(1)))""");
}
@Test
void generateInjectionForNonRequiredMethodWithGenericParameter() {
Method method = method(SampleBean.class, "nameAndCounter", String.class, ObjectProvider.class);
assertThat(generateInjection(method, false)).isEqualTo("""
instanceContext.method("nameAndCounter", String.class, ObjectProvider.class)
.resolve(beanFactory, false).ifResolved((attributes) -> bean.nameAndCounter(attributes.get(0), attributes.get(1)))""");
}
@Test
void generateInjectionForRequiredField() {
Field field = field(SampleBean.class, "counter");
assertThat(generateInjection(field, true)).isEqualTo("""
instanceContext.field("counter")
.invoke(beanFactory, (attributes) -> bean.counter = attributes.get(0))""");
}
@Test
void generateInjectionForNonRequiredField() {
Field field = field(SampleBean.class, "counter");
assertThat(generateInjection(field, false)).isEqualTo("""
instanceContext.field("counter")
.resolve(beanFactory, false).ifResolved((attributes) -> bean.counter = attributes.get(0))""");
}
@Test
void generateInjectionForRequiredPrivateField() {
Field field = field(SampleBean.class, "source");
assertThat(generateInjection(field, true)).isEqualTo("""
instanceContext.field("source")
.invoke(beanFactory, (attributes) -> {
Field sourceField = ReflectionUtils.findField(InjectionGeneratorTests.SampleBean.class, "source");
ReflectionUtils.makeAccessible(sourceField);
ReflectionUtils.setField(sourceField, bean, attributes.get(0));
})""");
}
@Test
void getProtectedAccessInjectionOptionsForUnsupportedMember() {
assertThatIllegalArgumentException().isThrownBy(() ->
getProtectedAccessInjectionOptions(mock(Member.class)));
}
@Test
void getProtectedAccessInjectionOptionsForPackagePublicField() {
analyzeProtectedAccess(field(SampleBean.class, "enabled"));
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
}
@Test
void getProtectedAccessInjectionOptionsForPackageProtectedField() {
analyzeProtectedAccess(field(SampleBean.class, "counter"));
assertPrivilegedAccess(SampleBean.class);
}
@Test
void getProtectedAccessInjectionOptionsForPrivateField() {
analyzeProtectedAccess(field(SampleBean.class, "source"));
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
}
@Test
void getProtectedAccessInjectionOptionsForPublicMethod() {
analyzeProtectedAccess(method(SampleBean.class, "setEnabled", Boolean.class));
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
}
@Test
void getProtectedAccessInjectionOptionsForPackageProtectedMethod() {
analyzeProtectedAccess(method(SampleBean.class, "sourceAndCounter", String.class, Integer.class));
assertPrivilegedAccess(SampleBean.class);
}
private Method method(Class<?> type, String name, Class<?>... parameterTypes) {
Method method = ReflectionUtils.findMethod(type, name, parameterTypes);
assertThat(method).isNotNull();
return method;
}
private Field field(Class<?> type, String name) {
Field field = ReflectionUtils.findField(type, name);
assertThat(field).isNotNull();
return field;
}
private String generateInstantiation(Executable creator) {
return CodeSnippet.process(code -> code.add(new InjectionGenerator().generateInstantiation(creator)));
}
private String generateInjection(Member member, boolean required) {
return CodeSnippet.process(code -> code.add(new InjectionGenerator().generateInjection(member, required)));
}
private void analyzeProtectedAccess(Member member) {
this.protectedAccess.analyze(member, getProtectedAccessInjectionOptions(member));
}
private Options getProtectedAccessInjectionOptions(Member member) {
return new InjectionGenerator().getProtectedAccessInjectionOptions(member);
}
private void assertPrivilegedAccess(Class<?> target) {
assertThat(this.protectedAccess.isAccessible("com.example")).isFalse();
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")).isEqualTo(target.getPackageName());
assertThat(this.protectedAccess.isAccessible(target.getPackageName())).isTrue();
}
@SuppressWarnings("unused")
public static class SampleBean {
public Boolean enabled;
private String source;
Integer counter;
public void setEnabled(Boolean enabled) {
}
void sourceAndCounter(String source, Integer counter) {
}
void nameAndCounter(String name, ObjectProvider<Integer> counter) {
}
String source(Integer counter) {
return "source" + counter;
}
String sourceWithProvider(ObjectProvider<Integer> counter) {
return "source" + counter.getIfAvailable(() -> 0);
}
static String staticSource(Integer counter) {
return counter + "source";
}
}
@SuppressWarnings("unused")
static class SimpleBean {
String name() {
return "test";
}
static Integer number() {
return 42;
}
}
@SuppressWarnings("unused")
static class SimpleConstructorBean {
private final String source;
private final Integer counter;
public SimpleConstructorBean(String source, Integer counter) {
this.source = source;
this.counter = counter;
}
class InnerClass {
}
}
@SuppressWarnings("unused")
static class GenericConstructorBean {
private final ObjectProvider<Integer> counter;
GenericConstructorBean(ObjectProvider<Integer> counter) {
this.counter = counter;
}
}
static class AmbiguousConstructorBean {
AmbiguousConstructorBean(String first, String second) {
}
AmbiguousConstructorBean(String first, Number second) {
}
}
}

View File

@ -1,463 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator.config;
import java.io.IOException;
import java.lang.reflect.Field;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.generator.config.BeanDefinitionRegistrar.BeanInstanceContext;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link BeanDefinitionRegistrar}.
*
* @author Stephane Nicoll
*/
class BeanDefinitionRegistrarTests {
@Test
void beanDefinitionWithBeanClassDoesNotSetTargetType() {
RootBeanDefinition beanDefinition = BeanDefinitionRegistrar.of("test", String.class).toBeanDefinition();
assertThat(beanDefinition.getBeanClass()).isEqualTo(String.class);
assertThat(beanDefinition.getTargetType()).isNull();
}
@Test
void beanDefinitionWithResolvableTypeSetsTargetType() {
ResolvableType targetType = ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class);
RootBeanDefinition beanDefinition = BeanDefinitionRegistrar.of("test", targetType).toBeanDefinition();
assertThat(beanDefinition.getTargetType()).isNotNull().isEqualTo(NumberHolder.class);
}
@Test
void registerWithSimpleInstanceSupplier() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
BeanDefinitionRegistrar.of("test", InjectionSample.class)
.instanceSupplier(InjectionSample::new).register(beanFactory);
assertBeanFactory(beanFactory, () -> {
assertThat(beanFactory.containsBean("test")).isTrue();
assertThat(beanFactory.getBean(InjectionSample.class)).isNotNull();
});
}
@Test
void registerWithSimpleInstanceSupplierThatThrowsRuntimeException() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
Exception exception = new IllegalArgumentException("test exception");
BeanDefinitionRegistrar.of("testBean", InjectionSample.class)
.instanceSupplier(() -> {
throw exception;
}).register(beanFactory);
assertThatThrownBy(() -> beanFactory.getBean("testBean")).isInstanceOf(BeanCreationException.class)
.getRootCause().isEqualTo(exception);
}
@Test
void registerWithSimpleInstanceSupplierThatThrowsCheckedException() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
Exception exception = new IOException("test exception");
BeanDefinitionRegistrar.of("testBean", InjectionSample.class)
.instanceSupplier(() -> {
throw exception;
}).register(beanFactory);
assertThatThrownBy(() -> beanFactory.getBean("testBean")).isInstanceOf(BeanCreationException.class)
.getRootCause().isEqualTo(exception);
}
@Test
void registerWithoutBeanNameFails() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
BeanDefinitionRegistrar registrar = BeanDefinitionRegistrar.inner(InjectionSample.class)
.instanceSupplier(InjectionSample::new);
assertThatIllegalStateException().isThrownBy(() -> registrar.register(beanFactory))
.withMessageContaining("Bean name not set.");
}
@Test
@SuppressWarnings("unchecked")
void registerWithCustomizer() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
BeanDefinitionRegistrar.ThrowableConsumer<RootBeanDefinition> first = mock(BeanDefinitionRegistrar.ThrowableConsumer.class);
BeanDefinitionRegistrar.ThrowableConsumer<RootBeanDefinition> second = mock(BeanDefinitionRegistrar.ThrowableConsumer.class);
BeanDefinitionRegistrar.of("test", InjectionSample.class)
.instanceSupplier(InjectionSample::new).customize(first).customize(second).register(beanFactory);
assertBeanFactory(beanFactory, () -> {
assertThat(beanFactory.containsBean("test")).isTrue();
InOrder ordered = inOrder(first, second);
ordered.verify(first).accept(any(RootBeanDefinition.class));
ordered.verify(second).accept(any(RootBeanDefinition.class));
});
}
@Test
void registerWithCustomizerThatThrowsRuntimeException() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
Exception exception = new RuntimeException("test exception");
BeanDefinitionRegistrar registrar = BeanDefinitionRegistrar.of("test", InjectionSample.class)
.instanceSupplier(InjectionSample::new).customize(bd -> {
throw exception;
});
assertThatThrownBy(() -> registrar.register(beanFactory)).isInstanceOf(FatalBeanException.class)
.hasMessageContaining("Failed to create bean definition for bean with name 'test'")
.hasMessageContaining("test exception")
.hasCause(exception);
}
@Test
void registerWithCustomizerThatThrowsCheckedException() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
Exception exception = new IOException("test exception");
BeanDefinitionRegistrar registrar = BeanDefinitionRegistrar.of("test", InjectionSample.class)
.instanceSupplier(InjectionSample::new).customize(bd -> {
throw exception;
});
assertThatThrownBy(() -> registrar.register(beanFactory)).isInstanceOf(FatalBeanException.class)
.hasMessageContaining("Failed to create bean definition for bean with name 'test'")
.hasMessageContaining("test exception");
}
@Test
void registerWithConstructorInstantiation() {
ResourceLoader resourceLoader = new DefaultResourceLoader();
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerResolvableDependency(ResourceLoader.class, resourceLoader);
BeanDefinitionRegistrar.of("test", ConstructorSample.class).withConstructor(ResourceLoader.class)
.instanceSupplier(instanceContext -> instanceContext.create(beanFactory, attributes ->
new ConstructorSample(attributes.get(0)))).register(beanFactory);
assertBeanFactory(beanFactory, () -> {
assertThat(beanFactory.containsBean("test")).isTrue();
assertThat(beanFactory.getBean(ConstructorSample.class).resourceLoader).isEqualTo(resourceLoader);
});
}
@Test
void registerWithConstructorInstantiationThatThrowsRuntimeException() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
Exception exception = new RuntimeException("test exception");
BeanDefinitionRegistrar.of("test", ConstructorSample.class).withConstructor(ResourceLoader.class)
.instanceSupplier(instanceContext -> {
throw exception;
}).register(beanFactory);
assertThatThrownBy(() -> beanFactory.getBean("test")).isInstanceOf(BeanCreationException.class)
.getRootCause().isEqualTo(exception);
}
@Test
void registerWithConstructorInstantiationThatThrowsCheckedException() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
Exception exception = new IOException("test exception");
BeanDefinitionRegistrar.of("test", ConstructorSample.class).withConstructor(ResourceLoader.class)
.instanceSupplier(instanceContext -> {
throw exception;
}).register(beanFactory);
assertThatThrownBy(() -> beanFactory.getBean("test")).isInstanceOf(BeanCreationException.class)
.getRootCause().isEqualTo(exception);
}
@Test
void registerWithConstructorOnInnerClass() {
Environment environment = mock(Environment.class);
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("environment", environment);
beanFactory.registerBeanDefinition("sample", BeanDefinitionBuilder.rootBeanDefinition(InnerClassSample.class).getBeanDefinition());
BeanDefinitionRegistrar.of("test", InnerClassSample.Inner.class).withConstructor(InnerClassSample.class, Environment.class)
.instanceSupplier(instanceContext -> instanceContext.create(beanFactory, attributes ->
beanFactory.getBean(InnerClassSample.class).new Inner(attributes.get(1))))
.register(beanFactory);
assertBeanFactory(beanFactory, () -> {
assertThat(beanFactory.containsBean("test")).isTrue();
InnerClassSample.Inner bean = beanFactory.getBean(InnerClassSample.Inner.class);
assertThat(bean.environment).isEqualTo(environment);
});
}
@Test
void registerWithInvalidConstructor() {
assertThatThrownBy(() -> BeanDefinitionRegistrar.of("test", ConstructorSample.class).withConstructor(Object.class))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("No constructor with type(s) [java.lang.Object] found on")
.hasMessageContaining(ConstructorSample.class.getName());
}
@Test
void registerWithFactoryMethod() {
ResourceLoader resourceLoader = new DefaultResourceLoader();
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerResolvableDependency(ResourceLoader.class, resourceLoader);
BeanDefinitionRegistrar.of("configuration", ConfigurationSample.class).instanceSupplier(ConfigurationSample::new)
.register(beanFactory);
BeanDefinitionRegistrar.of("test", ConstructorSample.class)
.withFactoryMethod(ConfigurationSample.class, "sampleBean", ResourceLoader.class)
.instanceSupplier(instanceContext -> instanceContext.create(beanFactory, attributes ->
beanFactory.getBean(ConfigurationSample.class).sampleBean(attributes.get(0))))
.register(beanFactory);
assertBeanFactory(beanFactory, () -> {
assertThat(beanFactory.containsBean("configuration")).isTrue();
assertThat(beanFactory.containsBean("test")).isTrue();
assertThat(beanFactory.getBean(ConstructorSample.class).resourceLoader).isEqualTo(resourceLoader);
RootBeanDefinition bd = (RootBeanDefinition) beanFactory.getBeanDefinition("test");
assertThat(bd.getResolvedFactoryMethod()).isNotNull().isEqualTo(
ReflectionUtils.findMethod(ConfigurationSample.class, "sampleBean", ResourceLoader.class));
});
}
@Test
void registerWithCreateShortcutWithoutFactoryMethod() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
BeanDefinitionRegistrar.of("configuration", ConfigurationSample.class).instanceSupplier(ConfigurationSample::new)
.register(beanFactory);
BeanDefinitionRegistrar.of("test", ConstructorSample.class)
.instanceSupplier(instanceContext -> instanceContext.create(beanFactory, attributes ->
beanFactory.getBean(ConfigurationSample.class).sampleBean(attributes.get(0))))
.register(beanFactory);
assertThatThrownBy(() -> beanFactory.getBean("test")).isInstanceOf(BeanCreationException.class)
.hasMessageContaining("No factory method or constructor is set");
}
@Test
void registerWithInjectedField() {
Environment environment = mock(Environment.class);
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("environment", environment);
BeanDefinitionRegistrar.of("test", InjectionSample.class).instanceSupplier(instanceContext -> {
InjectionSample bean = new InjectionSample();
instanceContext.field("environment").invoke(beanFactory, attributes ->
bean.environment = (attributes.get(0)));
return bean;
}).register(beanFactory);
assertBeanFactory(beanFactory, () -> {
assertThat(beanFactory.containsBean("test")).isTrue();
assertThat(beanFactory.getBean(InjectionSample.class).environment).isEqualTo(environment);
});
}
@Test
void registerWithInvalidField() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
BeanDefinitionRegistrar.of("test", InjectionSample.class).instanceSupplier(instanceContext ->
instanceContext.field("doesNotExist").resolve(beanFactory)).register(beanFactory);
assertThatThrownBy(() -> beanFactory.getBean(InjectionSample.class)
).isInstanceOf(BeanCreationException.class).hasMessageContaining(
"No field 'doesNotExist' found on " + InjectionSample.class.getName());
}
@Test
void registerWithInjectedMethod() {
Environment environment = mock(Environment.class);
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("environment", environment);
BeanDefinitionRegistrar.of("test", InjectionSample.class).instanceSupplier(instanceContext -> {
InjectionSample bean = new InjectionSample();
instanceContext.method("setEnvironment", Environment.class).invoke(beanFactory,
attributes -> bean.setEnvironment(attributes.get(0)));
return bean;
}).register(beanFactory);
assertBeanFactory(beanFactory, () -> {
assertThat(beanFactory.containsBean("test")).isTrue();
assertThat(beanFactory.getBean(InjectionSample.class).environment).isEqualTo(environment);
});
}
@Test
void registerWithInvalidMethod() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
assertThatThrownBy(() -> {
BeanDefinitionRegistrar.of("test", InjectionSample.class).instanceSupplier(instanceContext ->
instanceContext.method("setEnvironment", Object.class).resolve(beanFactory)).register(beanFactory);
beanFactory.getBean(InjectionSample.class);
}
).isInstanceOf(BeanCreationException.class)
.hasMessageContaining("No method '%s' with type(s) [%s] found", "setEnvironment", Object.class.getName())
.hasMessageContaining(InjectionSample.class.getName());
}
@Test
void innerBeanDefinitionWithClass() {
RootBeanDefinition beanDefinition = BeanDefinitionRegistrar.inner(ConfigurationSample.class)
.customize(bd -> bd.setSynthetic(true)).toBeanDefinition();
assertThat(beanDefinition).isNotNull();
assertThat(beanDefinition.getResolvableType().resolve()).isEqualTo(ConfigurationSample.class);
assertThat(beanDefinition.isSynthetic()).isTrue();
}
@Test
void innerBeanDefinitionWithResolvableType() {
RootBeanDefinition beanDefinition = BeanDefinitionRegistrar.inner(ResolvableType.forClass(ConfigurationSample.class))
.customize(bd -> bd.setDescription("test")).toBeanDefinition();
assertThat(beanDefinition).isNotNull();
assertThat(beanDefinition.getResolvableType().resolve()).isEqualTo(ConfigurationSample.class);
assertThat(beanDefinition.getDescription()).isEqualTo("test");
}
@Test
void innerBeanDefinitionHasInnerBeanNameInInstanceSupplier() {
RootBeanDefinition beanDefinition = BeanDefinitionRegistrar.inner(String.class)
.instanceSupplier(instanceContext -> {
Field field = ReflectionUtils.findField(BeanInstanceContext.class, "beanName", String.class);
ReflectionUtils.makeAccessible(field);
return ReflectionUtils.getField(field, instanceContext);
}).toBeanDefinition();
assertThat(beanDefinition).isNotNull();
String beanName = (String) beanDefinition.getInstanceSupplier().get();
assertThat(beanName).isNotNull().startsWith("(inner bean)#");
}
private void assertBeanFactory(DefaultListableBeanFactory beanFactory, Runnable assertions) {
assertions.run();
}
static class ConfigurationSample {
ConstructorSample sampleBean(ResourceLoader resourceLoader) {
return new ConstructorSample(resourceLoader);
}
}
static class ConstructorSample {
private final ResourceLoader resourceLoader;
ConstructorSample(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
static class MultiArgConstructorSample {
@SuppressWarnings("unused")
private final String name;
@SuppressWarnings("unused")
private final Integer counter;
public MultiArgConstructorSample(String name, Integer counter) {
this.name = name;
this.counter = counter;
}
}
static class InjectionSample {
private Environment environment;
@SuppressWarnings("unused")
private String name;
@SuppressWarnings("unused")
private Integer counter;
void setEnvironment(Environment environment) {
this.environment = environment;
}
void setNameAndCounter(@Value("${test.name:test}") String name, @Value("${test.counter:42}") Integer counter) {
this.name = name;
this.counter = counter;
}
}
static class InnerClassSample {
class Inner {
private Environment environment;
Inner(Environment environment) {
this.environment = environment;
}
}
}
static class GenericFactoryBeanConfiguration {
FactoryBean<NumberHolder<?>> integerHolderFactory() {
return new GenericFactoryBean<>(integerHolder());
}
NumberHolder<?> integerHolder() {
return new NumberHolder<>(42);
}
}
static class GenericFactoryBean<T> implements FactoryBean<T> {
private final T value;
public GenericFactoryBean(T value) {
this.value = value;
}
@Override
public T getObject() {
return this.value;
}
@Override
public Class<?> getObjectType() {
return this.value.getClass();
}
}
static class NumberHolder<N extends Number> {
@SuppressWarnings("unused")
private final N number;
public NumberHolder(N number) {
this.number = number;
}
}
static class NumberHolderSample {
@Autowired
@SuppressWarnings("unused")
private NumberHolder<Integer> numberHolder;
}
}

View File

@ -1,476 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator.config;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import org.assertj.core.util.Arrays;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link InjectedConstructionResolver}.
*
* @author Stephane Nicoll
*/
class InjectedConstructionResolverTests {
@Test
void resolveNoArgConstructor() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
InjectedElementAttributes attributes = createResolverForConstructor(
InjectedConstructionResolverTests.class).resolve(beanFactory);
assertThat(attributes.isResolved()).isTrue();
}
@ParameterizedTest
@MethodSource("singleArgConstruction")
void resolveSingleArgConstructor(InjectedConstructionResolver resolver) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("one", "1");
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
assertThat(attributes.isResolved()).isTrue();
assertThat((String) attributes.get(0)).isEqualTo("1");
}
@ParameterizedTest
@MethodSource("singleArgConstruction")
void resolveRequiredDependencyNotPresentThrowsUnsatisfiedDependencyException(InjectedConstructionResolver resolver) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
assertThatThrownBy(() -> resolver.resolve(beanFactory))
.isInstanceOfSatisfying(UnsatisfiedDependencyException.class, ex -> {
assertThat(ex.getBeanName()).isEqualTo("test");
assertThat(ex.getInjectionPoint()).isNotNull();
assertThat(ex.getInjectionPoint().getMember()).isEqualTo(resolver.getExecutable());
});
}
@ParameterizedTest
@MethodSource("arrayOfBeansConstruction")
void resolveArrayOfBeans(InjectedConstructionResolver resolver) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("one", "1");
beanFactory.registerSingleton("two", "2");
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
assertThat(attributes.isResolved()).isTrue();
Object attribute = attributes.get(0);
assertThat(Arrays.isArray(attribute)).isTrue();
assertThat((Object[]) attribute).containsExactly("1", "2");
}
@ParameterizedTest
@MethodSource("arrayOfBeansConstruction")
void resolveRequiredArrayOfBeansInjectEmptyArray(InjectedConstructionResolver resolver) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
assertThat(attributes.isResolved()).isTrue();
Object attribute = attributes.get(0);
assertThat(Arrays.isArray(attribute)).isTrue();
assertThat((Object[]) attribute).isEmpty();
}
static Stream<Arguments> arrayOfBeansConstruction() {
return Stream.of(Arguments.of(createResolverForConstructor(BeansCollectionConstructor.class, String[].class)),
Arguments.of(createResolverForFactoryMethod(BeansCollectionFactory.class, "array", String[].class)));
}
@ParameterizedTest
@MethodSource("listOfBeansConstruction")
void resolveListOfBeans(InjectedConstructionResolver resolver) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("one", "1");
beanFactory.registerSingleton("two", "2");
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
assertThat(attributes.isResolved()).isTrue();
Object attribute = attributes.get(0);
assertThat(attribute).isInstanceOf(List.class).asList().containsExactly("1", "2");
}
@ParameterizedTest
@MethodSource("listOfBeansConstruction")
void resolveRequiredListOfBeansInjectEmptyList(InjectedConstructionResolver resolver) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
assertThat(attributes.isResolved()).isTrue();
Object attribute = attributes.get(0);
assertThat(attribute).isInstanceOf(List.class);
assertThat((List<?>) attribute).isEmpty();
}
static Stream<Arguments> listOfBeansConstruction() {
return Stream.of(Arguments.of(createResolverForConstructor(BeansCollectionConstructor.class, List.class)),
Arguments.of(createResolverForFactoryMethod(BeansCollectionFactory.class, "list", List.class)));
}
@ParameterizedTest
@MethodSource("setOfBeansConstruction")
@SuppressWarnings("unchecked")
void resolveSetOfBeans(InjectedConstructionResolver resolver) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("one", "1");
beanFactory.registerSingleton("two", "2");
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
assertThat(attributes.isResolved()).isTrue();
Object attribute = attributes.get(0);
assertThat(attribute).isInstanceOf(Set.class);
assertThat((Set<String>) attribute).containsExactly("1", "2");
}
@ParameterizedTest
@MethodSource("setOfBeansConstruction")
void resolveRequiredSetOfBeansInjectEmptySet(InjectedConstructionResolver resolver) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
assertThat(attributes.isResolved()).isTrue();
Object attribute = attributes.get(0);
assertThat(attribute).isInstanceOf(Set.class);
assertThat((Set<?>) attribute).isEmpty();
}
static Stream<Arguments> setOfBeansConstruction() {
return Stream.of(Arguments.of(createResolverForConstructor(BeansCollectionConstructor.class, Set.class)),
Arguments.of(createResolverForFactoryMethod(BeansCollectionFactory.class, "set", Set.class)));
}
@ParameterizedTest
@MethodSource("mapOfBeansConstruction")
@SuppressWarnings("unchecked")
void resolveMapOfBeans(InjectedConstructionResolver resolver) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("one", "1");
beanFactory.registerSingleton("two", "2");
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
assertThat(attributes.isResolved()).isTrue();
Object attribute = attributes.get(0);
assertThat(attribute).isInstanceOf(Map.class);
assertThat((Map<String, String>) attribute).containsExactly(entry("one", "1"), entry("two", "2"));
}
@ParameterizedTest
@MethodSource("mapOfBeansConstruction")
void resolveRequiredMapOfBeansInjectEmptySet(InjectedConstructionResolver resolver) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
assertThat(attributes.isResolved()).isTrue();
Object attribute = attributes.get(0);
assertThat(attribute).isInstanceOf(Map.class);
assertThat((Map<?, ?>) attribute).isEmpty();
}
static Stream<Arguments> mapOfBeansConstruction() {
return Stream.of(Arguments.of(createResolverForConstructor(BeansCollectionConstructor.class, Map.class)),
Arguments.of(createResolverForFactoryMethod(BeansCollectionFactory.class, "map", Map.class)));
}
@ParameterizedTest
@MethodSource("multiArgsConstruction")
void resolveMultiArgsConstructor(InjectedConstructionResolver resolver) {
ResourceLoader resourceLoader = new DefaultResourceLoader();
Environment environment = mock(Environment.class);
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerResolvableDependency(ResourceLoader.class, resourceLoader);
beanFactory.registerSingleton("environment", environment);
beanFactory.registerSingleton("one", "1");
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
assertThat(attributes.isResolved()).isTrue();
assertThat((ResourceLoader) attributes.get(0)).isEqualTo(resourceLoader);
assertThat((Environment) attributes.get(1)).isEqualTo(environment);
ObjectProvider<String> provider = attributes.get(2);
assertThat(provider.getIfAvailable()).isEqualTo("1");
}
@ParameterizedTest
@MethodSource("mixedArgsConstruction")
void resolveMixedArgsConstructorWithUserValue(InjectedConstructionResolver resolver) {
ResourceLoader resourceLoader = new DefaultResourceLoader();
Environment environment = mock(Environment.class);
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerResolvableDependency(ResourceLoader.class, resourceLoader);
beanFactory.registerSingleton("environment", environment);
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(MixedArgsConstructor.class)
.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR).getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1, "user-value");
beanFactory.registerBeanDefinition("test", beanDefinition);
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
assertThat(attributes.isResolved()).isTrue();
assertThat((ResourceLoader) attributes.get(0)).isEqualTo(resourceLoader);
assertThat((String) attributes.get(1)).isEqualTo("user-value");
assertThat((Environment) attributes.get(2)).isEqualTo(environment);
}
@ParameterizedTest
@MethodSource("mixedArgsConstruction")
void resolveMixedArgsConstructorWithUserBeanReference(InjectedConstructionResolver resolver) {
ResourceLoader resourceLoader = new DefaultResourceLoader();
Environment environment = mock(Environment.class);
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerResolvableDependency(ResourceLoader.class, resourceLoader);
beanFactory.registerSingleton("environment", environment);
beanFactory.registerSingleton("one", "1");
beanFactory.registerSingleton("two", "2");
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(MixedArgsConstructor.class)
.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR).getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1, new RuntimeBeanReference("two"));
beanFactory.registerBeanDefinition("test", beanDefinition);
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
assertThat(attributes.isResolved()).isTrue();
assertThat((ResourceLoader) attributes.get(0)).isEqualTo(resourceLoader);
assertThat((String) attributes.get(1)).isEqualTo("2");
assertThat((Environment) attributes.get(2)).isEqualTo(environment);
}
@Test
void resolveUserValueWithTypeConversionRequired() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(CharDependency.class)
.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR).getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, "\\");
beanFactory.registerBeanDefinition("test", beanDefinition);
InjectedElementAttributes attributes = createResolverForConstructor(CharDependency.class, char.class).resolve(beanFactory);
assertThat(attributes.isResolved()).isTrue();
Object attribute = attributes.get(0);
assertThat(attribute).isInstanceOf(Character.class);
assertThat((Character) attribute).isEqualTo('\\');
}
@ParameterizedTest
@MethodSource("singleArgConstruction")
void resolveUserValueWithBeanReference(InjectedConstructionResolver resolver) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("stringBean", "string");
beanFactory.registerBeanDefinition("test", BeanDefinitionBuilder.rootBeanDefinition(SingleArgConstructor.class)
.addConstructorArgReference("stringBean").getBeanDefinition());
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
assertThat(attributes.isResolved()).isTrue();
Object attribute = attributes.get(0);
assertThat(attribute).isEqualTo("string");
}
@ParameterizedTest
@MethodSource("singleArgConstruction")
void resolveUserValueWithBeanDefinition(InjectedConstructionResolver resolver) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AbstractBeanDefinition userValue = BeanDefinitionBuilder.rootBeanDefinition(String.class, () -> "string").getBeanDefinition();
beanFactory.registerBeanDefinition("test", BeanDefinitionBuilder.rootBeanDefinition(SingleArgConstructor.class)
.addConstructorArgValue(userValue).getBeanDefinition());
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
assertThat(attributes.isResolved()).isTrue();
Object attribute = attributes.get(0);
assertThat(attribute).isEqualTo("string");
}
@ParameterizedTest
@MethodSource("singleArgConstruction")
void resolveUserValueThatIsAlreadyResolved(InjectedConstructionResolver resolver) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(SingleArgConstructor.class).getBeanDefinition();
ValueHolder valueHolder = new ValueHolder('a');
valueHolder.setConvertedValue("this is an a");
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, valueHolder);
beanFactory.registerBeanDefinition("test", beanDefinition);
InjectedElementAttributes attributes = resolver.resolve(beanFactory);
assertThat(attributes.isResolved()).isTrue();
Object attribute = attributes.get(0);
assertThat(attribute).isEqualTo("this is an a");
}
@ParameterizedTest
@MethodSource("singleArgConstruction")
void createInvokeFactory(InjectedConstructionResolver resolver) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("one", "1");
String instance = resolver.create(beanFactory, attributes -> attributes.get(0));
assertThat(instance).isEqualTo("1");
}
private static InjectedConstructionResolver createResolverForConstructor(Class<?> beanType, Class<?>... parameterTypes) {
try {
Constructor<?> executable = beanType.getDeclaredConstructor(parameterTypes);
return new InjectedConstructionResolver(executable, beanType, "test",
InjectedConstructionResolverTests::safeGetBeanDefinition);
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException(ex);
}
}
private static InjectedConstructionResolver createResolverForFactoryMethod(Class<?> targetType,
String methodName, Class<?>... parameterTypes) {
Method executable = ReflectionUtils.findMethod(targetType, methodName, parameterTypes);
return new InjectedConstructionResolver(executable, targetType, "test",
InjectedConstructionResolverTests::safeGetBeanDefinition);
}
private static BeanDefinition safeGetBeanDefinition(DefaultListableBeanFactory beanFactory) {
try {
return beanFactory.getBeanDefinition("test");
}
catch (NoSuchBeanDefinitionException ex) {
return null;
}
}
static Stream<Arguments> singleArgConstruction() {
return Stream.of(Arguments.of(createResolverForConstructor(SingleArgConstructor.class, String.class)),
Arguments.of(createResolverForFactoryMethod(SingleArgFactory.class, "single", String.class)));
}
@SuppressWarnings("unused")
static class SingleArgConstructor {
public SingleArgConstructor(String s) {
}
}
@SuppressWarnings("unused")
static class SingleArgFactory {
String single(String s) {
return s;
}
}
@SuppressWarnings("unused")
static class BeansCollectionConstructor {
public BeansCollectionConstructor(String[] beans) {
}
public BeansCollectionConstructor(List<String> beans) {
}
public BeansCollectionConstructor(Set<String> beans) {
}
public BeansCollectionConstructor(Map<String, String> beans) {
}
}
@SuppressWarnings("unused")
static class BeansCollectionFactory {
public String array(String[] beans) {
return "test";
}
public String list(List<String> beans) {
return "test";
}
public String set(Set<String> beans) {
return "test";
}
public String map(Map<String, String> beans) {
return "test";
}
}
static Stream<Arguments> multiArgsConstruction() {
return Stream.of(
Arguments.of(createResolverForConstructor(MultiArgsConstructor.class, ResourceLoader.class,
Environment.class, ObjectProvider.class)),
Arguments.of(createResolverForFactoryMethod(MultiArgsFactory.class, "multiArgs", ResourceLoader.class,
Environment.class, ObjectProvider.class)));
}
@SuppressWarnings("unused")
static class MultiArgsConstructor {
public MultiArgsConstructor(ResourceLoader resourceLoader, Environment environment, ObjectProvider<String> provider) {
}
}
@SuppressWarnings("unused")
static class MultiArgsFactory {
String multiArgs(ResourceLoader resourceLoader, Environment environment, ObjectProvider<String> provider) {
return "test";
}
}
static Stream<Arguments> mixedArgsConstruction() {
return Stream.of(
Arguments.of(createResolverForConstructor(MixedArgsConstructor.class, ResourceLoader.class,
String.class, Environment.class)),
Arguments.of(createResolverForFactoryMethod(MixedArgsFactory.class, "mixedArgs", ResourceLoader.class,
String.class, Environment.class)));
}
@SuppressWarnings("unused")
static class MixedArgsConstructor {
public MixedArgsConstructor(ResourceLoader resourceLoader, String test, Environment environment) {
}
}
@SuppressWarnings("unused")
static class MixedArgsFactory {
String mixedArgs(ResourceLoader resourceLoader, String test, Environment environment) {
return "test";
}
}
@SuppressWarnings("unused")
static class CharDependency {
CharDependency(char escapeChar) {
}
}
}

View File

@ -1,91 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator.config;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
/**
* Tests for {@link InjectedElementAttributes}.
*
* @author Stephane Nicoll
*/
class InjectedElementAttributesTests {
private static final InjectedElementAttributes unresolved = new InjectedElementAttributes(null);
private static final InjectedElementAttributes resolved = new InjectedElementAttributes(Collections.singletonList("test"));
@Test
void isResolvedWithUnresolvedAttributes() {
assertThat(unresolved.isResolved()).isFalse();
}
@Test
void isResolvedWithResoledAttributes() {
assertThat(resolved.isResolved()).isTrue();
}
@Test
void ifResolvedWithUnresolvedAttributesDoesNotInvokeRunnable() {
Runnable runnable = mock(Runnable.class);
unresolved.ifResolved(runnable);
verifyNoInteractions(runnable);
}
@Test
void ifResolvedWithResolvedAttributesInvokesRunnable() {
Runnable runnable = mock(Runnable.class);
resolved.ifResolved(runnable);
verify(runnable).run();
}
@Test
@SuppressWarnings("unchecked")
void ifResolvedWithUnresolvedAttributesDoesNotInvokeConsumer() {
BeanDefinitionRegistrar.ThrowableConsumer<InjectedElementAttributes> consumer = mock(BeanDefinitionRegistrar.ThrowableConsumer.class);
unresolved.ifResolved(consumer);
verifyNoInteractions(consumer);
}
@Test
@SuppressWarnings("unchecked")
void ifResolvedWithResolvedAttributesInvokesConsumer() {
BeanDefinitionRegistrar.ThrowableConsumer<InjectedElementAttributes> consumer = mock(BeanDefinitionRegistrar.ThrowableConsumer.class);
resolved.ifResolved(consumer);
verify(consumer).accept(resolved);
}
@Test
void getWithAvailableAttribute() {
InjectedElementAttributes attributes = new InjectedElementAttributes(Collections.singletonList("test"));
assertThat((String) attributes.get(0)).isEqualTo("test");
}
@Test
void getWithTypeAndAvailableAttribute() {
InjectedElementAttributes attributes = new InjectedElementAttributes(Collections.singletonList("test"));
assertThat(attributes.get(0, String.class)).isEqualTo("test");
}
}

View File

@ -1,79 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator.config;
import java.lang.reflect.Field;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link InjectedFieldResolver}.
*
* @author Stephane Nicoll
*/
class InjectedFieldResolverTests {
@Test
void resolveDependency() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("one", "1");
InjectedElementAttributes attributes = createResolver(TestBean.class, "string",
String.class).resolve(beanFactory, true);
assertThat(attributes.isResolved()).isTrue();
assertThat((String) attributes.get(0)).isEqualTo("1");
}
@Test
void resolveRequiredDependencyNotPresentThrowsUnsatisfiedDependencyException() {
Field field = ReflectionUtils.findField(TestBean.class, "string", String.class);
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
assertThatThrownBy(() -> createResolver(TestBean.class, "string", String.class).resolve(beanFactory))
.isInstanceOfSatisfying(UnsatisfiedDependencyException.class, ex -> {
assertThat(ex.getBeanName()).isEqualTo("test");
assertThat(ex.getInjectionPoint()).isNotNull();
assertThat(ex.getInjectionPoint().getField()).isEqualTo(field);
});
}
@Test
void resolveNonRequiredDependency() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
InjectedElementAttributes attributes = createResolver(TestBean.class, "string", String.class).resolve(beanFactory, false);
assertThat(attributes.isResolved()).isFalse();
}
private InjectedFieldResolver createResolver(Class<?> beanType, String fieldName, Class<?> fieldType) {
Field field = ReflectionUtils.findField(beanType, fieldName, fieldType);
assertThat(field).isNotNull();
return new InjectedFieldResolver(field, "test");
}
static class TestBean {
@SuppressWarnings("unused")
private String string;
}
}

View File

@ -1,124 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator.config;
import java.lang.reflect.Method;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.env.Environment;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
/**
* Tests for {@link InjectedMethodResolver}.
*
* @author Stephane Nicoll
*/
class InjectedMethodResolverTests {
@Test
void resolveSingleDependency() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("test", "testValue");
InjectedElementAttributes attributes = createResolver(TestBean.class, "injectString", String.class)
.resolve(beanFactory, true);
assertThat(attributes.isResolved()).isTrue();
assertThat((String) attributes.get(0)).isEqualTo("testValue");
}
@Test
void resolveRequiredDependencyNotPresentThrowsUnsatisfiedDependencyException() {
Method method = ReflectionUtils.findMethod(TestBean.class, "injectString", String.class);
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
assertThatThrownBy(() -> createResolver(TestBean.class, "injectString", String.class)
.resolve(beanFactory)).isInstanceOfSatisfying(UnsatisfiedDependencyException.class, ex -> {
assertThat(ex.getBeanName()).isEqualTo("test");
assertThat(ex.getInjectionPoint()).isNotNull();
assertThat(ex.getInjectionPoint().getMember()).isEqualTo(method);
});
}
@Test
void resolveNonRequiredDependency() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
InjectedElementAttributes attributes = createResolver(TestBean.class, "injectString", String.class)
.resolve(beanFactory, false);
assertThat(attributes.isResolved()).isFalse();
}
@Test
void resolveDependencyAndEnvironment() {
Environment environment = mock(Environment.class);
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("environment", environment);
beanFactory.registerSingleton("test", "testValue");
InjectedElementAttributes attributes = createResolver(TestBean.class, "injectStringAndEnvironment",
String.class, Environment.class).resolve(beanFactory, true);
assertThat(attributes.isResolved()).isTrue();
String string = attributes.get(0);
assertThat(string).isEqualTo("testValue");
assertThat((Environment) attributes.get(1)).isEqualTo(environment);
}
@Test
@SuppressWarnings("unchecked")
void createWithUnresolvedAttributesDoesNotInvokeCallback() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
BeanDefinitionRegistrar.ThrowableFunction<InjectedElementAttributes, ?> callback = mock(BeanDefinitionRegistrar.ThrowableFunction.class);
assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() ->
createResolver(TestBean.class, "injectString", String.class).create(beanFactory, callback));
verifyNoInteractions(callback);
}
@Test
@SuppressWarnings("unchecked")
void invokeWithUnresolvedAttributesDoesNotInvokeCallback() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
BeanDefinitionRegistrar.ThrowableConsumer<InjectedElementAttributes> callback = mock(BeanDefinitionRegistrar.ThrowableConsumer.class);
assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() ->
createResolver(TestBean.class, "injectString", String.class).invoke(beanFactory, callback));
verifyNoInteractions(callback);
}
private InjectedMethodResolver createResolver(Class<?> beanType, String methodName, Class<?>... parameterTypes) {
Method method = ReflectionUtils.findMethod(beanType, methodName, parameterTypes);
assertThat(method).isNotNull();
return new InjectedMethodResolver(method, beanType, "test");
}
@SuppressWarnings("unused")
static class TestBean {
public void injectString(String string) {
}
public void injectStringAndEnvironment(String string, Environment environment) {
}
}
}

View File

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

View File

@ -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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +0,0 @@
org.springframework.beans.factory.generator.AotContributingBeanFactoryPostProcessor= \
org.springframework.context.generator.ApplicationContextAotGeneratorTests.NoOpAotContributingBeanFactoryPostProcessor, \
org.springframework.context.generator.ApplicationContextAotGeneratorTests.TextAotContributingBeanFactoryPostProcessor

View File

@ -1,2 +0,0 @@
org.springframework.aot.hint.RuntimeHintsRegistrar= \
org.springframework.context.generator.RuntimeHintsPostProcessorTests.SampleRuntimeHintsRegistrar

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.
*

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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