Browse Source
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-28088pull/28170/head
11 changed files with 1965 additions and 8 deletions
@ -0,0 +1,34 @@
@@ -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 @@
@@ -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 @@
@@ -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); |
||||
|
||||
} |
||||
|
||||
@ -0,0 +1,469 @@
@@ -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 @@
@@ -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); |
||||
|
||||
} |
||||
@ -0,0 +1,494 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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