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 {
|
||||
|
||||
/**
|
||||
* A default instance that does not handle inner bean definitions.
|
||||
*/
|
||||
public static final BeanParameterGenerator INSTANCE = new BeanParameterGenerator();
|
||||
|
||||
private final ResolvableTypeGenerator typeGenerator = new ResolvableTypeGenerator();
|
||||
|
||||
private final Function<BeanDefinition, CodeBlock> innerBeanDefinitionGenerator;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* Generate the necessary statements to instantiate a bean.
|
||||
* Default {@link BeanInstantiationGenerator} implementation.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @see BeanInstantiationContribution
|
||||
*/
|
||||
class DefaultBeanInstantiationGenerator {
|
||||
class DefaultBeanInstantiationGenerator implements BeanInstantiationGenerator {
|
||||
|
||||
private final Executable instanceCreator;
|
||||
|
||||
|
|
@ -57,12 +57,12 @@ class DefaultBeanInstantiationGenerator {
|
|||
.assignReturnType(member -> !this.contributions.isEmpty()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the necessary code to instantiate and post-process the bean
|
||||
* handled by this instance.
|
||||
* @param runtimeHints the runtime hints instance to use
|
||||
* @return a code contribution that provides an initialized bean instance
|
||||
*/
|
||||
@Override
|
||||
public Executable getInstanceCreator() {
|
||||
return this.instanceCreator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeContribution generateBeanInstantiation(RuntimeHints runtimeHints) {
|
||||
DefaultCodeContribution codeContribution = new DefaultCodeContribution(runtimeHints);
|
||||
codeContribution.protectedAccess().analyze(this.instanceCreator, this.beanInstanceOptions);
|
||||
|
|
|
|||
|
|
@ -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