Introduce BeanFactoryContribution
This commit introduces an infrastructure to contribute generated code ahead of time to initialize a BeanFactory. Code and hints can be contributed to a BeanFactorInitialization, with the ability to write to other packages if necessary. An implementation of that new interface that registers a BeanDefinition is also included in this commit. It delegates to a BeanInstantiationGenerator for geenerating the instance supplier that creates the bean instance. For corner cases, a BeanRegistrationContributionProvider can be implemented. It allows to return a custom BeanFactoryContribution for a particualr bean definition. This usually uses the default implementation with a custom instance supplier. Note that this commit adds an temporary executable resolution that is meant to be replaced by the use of ConstructorResolver See gh-28088
This commit is contained in:
parent
cc57b55c61
commit
5bc701d4fe
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -49,6 +49,11 @@ import org.springframework.util.ObjectUtils;
|
||||||
*/
|
*/
|
||||||
public final class BeanParameterGenerator {
|
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 ResolvableTypeGenerator typeGenerator = new ResolvableTypeGenerator();
|
||||||
|
|
||||||
private final Function<BeanDefinition, CodeBlock> innerBeanDefinitionGenerator;
|
private final Function<BeanDefinition, CodeBlock> innerBeanDefinitionGenerator;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,469 @@
|
||||||
|
/*
|
||||||
|
* 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.AbstractBeanDefinition;
|
||||||
|
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.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 BeanDefinition beanDefinition;
|
||||||
|
|
||||||
|
private final BeanInstantiationGenerator beanInstantiationGenerator;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final DefaultBeanRegistrationContributionProvider innerBeanRegistrationContributionProvider;
|
||||||
|
|
||||||
|
private int nesting = 0;
|
||||||
|
|
||||||
|
BeanRegistrationBeanFactoryContribution(String beanName, BeanDefinition beanDefinition,
|
||||||
|
BeanInstantiationGenerator beanInstantiationGenerator,
|
||||||
|
@Nullable DefaultBeanRegistrationContributionProvider innerBeanRegistrationContributionProvider) {
|
||||||
|
this.beanName = beanName;
|
||||||
|
this.beanDefinition = beanDefinition;
|
||||||
|
this.beanInstantiationGenerator = beanInstantiationGenerator;
|
||||||
|
this.innerBeanRegistrationContributionProvider = innerBeanRegistrationContributionProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BeanRegistrationBeanFactoryContribution(String beanName, BeanDefinition beanDefinition,
|
||||||
|
BeanInstantiationGenerator beanInstantiationGenerator) {
|
||||||
|
this(beanName, beanDefinition, beanInstantiationGenerator, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getBeanName() {
|
||||||
|
return this.beanName;
|
||||||
|
}
|
||||||
|
|
||||||
|
BeanDefinition 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) {
|
||||||
|
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 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 BeanDefinition 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.toCodeBlock());
|
||||||
|
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();
|
||||||
|
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 instanceof AbstractBeanDefinition
|
||||||
|
&& ((AbstractBeanDefinition) 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.toCodeBlock(".customize((" + bdVariable + ") ->"));
|
||||||
|
code.add(")");
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -33,12 +33,12 @@ import org.springframework.javapoet.CodeBlock;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the necessary statements to instantiate a bean.
|
* Default {@link BeanInstantiationGenerator} implementation.
|
||||||
*
|
*
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
* @see BeanInstantiationContribution
|
* @see BeanInstantiationContribution
|
||||||
*/
|
*/
|
||||||
class DefaultBeanInstantiationGenerator {
|
class DefaultBeanInstantiationGenerator implements BeanInstantiationGenerator {
|
||||||
|
|
||||||
private final Executable instanceCreator;
|
private final Executable instanceCreator;
|
||||||
|
|
||||||
|
|
@ -57,12 +57,12 @@ class DefaultBeanInstantiationGenerator {
|
||||||
.assignReturnType(member -> !this.contributions.isEmpty()).build();
|
.assignReturnType(member -> !this.contributions.isEmpty()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Return the necessary code to instantiate and post-process the bean
|
public Executable getInstanceCreator() {
|
||||||
* handled by this instance.
|
return this.instanceCreator;
|
||||||
* @param runtimeHints the runtime hints instance to use
|
}
|
||||||
* @return a code contribution that provides an initialized bean instance
|
|
||||||
*/
|
@Override
|
||||||
public CodeContribution generateBeanInstantiation(RuntimeHints runtimeHints) {
|
public CodeContribution generateBeanInstantiation(RuntimeHints runtimeHints) {
|
||||||
DefaultCodeContribution codeContribution = new DefaultCodeContribution(runtimeHints);
|
DefaultCodeContribution codeContribution = new DefaultCodeContribution(runtimeHints);
|
||||||
codeContribution.protectedAccess().analyze(this.instanceCreator, this.beanInstanceOptions);
|
codeContribution.protectedAccess().analyze(this.instanceCreator, this.beanInstanceOptions);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,494 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
import org.springframework.beans.factory.generator.config.BeanDefinitionRegistrar;
|
||||||
|
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, BeanDefinition beanDefinition,
|
||||||
|
BeanInstantiationGenerator beanInstantiationGenerator,
|
||||||
|
DefaultBeanRegistrationContributionProvider innerBeanRegistrationContributionProvider) {
|
||||||
|
super(beanName, beanDefinition, beanInstantiationGenerator, innerBeanRegistrationContributionProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CodeBlock initializeBeanDefinitionRegistrar() {
|
||||||
|
return CodeBlock.of("$T.inner(", BeanDefinitionRegistrar.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,646 @@
|
||||||
|
/*
|
||||||
|
* 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.stream.Collectors;
|
||||||
|
|
||||||
|
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.beans.MutablePropertyValues;
|
||||||
|
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.ConstructorArgumentValues;
|
||||||
|
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.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.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.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();
|
||||||
|
getContribution(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();
|
||||||
|
getContribution(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();
|
||||||
|
getContribution(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());
|
||||||
|
getContribution(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 generateWithBeanDefinitionHavingSyntheticFlag() {
|
||||||
|
assertThat(simpleConfigurationRegistration(bd -> bd.setSynthetic(true)).getSnippet()).isEqualTo("""
|
||||||
|
BeanDefinitionRegistrar.of("test", SimpleConfiguration.class)
|
||||||
|
.instanceSupplier(() -> SimpleConfiguration::new).customize((bd) -> bd.setSynthetic(true)).register(beanFactory);
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void generateWithBeanDefinitionHavingDependsOn() {
|
||||||
|
assertThat(simpleConfigurationRegistration(bd -> bd.setDependsOn("test")).getSnippet()).isEqualTo("""
|
||||||
|
BeanDefinitionRegistrar.of("test", SimpleConfiguration.class)
|
||||||
|
.instanceSupplier(() -> SimpleConfiguration::new).customize((bd) -> bd.setDependsOn(new String[] { "test" })).register(beanFactory);
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void generateWithBeanDefinitionHavingLazyInit() {
|
||||||
|
assertThat(simpleConfigurationRegistration(bd -> bd.setLazyInit(true)).getSnippet()).isEqualTo("""
|
||||||
|
BeanDefinitionRegistrar.of("test", SimpleConfiguration.class)
|
||||||
|
.instanceSupplier(() -> SimpleConfiguration::new).customize((bd) -> bd.setLazyInit(true)).register(beanFactory);
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void generateWithBeanDefinitionHavingRole() {
|
||||||
|
assertThat(simpleConfigurationRegistration(bd -> bd.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)).getSnippet()).isEqualTo("""
|
||||||
|
BeanDefinitionRegistrar.of("test", SimpleConfiguration.class)
|
||||||
|
.instanceSupplier(() -> SimpleConfiguration::new).customize((bd) -> bd.setRole(2)).register(beanFactory);
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void generateWithBeanDefinitionHavingScope() {
|
||||||
|
assertThat(simpleConfigurationRegistration(bd -> bd.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)).getSnippet()).isEqualTo("""
|
||||||
|
BeanDefinitionRegistrar.of("test", SimpleConfiguration.class)
|
||||||
|
.instanceSupplier(() -> SimpleConfiguration::new).customize((bd) -> bd.setScope("prototype")).register(beanFactory);
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void generateWithBeanDefinitionHavingAutowiredCandidate() {
|
||||||
|
assertThat(simpleConfigurationRegistration(bd -> bd.setAutowireCandidate(false)).getSnippet()).isEqualTo("""
|
||||||
|
BeanDefinitionRegistrar.of("test", SimpleConfiguration.class)
|
||||||
|
.instanceSupplier(() -> SimpleConfiguration::new).customize((bd) -> bd.setAutowireCandidate(false)).register(beanFactory);
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void generateWithBeanDefinitionHavingDefaultAutowiredCandidateDoesNotConfigureIt() {
|
||||||
|
assertThat(simpleConfigurationRegistration(bd -> bd.setAutowireCandidate(true)).getSnippet())
|
||||||
|
.doesNotContain("bd.setAutowireCandidate(");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void generateWithBeanDefinitionHavingMultipleAttributes() {
|
||||||
|
assertThat(simpleConfigurationRegistration(bd -> {
|
||||||
|
bd.setSynthetic(true);
|
||||||
|
bd.setPrimary(true);
|
||||||
|
}).getSnippet()).isEqualTo("""
|
||||||
|
BeanDefinitionRegistrar.of("test", SimpleConfiguration.class)
|
||||||
|
.instanceSupplier(() -> SimpleConfiguration::new).customize((bd) -> {
|
||||||
|
bd.setPrimary(true);
|
||||||
|
bd.setSynthetic(true);
|
||||||
|
}).register(beanFactory);
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void generateWithBeanDefinitionHavingProperty() {
|
||||||
|
assertThat(simpleConfigurationRegistration(bd -> bd.getPropertyValues().addPropertyValue("test", "Hello")).getSnippet()).isEqualTo("""
|
||||||
|
BeanDefinitionRegistrar.of("test", SimpleConfiguration.class)
|
||||||
|
.instanceSupplier(() -> SimpleConfiguration::new).customize((bd) -> bd.getPropertyValues().addPropertyValue("test", "Hello")).register(beanFactory);
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void generateWithBeanDefinitionHavingSeveralProperties() {
|
||||||
|
CodeSnippet registration = simpleConfigurationRegistration(bd -> {
|
||||||
|
bd.getPropertyValues().addPropertyValue("test", "Hello");
|
||||||
|
bd.getPropertyValues().addPropertyValue("counter", 42);
|
||||||
|
});
|
||||||
|
assertThat(registration.getSnippet()).isEqualTo("""
|
||||||
|
BeanDefinitionRegistrar.of("test", SimpleConfiguration.class)
|
||||||
|
.instanceSupplier(() -> SimpleConfiguration::new).customize((bd) -> {
|
||||||
|
MutablePropertyValues propertyValues = bd.getPropertyValues();
|
||||||
|
propertyValues.addPropertyValue("test", "Hello");
|
||||||
|
propertyValues.addPropertyValue("counter", 42);
|
||||||
|
}).register(beanFactory);
|
||||||
|
""");
|
||||||
|
assertThat(registration.hasImport(MutablePropertyValues.class)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void generateWithBeanDefinitionHavingPropertyReference() {
|
||||||
|
CodeSnippet registration = simpleConfigurationRegistration(bd -> bd.getPropertyValues()
|
||||||
|
.addPropertyValue("myService", new RuntimeBeanReference("test")));
|
||||||
|
assertThat(registration.getSnippet()).isEqualTo("""
|
||||||
|
BeanDefinitionRegistrar.of("test", SimpleConfiguration.class)
|
||||||
|
.instanceSupplier(() -> SimpleConfiguration::new).customize((bd) -> bd.getPropertyValues().addPropertyValue("myService", new RuntimeBeanReference("test"))).register(beanFactory);
|
||||||
|
""");
|
||||||
|
assertThat(registration.hasImport(RuntimeBeanReference.class)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
getContribution(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()).toBeanDefinition())).register(beanFactory);
|
||||||
|
""");
|
||||||
|
assertThat(registration.hasImport(SimpleConfiguration.class)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
getContribution(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("names", List.of(BeanDefinitionRegistrar.inner(SimpleConfiguration.class).withFactoryMethod(SimpleConfiguration.class, "stringBean")
|
||||||
|
.instanceSupplier(() -> beanFactory.getBean(SimpleConfiguration.class).stringBean()).toBeanDefinition(), BeanDefinitionRegistrar.inner(SimpleConfiguration.class).withFactoryMethod(SimpleConfiguration.class, "stringBean")
|
||||||
|
.instanceSupplier(() -> beanFactory.getBean(SimpleConfiguration.class).stringBean()).toBeanDefinition()))).register(beanFactory);
|
||||||
|
""");
|
||||||
|
assertThat(registration.hasImport(SimpleConfiguration.class)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
getContribution(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");
|
||||||
|
CodeSnippet registration = beanRegistration(beanDefinition, method(SampleFactory.class, "create", String.class),
|
||||||
|
code -> code.add("() -> test"));
|
||||||
|
assertThat(registration.getSnippet()).isEqualTo("""
|
||||||
|
BeanDefinitionRegistrar.of("test", String.class).withFactoryMethod(SampleFactory.class, "create", String.class)
|
||||||
|
.instanceSupplier(() -> test).customize((bd) -> bd.getConstructorArgumentValues().addIndexedArgumentValue(0, "hello")).register(beanFactory);
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void generateUsingSeveralConstructorArguments() {
|
||||||
|
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(String.class)
|
||||||
|
.addConstructorArgValue(42).addConstructorArgReference("testBean")
|
||||||
|
.getBeanDefinition();
|
||||||
|
CodeSnippet registration = beanRegistration(beanDefinition, method(SampleFactory.class, "create", Number.class, String.class),
|
||||||
|
code -> code.add("() -> test"));
|
||||||
|
assertThat(registration.getSnippet()).isEqualTo("""
|
||||||
|
BeanDefinitionRegistrar.of("test", String.class).withFactoryMethod(SampleFactory.class, "create", Number.class, String.class)
|
||||||
|
.instanceSupplier(() -> test).customize((bd) -> {
|
||||||
|
ConstructorArgumentValues argumentValues = bd.getConstructorArgumentValues();
|
||||||
|
argumentValues.addIndexedArgumentValue(0, 42);
|
||||||
|
argumentValues.addIndexedArgumentValue(1, new RuntimeBeanReference("testBean"));
|
||||||
|
}).register(beanFactory);
|
||||||
|
""");
|
||||||
|
assertThat(registration.hasImport(ConstructorArgumentValues.class)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void registerRuntimeHintsWithNoPropertyValuesDoesNotAccessRuntimeHints() {
|
||||||
|
RootBeanDefinition bd = new RootBeanDefinition(String.class);
|
||||||
|
RuntimeHints runtimeHints = mock(RuntimeHints.class);
|
||||||
|
getContribution(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();
|
||||||
|
getContribution(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();
|
||||||
|
getContribution(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(methodHint("setName", String.class));
|
||||||
|
assertThat(typeHint.fields()).isEmpty();
|
||||||
|
}).anySatisfy(typeHint -> {
|
||||||
|
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IntegerFactoryBean.class));
|
||||||
|
assertThat(typeHint.constructors()).singleElement()
|
||||||
|
.satisfies(constructorHint(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();
|
||||||
|
getContribution(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(methodHint("setName", String.class))
|
||||||
|
.anySatisfy(methodHint("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);
|
||||||
|
getContribution(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(methodHint("setCounter", Integer.class));
|
||||||
|
assertThat(typeHint.fields()).isEmpty();
|
||||||
|
}).anySatisfy(typeHint -> {
|
||||||
|
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(BaseFactoryBean.class));
|
||||||
|
assertThat(typeHint.methods()).singleElement().satisfies(methodHint("setName", String.class));
|
||||||
|
}).anySatisfy(typeHint -> {
|
||||||
|
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IntegerFactoryBean.class));
|
||||||
|
assertThat(typeHint.constructors()).singleElement().satisfies(constructorHint(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);
|
||||||
|
getContribution(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(methodHint("setCounters", List.class));
|
||||||
|
assertThat(typeHint.fields()).isEmpty();
|
||||||
|
}).anySatisfy(typeHint -> {
|
||||||
|
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(BaseFactoryBean.class));
|
||||||
|
assertThat(typeHint.methods()).singleElement().satisfies(methodHint("setName", String.class));
|
||||||
|
}).anySatisfy(typeHint -> {
|
||||||
|
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IntegerFactoryBean.class));
|
||||||
|
assertThat(typeHint.constructors()).singleElement().satisfies(constructorHint(Environment.class));
|
||||||
|
}).anySatisfy(typeHint -> {
|
||||||
|
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(AnotherIntegerFactoryBean.class));
|
||||||
|
assertThat(typeHint.constructors()).singleElement().satisfies(constructorHint(Environment.class));
|
||||||
|
}).hasSize(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Consumer<ExecutableHint> methodHint(String name, Class<?>... parameterTypes) {
|
||||||
|
return executableHint -> {
|
||||||
|
assertThat(executableHint.getName()).isEqualTo(name);
|
||||||
|
assertThat(executableHint.getParameterTypes()).containsExactly(Arrays.stream(parameterTypes)
|
||||||
|
.map(TypeReference::of).toArray(TypeReference[]::new));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Consumer<ExecutableHint> constructorHint(Class<?>... parameterTypes) {
|
||||||
|
return methodHint("<init>", parameterTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private CodeSnippet simpleConfigurationRegistration(Consumer<RootBeanDefinition> bd) {
|
||||||
|
RootBeanDefinition beanDefinition = (RootBeanDefinition) BeanDefinitionBuilder
|
||||||
|
.rootBeanDefinition(SimpleConfiguration.class).getBeanDefinition();
|
||||||
|
bd.accept(beanDefinition);
|
||||||
|
return beanRegistration(beanDefinition, singleConstructor(SimpleConfiguration.class),
|
||||||
|
code -> code.add("() -> SimpleConfiguration::new"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeanRegistrationBeanFactoryContribution getContribution(DefaultListableBeanFactory beanFactory, BeanDefinition beanDefinition) {
|
||||||
|
BeanRegistrationBeanFactoryContribution contribution = new DefaultBeanRegistrationContributionProvider(beanFactory)
|
||||||
|
.getContributionFor("test", (RootBeanDefinition) beanDefinition);
|
||||||
|
assertThat(contribution).isNotNull();
|
||||||
|
return contribution;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeanFactoryContribution getContribution(BeanDefinition beanDefinition, Executable instanceCreator) {
|
||||||
|
return new BeanRegistrationBeanFactoryContribution("test", beanDefinition,
|
||||||
|
new DefaultBeanInstantiationGenerator(instanceCreator, Collections.emptyList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private CodeSnippet beanRegistration(BeanDefinition beanDefinition, Executable instanceCreator, Consumer<Builder> instanceSupplier) {
|
||||||
|
BeanRegistrationBeanFactoryContribution generator = new BeanRegistrationBeanFactoryContribution("test", 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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue