Add AOT interfaces and classes to support bean factories

Add AOT processor and contribution interfaces and classes to
support the generation of code that can re-hydrate a bean
factory.

See gh-28414
This commit is contained in:
Phillip Webb
2022-04-13 20:20:59 -07:00
parent c5c68a4662
commit 4d87071f3a
46 changed files with 6492 additions and 1 deletions

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.function.Predicate;
import org.springframework.javapoet.CodeBlock;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* Code generator to apply {@link AutowiredArguments}.
* <p>
* Generates code in the form:<pre class="code">{@code
* args.get(0), args.get(1)
* }</pre> or <pre class="code">{@code
* args.get(0, String.class), args.get(1, Integer.class)
* }</pre>
* <p>
* The simpler form is only used if the target method or constructor is
* unambiguous.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 6.0
*/
public class AutowiredArgumentsCodeGenerator {
private final Class<?> target;
private final Executable executable;
public AutowiredArgumentsCodeGenerator(Class<?> target, Executable executable) {
this.target = target;
this.executable = executable;
}
public CodeBlock generateCode(Class<?>[] parameterTypes) {
return generateCode(parameterTypes, 0, "args");
}
public CodeBlock generateCode(Class<?>[] parameterTypes, int startIndex) {
return generateCode(parameterTypes, startIndex, "args");
}
public CodeBlock generateCode(Class<?>[] parameterTypes, int startIndex,
String variableName) {
Assert.notNull(parameterTypes, "ParameterTypes must not be null");
Assert.notNull(variableName, "VariableName must not be null");
boolean ambiguous = isAmbiguous();
CodeBlock.Builder builder = CodeBlock.builder();
for (int i = startIndex; i < parameterTypes.length; i++) {
builder.add((i != startIndex) ? ", " : "");
if (!ambiguous) {
builder.add("$L.get($L)", variableName, i - startIndex);
}
else {
builder.add("$L.get($L, $T.class)", variableName, i - startIndex,
parameterTypes[i]);
}
}
return builder.build();
}
private boolean isAmbiguous() {
if (this.executable instanceof Constructor<?> constructor) {
return Arrays.stream(this.target.getDeclaredConstructors())
.filter(Predicate.not(constructor::equals))
.anyMatch(this::hasSameParameterCount);
}
if (this.executable instanceof Method method) {
return Arrays.stream(ReflectionUtils.getAllDeclaredMethods(this.target))
.filter(Predicate.not(method::equals))
.filter(candidate -> candidate.getName().equals(method.getName()))
.anyMatch(this::hasSameParameterCount);
}
return true;
}
private boolean hasSameParameterCount(Executable executable) {
return this.executable.getParameterCount() == executable.getParameterCount();
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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.aot;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.Assert;
/**
* AOT specific factory loading mechanism for internal use within the framework.
* <p>
* Loads and instantiates factories of a given type from
* {@value #FACTORIES_RESOURCE_LOCATION} and merges them with matching beans
* from a {@link ListableBeanFactory}.
*
* @author Phillip Webb
* @since 6.0
* @see SpringFactoriesLoader
*/
public class AotFactoriesLoader {
/**
* The location to look for AOT factories.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring/aot.factories";
private final ListableBeanFactory beanFactory;
private final SpringFactoriesLoader factoriesLoader;
/**
* Create a new {@link AotFactoriesLoader} instance backed by the given bean
* factory.
* @param beanFactory the bean factory to use
*/
public AotFactoriesLoader(ListableBeanFactory beanFactory) {
Assert.notNull(beanFactory, "BeanFactory must not be null");
ClassLoader classLoader = (beanFactory instanceof ConfigurableBeanFactory configurableBeanFactory)
? configurableBeanFactory.getBeanClassLoader() : null;
this.beanFactory = beanFactory;
this.factoriesLoader = SpringFactoriesLoader.forResourceLocation(classLoader,
FACTORIES_RESOURCE_LOCATION);
}
/**
* Create a new {@link AotFactoriesLoader} instance backed by the given bean
* factory and loading items from the given {@link SpringFactoriesLoader}
* rather than from {@value #FACTORIES_RESOURCE_LOCATION}.
* @param beanFactory the bean factory to use
* @param factoriesLoader the factories loader to use
*/
public AotFactoriesLoader(ListableBeanFactory beanFactory,
SpringFactoriesLoader factoriesLoader) {
Assert.notNull(beanFactory, "BeanFactory must not be null");
Assert.notNull(factoriesLoader, "FactoriesLoader must not be null");
this.beanFactory = beanFactory;
this.factoriesLoader = factoriesLoader;
}
/**
* Load items from factories file and merge them with any beans defined in
* the {@link DefaultListableBeanFactory}.
* @param <T> the item type
* @param type the item type to load
* @return a list of loaded instances
*/
public <T> List<T> load(Class<T> type) {
List<T> result = new ArrayList<>();
result.addAll(BeanFactoryUtils
.beansOfTypeIncludingAncestors(this.beanFactory, type, true, false)
.values());
result.addAll(this.factoriesLoader.load(type));
AnnotationAwareOrderComparator.sort(result);
return Collections.unmodifiableList(result);
}
}

View File

@@ -0,0 +1,217 @@
/*
* 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.aot;
import java.lang.reflect.Executable;
import java.util.List;
import javax.lang.model.element.Modifier;
import org.springframework.aot.generate.ClassGenerator.JavaFileGenerator;
import org.springframework.aot.generate.GeneratedClass;
import org.springframework.aot.generate.GeneratedMethod;
import org.springframework.aot.generate.GeneratedMethods;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodGenerator;
import org.springframework.aot.generate.MethodNameGenerator;
import org.springframework.aot.generate.MethodReference;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.JavaFile;
import org.springframework.javapoet.TypeSpec;
import org.springframework.lang.Nullable;
/**
* Generates a method that returns a {@link BeanDefinition} to be registered.
*
* @author Phillip Webb
* @since 6.0
* @see BeanDefinitionMethodGeneratorFactory
*/
class BeanDefinitionMethodGenerator {
private final BeanDefinitionMethodGeneratorFactory methodGeneratorFactory;
private final RegisteredBean registeredBean;
private final Executable constructorOrFactoryMethod;
@Nullable
private final String innerBeanPropertyName;
private final List<BeanRegistrationAotContribution> aotContributions;
private final List<BeanRegistrationCodeFragmentsCustomizer> codeFragmentsCustomizers;
/**
* Create a new {@link BeanDefinitionMethodGenerator} instance.
* @param methodGeneratorFactory the method generator factory
* @param registeredBean the registered bean
* @param innerBeanPropertyName the inner bean property name
* @param aotContributions the AOT contributions
* @param codeFragmentsCustomizers the code fragments customizers
*/
BeanDefinitionMethodGenerator(
BeanDefinitionMethodGeneratorFactory methodGeneratorFactory,
RegisteredBean registeredBean, @Nullable String innerBeanPropertyName,
List<BeanRegistrationAotContribution> aotContributions,
List<BeanRegistrationCodeFragmentsCustomizer> codeFragmentsCustomizers) {
this.methodGeneratorFactory = methodGeneratorFactory;
this.registeredBean = registeredBean;
this.constructorOrFactoryMethod = ConstructorOrFactoryMethodResolver
.resolve(registeredBean);
this.innerBeanPropertyName = innerBeanPropertyName;
this.aotContributions = aotContributions;
this.codeFragmentsCustomizers = codeFragmentsCustomizers;
}
/**
* Generate the method that returns the {@link BeanDefinition} to be
* registered.
* @param generationContext the generation context
* @param beanRegistrationsCode the bean registrations code
* @return a reference to the generated method.
*/
MethodReference generateBeanDefinitionMethod(GenerationContext generationContext,
BeanRegistrationsCode beanRegistrationsCode) {
BeanRegistrationCodeFragments codeFragments = getCodeFragments(
beanRegistrationsCode);
Class<?> target = codeFragments.getTarget(this.registeredBean,
this.constructorOrFactoryMethod);
if (!target.getName().startsWith("java.")) {
GeneratedClass generatedClass = generationContext.getClassGenerator()
.getOrGenerateClass(new BeanDefinitionsJavaFileGenerator(target),
target, "BeanDefinitions");
MethodGenerator methodGenerator = generatedClass.getMethodGenerator()
.withName(getName());
GeneratedMethod generatedMethod = generateBeanDefinitionMethod(
generationContext, generatedClass.getName(), methodGenerator,
codeFragments, Modifier.PUBLIC);
return MethodReference.ofStatic(generatedClass.getName(),
generatedMethod.getName());
}
MethodGenerator methodGenerator = beanRegistrationsCode.getMethodGenerator()
.withName(getName());
GeneratedMethod generatedMethod = generateBeanDefinitionMethod(generationContext,
beanRegistrationsCode.getClassName(), methodGenerator, codeFragments,
Modifier.PRIVATE);
return MethodReference.ofStatic(beanRegistrationsCode.getClassName(),
generatedMethod.getName().toString());
}
private GeneratedMethod generateBeanDefinitionMethod(
GenerationContext generationContext, ClassName className,
MethodGenerator methodGenerator, BeanRegistrationCodeFragments codeFragments,
Modifier modifier) {
BeanRegistrationCodeGenerator codeGenerator = new BeanRegistrationCodeGenerator(
className, methodGenerator, this.registeredBean,
this.constructorOrFactoryMethod, codeFragments);
GeneratedMethod method = methodGenerator.generateMethod("get", "bean",
"definition");
this.aotContributions.forEach(aotContribution -> aotContribution
.applyTo(generationContext, codeGenerator));
return method.using(builder -> {
builder.addJavadoc("Get the $L definition for '$L'",
(!this.registeredBean.isInnerBean()) ? "bean" : "inner-bean",
getName());
builder.addModifiers(modifier, Modifier.STATIC);
builder.returns(BeanDefinition.class);
builder.addCode(codeGenerator.generateCode(generationContext));
});
}
private BeanRegistrationCodeFragments getCodeFragments(
BeanRegistrationsCode beanRegistrationsCode) {
BeanRegistrationCodeFragments codeFragments = new DefaultBeanRegistrationCodeFragments(
beanRegistrationsCode, this.registeredBean, this.methodGeneratorFactory);
for (BeanRegistrationCodeFragmentsCustomizer customizer : this.codeFragmentsCustomizers) {
codeFragments = customizer.customizeBeanRegistrationCodeFragments(
this.registeredBean, codeFragments);
}
return codeFragments;
}
private String getName() {
if (this.innerBeanPropertyName != null) {
return this.innerBeanPropertyName;
}
if (!this.registeredBean.isGeneratedBeanName()) {
return getSimpleBeanName(this.registeredBean.getBeanName());
}
RegisteredBean nonGeneratedParent = this.registeredBean;
while (nonGeneratedParent != null && nonGeneratedParent.isGeneratedBeanName()) {
nonGeneratedParent = nonGeneratedParent.getParent();
}
return (nonGeneratedParent != null)
? MethodNameGenerator.join(
getSimpleBeanName(nonGeneratedParent.getBeanName()), "innerBean")
: "innerBean";
}
private String getSimpleBeanName(String beanName) {
int lastDot = beanName.lastIndexOf('.');
beanName = (lastDot != -1) ? beanName.substring(lastDot + 1) : beanName;
int lastDollar = beanName.lastIndexOf('$');
beanName = (lastDollar != -1) ? beanName.substring(lastDollar + 1) : beanName;
return beanName;
}
/**
* {@link BeanDefinitionsJavaFileGenerator} to create the
* {@code BeanDefinitions} file.
*/
private static class BeanDefinitionsJavaFileGenerator implements JavaFileGenerator {
private final Class<?> target;
BeanDefinitionsJavaFileGenerator(Class<?> target) {
this.target = target;
}
@Override
public JavaFile generateJavaFile(ClassName className, GeneratedMethods methods) {
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className);
classBuilder.addJavadoc("Bean definitions for {@link $T}", this.target);
classBuilder.addModifiers(Modifier.PUBLIC);
methods.doWithMethodSpecs(classBuilder::addMethod);
return JavaFile.builder(className.packageName(), classBuilder.build())
.build();
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public boolean equals(Object obj) {
return getClass() == obj.getClass();
}
}
}

View File

@@ -0,0 +1,140 @@
/*
* 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.aot;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.core.log.LogMessage;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
* Factory used to create a {@link BeanDefinitionMethodGenerator} instance for a
* {@link RegisteredBean}.
*
* @author Phillip Webb
* @since 6.0
* @see BeanDefinitionMethodGenerator
* @see #getBeanDefinitionMethodGenerator(RegisteredBean, String)
*/
class BeanDefinitionMethodGeneratorFactory {
private static final Log logger = LogFactory
.getLog(BeanDefinitionMethodGeneratorFactory.class);
private final List<BeanRegistrationAotProcessor> aotProcessors;
private final List<BeanRegistrationExcludeFilter> excludeFilters;
private final List<BeanRegistrationCodeFragmentsCustomizer> codeGenerationCustomizers;
/**
* Create a new {@link BeanDefinitionMethodGeneratorFactory} backed by the
* given {@link ConfigurableListableBeanFactory}.
* @param beanFactory the bean factory use
*/
BeanDefinitionMethodGeneratorFactory(ConfigurableListableBeanFactory beanFactory) {
this(new AotFactoriesLoader(beanFactory));
}
/**
* Create a new {@link BeanDefinitionMethodGeneratorFactory} backed by the
* given {@link AotFactoriesLoader}.
* @param loader the AOT factory loader to use
*/
BeanDefinitionMethodGeneratorFactory(AotFactoriesLoader loader) {
this.aotProcessors = loader.load(BeanRegistrationAotProcessor.class);
this.excludeFilters = loader.load(BeanRegistrationExcludeFilter.class);
this.codeGenerationCustomizers = loader
.load(BeanRegistrationCodeFragmentsCustomizer.class);
}
/**
* Return a {@link BeanDefinitionMethodGenerator} for the given
* {@link RegisteredBean} or {@code null} if the registered bean is excluded
* by a {@link BeanRegistrationExcludeFilter}. The resulting
* {@link BeanDefinitionMethodGenerator} will include all
* {@link BeanRegistrationAotProcessor} provided contributions.
* @param registeredBean the registered bean
* @return a new {@link BeanDefinitionMethodGenerator} instance or
* {@code null}
*/
@Nullable
BeanDefinitionMethodGenerator getBeanDefinitionMethodGenerator(
RegisteredBean registeredBean, @Nullable String innerBeanPropertyName) {
if (isExcluded(registeredBean)) {
return null;
}
List<BeanRegistrationAotContribution> contributions = getAotContributions(
registeredBean);
return new BeanDefinitionMethodGenerator(this, registeredBean,
innerBeanPropertyName, contributions, this.codeGenerationCustomizers);
}
private boolean isExcluded(RegisteredBean registeredBean) {
if (isImplicitlyExcluded(registeredBean)) {
return true;
}
for (BeanRegistrationExcludeFilter excludeFilter : this.excludeFilters) {
if (excludeFilter.isExcluded(registeredBean)) {
logger.trace(LogMessage.format(
"Excluding registered bean '%s' from bean factory %s due to %s",
registeredBean.getBeanName(),
ObjectUtils.identityToString(registeredBean.getBeanFactory()),
excludeFilter.getClass().getName()));
return true;
}
}
return false;
}
private boolean isImplicitlyExcluded(RegisteredBean registeredBean) {
Class<?> beanClass = registeredBean.getBeanClass();
return BeanFactoryInitializationAotProcessor.class.isAssignableFrom(beanClass)
|| BeanRegistrationAotProcessor.class.isAssignableFrom(beanClass);
}
private List<BeanRegistrationAotContribution> getAotContributions(
RegisteredBean registeredBean) {
String beanName = registeredBean.getBeanName();
List<BeanRegistrationAotContribution> contributions = new ArrayList<>();
for (BeanRegistrationAotProcessor aotProcessor : this.aotProcessors) {
BeanRegistrationAotContribution contribution = aotProcessor
.processAheadOfTime(registeredBean);
if (contribution != null) {
logger.trace(LogMessage.format(
"Adding bean registration AOT contribution %S from %S to '%S'",
contribution.getClass().getName(),
aotProcessor.getClass().getName(), beanName));
contributions.add(contribution);
}
}
return contributions;
}
}

View File

@@ -0,0 +1,264 @@
/*
* 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.aot;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import org.springframework.aot.generate.MethodGenerator;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.InstanceSupplier;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.CodeBlock.Builder;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* Internal code generator to set {@link RootBeanDefinition} properties.
* <p>
* Generates code in the following form:<blockquote><pre class="code">
* beanDefinition.setPrimary(true);
* beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
* ...
* </pre></blockquote>
* <p>
* The generated code expects the following variables to be available:
* <p>
* <ul>
* <li>{@code beanDefinition} - The {@link RootBeanDefinition} to
* configure.</li>
* </ul>
* <p>
* Note that this generator does <b>not</b> set the {@link InstanceSupplier}.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 6.0
*/
class BeanDefinitionPropertiesCodeGenerator {
private static final RootBeanDefinition DEFAULT_BEAN_DEFINITION = new RootBeanDefinition();
private static final String BEAN_DEFINITION_VARIABLE = BeanRegistrationCodeFragments.BEAN_DEFINITION_VARIABLE;
private final RuntimeHints hints;
private final Predicate<String> attributeFilter;
private final BiFunction<String, Object, CodeBlock> customValueCodeGenerator;
private final BeanDefinitionPropertyValueCodeGenerator valueCodeGenerator;
BeanDefinitionPropertiesCodeGenerator(RuntimeHints hints,
Predicate<String> attributeFilter, MethodGenerator methodGenerator,
BiFunction<String, Object, CodeBlock> customValueCodeGenerator) {
this.hints = hints;
this.attributeFilter = attributeFilter;
this.customValueCodeGenerator = customValueCodeGenerator;
this.valueCodeGenerator = new BeanDefinitionPropertyValueCodeGenerator(
methodGenerator);
}
CodeBlock generateCode(BeanDefinition beanDefinition) {
CodeBlock.Builder builder = CodeBlock.builder();
addStatementForValue(builder, beanDefinition, BeanDefinition::isPrimary,
"$L.setPrimary($L)");
addStatementForValue(builder, beanDefinition, BeanDefinition::getScope,
this::hasScope, "$L.setScope($S)");
addStatementForValue(builder, beanDefinition, BeanDefinition::getDependsOn,
this::hasDependsOn, "$L.setDependsOn($L)", this::toStringVarArgs);
addStatementForValue(builder, beanDefinition, BeanDefinition::isAutowireCandidate,
"$L.setAutowireCandidate($L)");
addStatementForValue(builder, beanDefinition, BeanDefinition::getRole,
this::hasRole, "$L.setRole($L)", this::toRole);
if (beanDefinition instanceof AbstractBeanDefinition abstractBeanDefinition) {
addStatementForValue(builder, beanDefinition,
AbstractBeanDefinition::getLazyInit, "$L.setLazyInit($L)");
addStatementForValue(builder, beanDefinition,
AbstractBeanDefinition::isSynthetic, "$L.setSynthetic($L)");
addInitDestroyMethods(builder, abstractBeanDefinition,
abstractBeanDefinition.getInitMethodNames(),
"$L.setInitMethodNames($L)");
addInitDestroyMethods(builder, abstractBeanDefinition,
abstractBeanDefinition.getDestroyMethodNames(),
"$L.setDestroyMethodNames($L)");
}
addConstructorArgumentValues(builder, beanDefinition);
addPropertyValues(builder, beanDefinition);
addAttributes(builder, beanDefinition);
return builder.build();
}
private void addInitDestroyMethods(Builder builder,
AbstractBeanDefinition beanDefinition, String[] methodNames, String format) {
if (!ObjectUtils.isEmpty(methodNames)) {
Class<?> beanUserClass = ClassUtils
.getUserClass(beanDefinition.getResolvableType().toClass());
Builder arguments = CodeBlock.builder();
for (int i = 0; i < methodNames.length; i++) {
String methodName = methodNames[i];
if (!AbstractBeanDefinition.INFER_METHOD.equals(methodName)) {
arguments.add((i != 0) ? ", $S" : "$S", methodName);
addInitDestroyHint(beanUserClass, methodName);
}
}
builder.addStatement(format, BEAN_DEFINITION_VARIABLE, arguments.build());
}
}
private void addInitDestroyHint(Class<?> beanUserClass, String methodName) {
Method method = ReflectionUtils.findMethod(beanUserClass, methodName);
if (method != null) {
this.hints.reflection().registerMethod(method);
}
}
private void addConstructorArgumentValues(CodeBlock.Builder builder,
BeanDefinition beanDefinition) {
Map<Integer, ValueHolder> argumentValues = beanDefinition
.getConstructorArgumentValues().getIndexedArgumentValues();
if (!argumentValues.isEmpty()) {
argumentValues.forEach((index, valueHolder) -> {
String name = valueHolder.getName();
Object value = valueHolder.getValue();
CodeBlock code = this.customValueCodeGenerator.apply(name, value);
if (code == null) {
code = this.valueCodeGenerator.generateCode(value);
}
builder.addStatement(
"$L.getConstructorArgumentValues().addIndexedArgumentValue($L, $L)",
BEAN_DEFINITION_VARIABLE, index, code);
});
}
}
private void addPropertyValues(CodeBlock.Builder builder,
BeanDefinition beanDefinition) {
MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
if (!propertyValues.isEmpty()) {
for (PropertyValue propertyValue : propertyValues) {
String name = propertyValue.getName();
Object value = propertyValue.getValue();
CodeBlock code = this.customValueCodeGenerator.apply(name, value);
if (code == null) {
code = this.valueCodeGenerator.generateCode(value);
}
builder.addStatement("$L.getPropertyValues().addPropertyValue($S, $L)",
BEAN_DEFINITION_VARIABLE, propertyValue.getName(), code);
}
}
}
private void addAttributes(CodeBlock.Builder builder, BeanDefinition beanDefinition) {
String[] attributeNames = beanDefinition.attributeNames();
if (!ObjectUtils.isEmpty(attributeNames)) {
for (String attributeName : attributeNames) {
if (this.attributeFilter.test(attributeName)) {
CodeBlock value = this.valueCodeGenerator
.generateCode(beanDefinition.getAttribute(attributeName));
builder.addStatement("$L.setAttribute($S, $L)",
BEAN_DEFINITION_VARIABLE, attributeName, value);
}
}
}
}
private boolean hasScope(String defaultValue, String actualValue) {
return StringUtils.hasText(actualValue)
&& !ConfigurableBeanFactory.SCOPE_SINGLETON.equals(actualValue);
}
private boolean hasDependsOn(String[] defaultValue, String[] actualValue) {
return !ObjectUtils.isEmpty(actualValue);
}
private boolean hasRole(int defaultValue, int actualValue) {
return actualValue != BeanDefinition.ROLE_APPLICATION;
}
private CodeBlock toStringVarArgs(String[] strings) {
CodeBlock.Builder builder = CodeBlock.builder();
for (int i = 0; i < strings.length; i++) {
builder.add((i != 0) ? ", " : "");
builder.add("$S", strings[i]);
}
return builder.build();
}
private Object toRole(int value) {
return switch (value) {
case BeanDefinition.ROLE_INFRASTRUCTURE -> CodeBlock.builder()
.add("$T.ROLE_INFRASTRUCTURE", BeanDefinition.class).build();
case BeanDefinition.ROLE_SUPPORT -> CodeBlock.builder()
.add("$T.ROLE_SUPPORT", BeanDefinition.class).build();
default -> value;
};
}
private <B extends BeanDefinition, T> void addStatementForValue(
CodeBlock.Builder builder, BeanDefinition beanDefinition,
Function<B, T> getter, String format) {
addStatementForValue(builder, beanDefinition, getter,
(defaultValue, actualValue) -> !Objects.equals(defaultValue, actualValue),
format);
}
private <B extends BeanDefinition, T> void addStatementForValue(
CodeBlock.Builder builder, BeanDefinition beanDefinition,
Function<B, T> getter, BiPredicate<T, T> filter, String format) {
addStatementForValue(builder, beanDefinition, getter, filter, format,
actualValue -> actualValue);
}
@SuppressWarnings("unchecked")
private <B extends BeanDefinition, T> void addStatementForValue(
CodeBlock.Builder builder, BeanDefinition beanDefinition,
Function<B, T> getter, BiPredicate<T, T> filter, String format,
Function<T, Object> formatter) {
T defaultValue = getter.apply((B) DEFAULT_BEAN_DEFINITION);
T actualValue = getter.apply((B) beanDefinition);
if (filter.test(defaultValue, actualValue)) {
builder.addStatement(format, BEAN_DEFINITION_VARIABLE,
formatter.apply(actualValue));
}
}
}

View File

@@ -0,0 +1,529 @@
/*
* 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.aot;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.springframework.aot.generate.GeneratedMethod;
import org.springframework.aot.generate.MethodGenerator;
import org.springframework.aot.generate.MethodNameGenerator;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.ManagedSet;
import org.springframework.core.ResolvableType;
import org.springframework.javapoet.AnnotationSpec;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.CodeBlock.Builder;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
/**
* Internal code generator used to generate code for a single value contained in
* a {@link BeanDefinition} property.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 6.0
*/
class BeanDefinitionPropertyValueCodeGenerator {
static final CodeBlock NULL_VALUE_CODE_BLOCK = CodeBlock.of("null");
private final MethodGenerator methodGenerator;
private final List<Delegate> delegates;
BeanDefinitionPropertyValueCodeGenerator(MethodGenerator methodGenerator) {
this.methodGenerator = methodGenerator;
this.delegates = new ArrayList<>();
this.delegates.add(new PrimitiveDelegate());
this.delegates.add(new StringDelegate());
this.delegates.add(new EnumDelegate());
this.delegates.add(new ClassDelegate());
this.delegates.add(new ResolvableTypeDelegate());
this.delegates.add(new ArrayDelegate());
this.delegates.add(new ManagedListDelegate());
this.delegates.add(new ManagedSetDelegate());
this.delegates.add(new ManagedMapDelegate());
this.delegates.add(new ListDelegate());
this.delegates.add(new SetDelegate());
this.delegates.add(new MapDelegate());
this.delegates.add(new BeanReferenceDelegate());
}
CodeBlock generateCode(@Nullable Object value) {
ResolvableType type = (value != null) ? ResolvableType.forInstance(value)
: ResolvableType.NONE;
return generateCode(value, type);
}
private CodeBlock generateCode(@Nullable Object value, ResolvableType type) {
if (value == null) {
return NULL_VALUE_CODE_BLOCK;
}
for (Delegate delegate : this.delegates) {
CodeBlock code = delegate.generateCode(value, type);
if (code != null) {
return code;
}
}
throw new IllegalArgumentException(
"'type' " + type + " must be supported for instance code generation");
}
/**
* Internal delegate used to support generation for a specific type.
*/
@FunctionalInterface
private interface Delegate {
@Nullable
CodeBlock generateCode(Object value, ResolvableType type);
}
/**
* {@link Delegate} for {@code primitive} types.
*/
private class PrimitiveDelegate implements Delegate {
private static final Map<Character, String> CHAR_ESCAPES;
static {
Map<Character, String> escapes = new HashMap<>();
escapes.put('\b', "\\b");
escapes.put('\t', "\\t");
escapes.put('\n', "\\n");
escapes.put('\f', "\\f");
escapes.put('\r', "\\r");
escapes.put('\"', "\"");
escapes.put('\'', "\\'");
escapes.put('\\', "\\\\");
CHAR_ESCAPES = Collections.unmodifiableMap(escapes);
}
@Override
@Nullable
public CodeBlock generateCode(Object value, ResolvableType type) {
if (value instanceof Boolean || value instanceof Integer) {
return CodeBlock.of("$L", value);
}
if (value instanceof Byte) {
return CodeBlock.of("(byte) $L", value);
}
if (value instanceof Short) {
return CodeBlock.of("(short) $L", value);
}
if (value instanceof Long) {
return CodeBlock.of("$LL", value);
}
if (value instanceof Float) {
return CodeBlock.of("$LF", value);
}
if (value instanceof Double) {
return CodeBlock.of("(double) $L", value);
}
if (value instanceof Character character) {
return CodeBlock.of("'$L'", escape(character));
}
return null;
}
private String escape(char ch) {
String escaped = CHAR_ESCAPES.get(ch);
if (escaped != null) {
return escaped;
}
return (!Character.isISOControl(ch)) ? Character.toString(ch)
: String.format("\\u%04x", (int) ch);
}
}
/**
* {@link Delegate} for {@link String} types.
*/
private class StringDelegate implements Delegate {
@Override
@Nullable
public CodeBlock generateCode(Object value, ResolvableType type) {
if (value instanceof String) {
return CodeBlock.of("$S", value);
}
return null;
}
}
/**
* {@link Delegate} for {@link Enum} types.
*/
private class EnumDelegate implements Delegate {
@Override
@Nullable
public CodeBlock generateCode(Object value, ResolvableType type) {
if (value instanceof Enum<?> enumValue) {
return CodeBlock.of("$T.$L", enumValue.getDeclaringClass(),
enumValue.name());
}
return null;
}
}
/**
* {@link Delegate} for {@link Class} types.
*/
private class ClassDelegate implements Delegate {
@Override
@Nullable
public CodeBlock generateCode(Object value, ResolvableType type) {
if (value instanceof Class<?> clazz) {
return CodeBlock.of("$T.class", ClassUtils.getUserClass(clazz));
}
return null;
}
}
/**
* {@link Delegate} for {@link ResolvableType} types.
*/
private class ResolvableTypeDelegate implements Delegate {
@Override
@Nullable
public CodeBlock generateCode(Object value, ResolvableType type) {
if (value instanceof ResolvableType resolvableType) {
return ResolvableTypeCodeGenerator.generateCode(resolvableType);
}
return null;
}
}
/**
* {@link Delegate} for {@code array} types.
*/
private class ArrayDelegate implements Delegate {
@Override
@Nullable
public CodeBlock generateCode(@Nullable Object value, ResolvableType type) {
if (type.isArray()) {
ResolvableType componentType = type.getComponentType();
int length = Array.getLength(value);
CodeBlock.Builder builder = CodeBlock.builder();
builder.add("new $T {", type.toClass());
for (int i = 0; i < length; i++) {
Object component = Array.get(value, i);
builder.add((i != 0) ? ", " : "");
builder.add("$L", BeanDefinitionPropertyValueCodeGenerator.this
.generateCode(component, componentType));
}
builder.add("}");
return builder.build();
}
return null;
}
}
/**
* Abstract {@link Delegate} for {@code Collection} types.
*/
private abstract class CollectionDelegate<T extends Collection<?>>
implements Delegate {
private final Class<?> collectionType;
private final CodeBlock emptyResult;
public CollectionDelegate(Class<?> collectionType, CodeBlock emptyResult) {
this.collectionType = collectionType;
this.emptyResult = emptyResult;
}
@Override
@SuppressWarnings("unchecked")
@Nullable
public CodeBlock generateCode(Object value, ResolvableType type) {
if (this.collectionType.isInstance(value)) {
T collection = (T) value;
if (collection.isEmpty()) {
return this.emptyResult;
}
ResolvableType elementType = type.as(this.collectionType).getGeneric();
return generateCollectionCode(elementType, collection);
}
return null;
}
protected CodeBlock generateCollectionCode(ResolvableType elementType,
T collection) {
return generateCollectionOf(collection, this.collectionType, elementType);
}
protected final CodeBlock generateCollectionOf(Collection<?> collection,
Class<?> collectionType, ResolvableType elementType) {
Builder builder = CodeBlock.builder();
builder.add("$T.of(", collectionType);
Iterator<?> iterator = collection.iterator();
while (iterator.hasNext()) {
Object element = iterator.next();
builder.add("$L", BeanDefinitionPropertyValueCodeGenerator.this
.generateCode(element, elementType));
builder.add((!iterator.hasNext()) ? "" : ", ");
}
builder.add(")");
return builder.build();
}
}
/**
* {@link Delegate} for {@link ManagedList} types.
*/
private class ManagedListDelegate extends CollectionDelegate<ManagedList<?>> {
public ManagedListDelegate() {
super(ManagedList.class, CodeBlock.of("new $T()", ManagedList.class));
}
}
/**
* {@link Delegate} for {@link ManagedSet} types.
*/
private class ManagedSetDelegate extends CollectionDelegate<ManagedSet<?>> {
public ManagedSetDelegate() {
super(ManagedSet.class, CodeBlock.of("new $T()", ManagedSet.class));
}
}
/**
* {@link Delegate} for {@link ManagedMap} types.
*/
private class ManagedMapDelegate implements Delegate {
private static final CodeBlock EMPTY_RESULT = CodeBlock.of("$T.ofEntries()",
ManagedMap.class);
@Override
@Nullable
public CodeBlock generateCode(Object value, ResolvableType type) {
if (value instanceof ManagedMap<?, ?> managedMap) {
return generateManagedMapCode(type, managedMap);
}
return null;
}
private <K, V> CodeBlock generateManagedMapCode(ResolvableType type,
ManagedMap<K, V> managedMap) {
if (managedMap.isEmpty()) {
return EMPTY_RESULT;
}
ResolvableType keyType = type.as(Map.class).getGeneric(0);
ResolvableType valueType = type.as(Map.class).getGeneric(1);
CodeBlock.Builder builder = CodeBlock.builder();
builder.add("$T.ofEntries(", ManagedMap.class);
Iterator<Map.Entry<K, V>> iterator = managedMap.entrySet().iterator();
while (iterator.hasNext()) {
Entry<?, ?> entry = iterator.next();
builder.add("$T.entry($L,$L)", Map.class,
BeanDefinitionPropertyValueCodeGenerator.this
.generateCode(entry.getKey(), keyType),
BeanDefinitionPropertyValueCodeGenerator.this
.generateCode(entry.getValue(), valueType));
builder.add((!iterator.hasNext()) ? "" : ", ");
}
builder.add(")");
return builder.build();
}
}
/**
* {@link Delegate} for {@link List} types.
*/
private class ListDelegate extends CollectionDelegate<List<?>> {
ListDelegate() {
super(List.class, CodeBlock.of("$T.emptyList()", Collections.class));
}
}
/**
* {@link Delegate} for {@link Set} types.
*/
private class SetDelegate extends CollectionDelegate<Set<?>> {
SetDelegate() {
super(Set.class, CodeBlock.of("$T.emptySet()", Collections.class));
}
@Override
protected CodeBlock generateCollectionCode(ResolvableType elementType,
Set<?> set) {
if (set instanceof LinkedHashSet) {
return CodeBlock.of("new $T($L)", LinkedHashSet.class,
generateCollectionOf(set, List.class, elementType));
}
set = orderForCodeConsistency(set);
return super.generateCollectionCode(elementType, set);
}
private Set<?> orderForCodeConsistency(Set<?> set) {
return new TreeSet<Object>(set);
}
}
/**
* {@link Delegate} for {@link Map} types.
*/
private class MapDelegate implements Delegate {
private static final CodeBlock EMPTY_RESULT = CodeBlock.of("$T.emptyMap()",
Collections.class);
@Override
@Nullable
public CodeBlock generateCode(Object value, ResolvableType type) {
if (value instanceof Map<?, ?> map) {
return generateMapCode(type, map);
}
return null;
}
private <K, V> CodeBlock generateMapCode(ResolvableType type, Map<K, V> map) {
if (map.isEmpty()) {
return EMPTY_RESULT;
}
ResolvableType keyType = type.as(Map.class).getGeneric(0);
ResolvableType valueType = type.as(Map.class).getGeneric(1);
if (map instanceof LinkedHashMap<?, ?>) {
return generateLinkedHashMapCode(map, keyType, valueType);
}
map = orderForCodeConsistency(map);
boolean useOfEntries = map.size() > 10;
CodeBlock.Builder builder = CodeBlock.builder();
builder.add("$T" + ((!useOfEntries) ? ".of(" : ".ofEntries("), Map.class);
Iterator<Map.Entry<K, V>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Entry<K, V> entry = iterator.next();
CodeBlock keyCode = BeanDefinitionPropertyValueCodeGenerator.this
.generateCode(entry.getKey(), keyType);
CodeBlock valueCode = BeanDefinitionPropertyValueCodeGenerator.this
.generateCode(entry.getValue(), valueType);
if (!useOfEntries) {
builder.add("$L, $L", keyCode, valueCode);
}
else {
builder.add("$T.entry($L,$L)", Map.class, keyCode, valueCode);
}
builder.add((!iterator.hasNext()) ? "" : ", ");
}
builder.add(")");
return builder.build();
}
private <K, V> Map<K, V> orderForCodeConsistency(Map<K, V> map) {
return new TreeMap<>(map);
}
private <K, V> CodeBlock generateLinkedHashMapCode(Map<K, V> map,
ResolvableType keyType, ResolvableType valueType) {
GeneratedMethod method = BeanDefinitionPropertyValueCodeGenerator.this.methodGenerator
.generateMethod(MethodNameGenerator.join("get", "map"))
.using(builder -> {
builder.addAnnotation(AnnotationSpec
.builder(SuppressWarnings.class)
.addMember("value", "{\"rawtypes\", \"unchecked\"}")
.build());
builder.returns(Map.class);
builder.addStatement("$T map = new $T($L)", Map.class,
LinkedHashMap.class, map.size());
map.forEach(
(key, value) -> builder.addStatement("map.put($L, $L)",
BeanDefinitionPropertyValueCodeGenerator.this
.generateCode(key, keyType),
BeanDefinitionPropertyValueCodeGenerator.this
.generateCode(value, valueType)));
builder.addStatement("return map");
});
return CodeBlock.of("$L()", method.getName());
}
}
/**
* {@link Delegate} for {@link BeanReference} types.
*/
private class BeanReferenceDelegate implements Delegate {
@Override
@Nullable
public CodeBlock generateCode(Object value, ResolvableType type) {
if (value instanceof BeanReference beanReference) {
return CodeBlock.of("new $T($S)", RuntimeBeanReference.class,
beanReference.getBeanName());
}
return null;
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.aot;
import org.springframework.aot.generate.GenerationContext;
/**
* AOT contribution from a {@link BeanFactoryInitializationAotProcessor} used to
* initialize a bean factory.
*
* @author Phillip Webb
* @since 6.0
* @see BeanFactoryInitializationAotProcessor
*/
@FunctionalInterface
public interface BeanFactoryInitializationAotContribution {
/**
* Apply this contribution to the given
* {@link BeanFactoryInitializationCode}.
* @param generationContext the active generation context
* @param beanFactoryInitializationCode the bean factory initialization code
*/
void applyTo(GenerationContext generationContext,
BeanFactoryInitializationCode beanFactoryInitializationCode);
}

View File

@@ -0,0 +1,52 @@
/*
* 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.aot;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.lang.Nullable;
/**
* AOT processor that makes bean factory initialization contributions by
* processing {@link ConfigurableListableBeanFactory} instances.
*
* @author Phillip Webb
* @since 6.0
* @see BeanFactoryInitializationAotContribution
*/
@FunctionalInterface
public interface BeanFactoryInitializationAotProcessor {
/**
* Process the given {@link ConfigurableListableBeanFactory} instance
* ahead-of-time and return a contribution or {@code null}.
* <p>
* Processors are free to use any techniques they like to analyze the given
* instance. Most typically use reflection to find fields or methods to use
* in the contribution. Contributions typically generate source code or
* resource files that can be used when the AOT optimized application runs.
* <p>
* If the given instance isn't relevant to the processor, it should return a
* {@code null} contribution.
* @param beanFactory the bean factory to process
* @return a {@link BeanFactoryInitializationAotContribution} or
* {@code null}
*/
@Nullable
BeanFactoryInitializationAotContribution processAheadOfTime(
ConfigurableListableBeanFactory beanFactory);
}

View File

@@ -0,0 +1,52 @@
/*
* 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.aot;
import org.springframework.aot.generate.MethodGenerator;
import org.springframework.aot.generate.MethodReference;
/**
* Interface that can be used to configure the code that will be generated to
* perform bean factory initialization.
*
* @author Phillip Webb
* @since 6.0
* @see BeanFactoryInitializationAotContribution
*/
public interface BeanFactoryInitializationCode {
/**
* The recommended variable name to used referring to the bean factory.
*/
String BEAN_FACTORY_VARIABLE = "beanFactory";
/**
* Return a {@link MethodGenerator} that can be used to add more methods to
* the Initializing code.
* @return the method generator
*/
MethodGenerator getMethodGenerator();
/**
* Add an initializer method call.
* @param methodReference a reference to the initialize method to call. The
* referenced method must have the same functional signature as
* {@code Consumer<DefaultListableBeanFactory>}.
*/
void addInitializer(MethodReference methodReference);
}

View File

@@ -0,0 +1,40 @@
/*
* 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.aot;
import org.springframework.aot.generate.GenerationContext;
/**
* AOT contribution from a {@link BeanRegistrationAotProcessor} used to register
* a single bean definition.
*
* @author Phillip Webb
* @since 6.0
* @see BeanRegistrationAotProcessor
*/
@FunctionalInterface
public interface BeanRegistrationAotContribution {
/**
* Apply this contribution to the given {@link BeanRegistrationCode}.
* @param generationContext the active generation context
* @param beanRegistrationCode the generated registration
*/
void applyTo(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode);
}

View File

@@ -0,0 +1,50 @@
/*
* 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.aot;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.lang.Nullable;
/**
* AOT processor that makes bean registration contributions by processing
* {@link RegisteredBean} instances.
*
* @author Phillip Webb
* @since 6.0
* @see BeanRegistrationAotContribution
*/
@FunctionalInterface
public interface BeanRegistrationAotProcessor {
/**
* Process the given {@link RegisteredBean} instance ahead-of-time and
* return a contribution or {@code null}.
* <p>
* Processors are free to use any techniques they like to analyze the given
* instance. Most typically use reflection to find fields or methods to use
* in the contribution. Contributions typically generate source code or
* resource files that can be used when the AOT optimized application runs.
* <p>
* If the given instance isn't relevant to the processor, it should return a
* {@code null} contribution.
* @param registeredBean the registered bean to process
* @return a {@link BeanRegistrationAotContribution} or {@code null}
*/
@Nullable
BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean);
}

View File

@@ -0,0 +1,57 @@
/*
* 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.aot;
import org.springframework.aot.generate.MethodGenerator;
import org.springframework.aot.generate.MethodReference;
import org.springframework.beans.factory.support.InstanceSupplier;
import org.springframework.javapoet.ClassName;
/**
* Interface that can be used to configure the code that will be generated to
* perform registration of a single bean.
*
* @author Phillip Webb
* @since 6.0
* @see BeanRegistrationCodeFragments
* @see BeanRegistrationCodeFragmentsCustomizer
*/
public interface BeanRegistrationCode {
/**
* Return the name of the class being used for registrations.
* @return the name of the class
*/
ClassName getClassName();
/**
* Return a {@link MethodGenerator} that can be used to add more methods to
* the registrations code.
* @return the method generator
*/
MethodGenerator getMethodGenerator();
/**
* Add an instance post processor method call to the registration code.
* @param methodReference a reference to the post-process method to call.
* The referenced method must have a functional signature compatible with
* {@link InstanceSupplier#andThen}.
* @see InstanceSupplier#andThen(org.springframework.util.function.ThrowableBiFunction)
*/
void addInstancePostProcessor(MethodReference methodReference);
}

View File

@@ -0,0 +1,166 @@
/*
* 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.aot;
import java.lang.reflect.Executable;
import java.util.List;
import java.util.function.Predicate;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
import org.springframework.beans.factory.support.InstanceSupplier;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.javapoet.CodeBlock;
import org.springframework.util.Assert;
/**
* Class used to generate the various fragments of code needed to register a
* bean.
*
* @author Phillip Webb
* @since 6.0
* @see BeanRegistrationCodeFragmentsWrapper
* @see BeanRegistrationCodeFragmentsCustomizer
*/
public abstract class BeanRegistrationCodeFragments {
/**
* The variable name to used when creating the bean definition.
*/
protected static final String BEAN_DEFINITION_VARIABLE = "beanDefinition";
/**
* The variable name to used when creating the bean definition.
*/
protected static final String INSTANCE_SUPPLIER_VARIABLE = "instanceSupplier";
private final BeanRegistrationCodeFragments codeFragments;
protected BeanRegistrationCodeFragments(BeanRegistrationCodeFragments codeFragments) {
Assert.notNull(codeFragments, "'codeFragments' must not be null");
this.codeFragments = codeFragments;
}
/**
* Package-private constructor exclusively for
* {@link DefaultBeanRegistrationCodeFragments}.
*/
BeanRegistrationCodeFragments() {
this.codeFragments = null;
}
/**
* Return the target for the registration. Used to determine where to write
* the code.
* @param registeredBean the registered bean
* @param constructorOrFactoryMethod the constructor or factory method
* @return the target class
*/
public Class<?> getTarget(RegisteredBean registeredBean,
Executable constructorOrFactoryMethod) {
return this.codeFragments.getTarget(registeredBean, constructorOrFactoryMethod);
}
/**
* Generate the code that defines the new bean definition instance.
* @param generationContext the generation context
* @param beanType the bean type
* @param beanRegistrationCode the bean registration code
* @return the generated code
*/
public CodeBlock generateNewBeanDefinitionCode(GenerationContext generationContext,
ResolvableType beanType, BeanRegistrationCode beanRegistrationCode) {
return this.codeFragments.generateNewBeanDefinitionCode(generationContext,
beanType, beanRegistrationCode);
}
/**
* Generate the code that sets the properties of the bean definition.
* @param generationContext the generation context
* @param beanRegistrationCode the bean registration code
* @param attributeFilter any attribute filtering that should be applied
* @return the generated code
*/
public CodeBlock generateSetBeanDefinitionPropertiesCode(
GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
Predicate<String> attributeFilter) {
return this.codeFragments.generateSetBeanDefinitionPropertiesCode(
generationContext, beanRegistrationCode, beanDefinition, attributeFilter);
}
/**
* Generate the code that sets the instance supplier on the bean definition.
* @param generationContext the generation context
* @param beanRegistrationCode the bean registration code
* @param instanceSupplierCode the instance supplier code supplier code
* @param postProcessors any instance post processors that should be applied
* @return the generated code
* @see #generateInstanceSupplierCode
*/
public CodeBlock generateSetBeanInstanceSupplierCode(
GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, CodeBlock instanceSupplierCode,
List<MethodReference> postProcessors) {
return this.codeFragments.generateSetBeanInstanceSupplierCode(generationContext,
beanRegistrationCode, instanceSupplierCode, postProcessors);
}
/**
* Generate the instance supplier code.
* @param generationContext the generation context
* @param beanRegistrationCode the bean registration code
* @param constructorOrFactoryMethod the constructor or factory method for
* the bean
* @param allowDirectSupplierShortcut if direct suppliers may be used rather
* than always needing an {@link InstanceSupplier}
* @return the generated code
*/
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode,
Executable constructorOrFactoryMethod, boolean allowDirectSupplierShortcut) {
return this.codeFragments.generateInstanceSupplierCode(generationContext,
beanRegistrationCode, constructorOrFactoryMethod,
allowDirectSupplierShortcut);
}
/**
* Generate the return statement.
* @param generationContext the generation context
* @param beanRegistrationCode the bean registration code
* @return the generated code
*/
public CodeBlock generateReturnCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode) {
return this.codeFragments.generateReturnCode(generationContext,
beanRegistrationCode);
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.aot;
import org.springframework.beans.factory.support.RegisteredBean;
/**
* Strategy factory interface that can be used to customize the
* {@link BeanRegistrationCodeFragments} that us used for a given
* {@link RegisteredBean}. This interface can be used if default code generation
* isn't suitable for specific types of {@link RegisteredBean}.
*
* @author Phillip Webb
* @since 6.0
*/
@FunctionalInterface
public interface BeanRegistrationCodeFragmentsCustomizer {
/**
* Apply this {@link BeanRegistrationCodeFragmentsCustomizer} to the given
* {@link BeanRegistrationCodeFragments code fragments generator}. The
* returned code generator my be a
* {@link BeanRegistrationCodeFragmentsWrapper wrapper} around the original.
* @param registeredBean the registered bean
* @param codeFragments the existing code fragments
* @return the code generator to use, either the original or a wrapped one;
*/
BeanRegistrationCodeFragments customizeBeanRegistrationCodeFragments(
RegisteredBean registeredBean, BeanRegistrationCodeFragments codeFragments);
}

View File

@@ -0,0 +1,99 @@
/*
* 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.aot;
import java.lang.reflect.Executable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodGenerator;
import org.springframework.aot.generate.MethodReference;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.CodeBlock;
import org.springframework.util.Assert;
/**
* {@link BeanRegistrationCode} implementation with code generation support.
*
* @author Phillip Webb
* @since 6.0
*/
class BeanRegistrationCodeGenerator implements BeanRegistrationCode {
private static final Predicate<String> NO_ATTRIBUTE_FILTER = attribute -> true;
private final ClassName className;
private final MethodGenerator methodGenerator;
private final List<MethodReference> instancePostProcessors = new ArrayList<>();
private final RegisteredBean registeredBean;
private final Executable constructorOrFactoryMethod;
private final BeanRegistrationCodeFragments codeFragments;
BeanRegistrationCodeGenerator(ClassName className, MethodGenerator methodGenerator,
RegisteredBean registeredBean, Executable constructorOrFactoryMethod,
BeanRegistrationCodeFragments codeFragments) {
this.className = className;
this.methodGenerator = methodGenerator;
this.registeredBean = registeredBean;
this.constructorOrFactoryMethod = constructorOrFactoryMethod;
this.codeFragments = codeFragments;
}
@Override
public ClassName getClassName() {
return this.className;
}
@Override
public MethodGenerator getMethodGenerator() {
return this.methodGenerator;
}
@Override
public void addInstancePostProcessor(MethodReference methodReference) {
Assert.notNull(methodReference, "MethodReference must not be null");
this.instancePostProcessors.add(methodReference);
}
CodeBlock generateCode(GenerationContext generationContext) {
CodeBlock.Builder builder = CodeBlock.builder();
builder.add(this.codeFragments.generateNewBeanDefinitionCode(generationContext,
this.registeredBean.getBeanType(), this));
builder.add(this.codeFragments.generateSetBeanDefinitionPropertiesCode(
generationContext, this, this.registeredBean.getMergedBeanDefinition(),
NO_ATTRIBUTE_FILTER));
CodeBlock instanceSupplierCode = this.codeFragments.generateInstanceSupplierCode(
generationContext, this, this.constructorOrFactoryMethod,
this.instancePostProcessors.isEmpty());
builder.add(
this.codeFragments.generateSetBeanInstanceSupplierCode(generationContext,
this, instanceSupplierCode, this.instancePostProcessors));
builder.add(this.codeFragments.generateReturnCode(generationContext, this));
return builder.build();
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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.aot;
import org.springframework.beans.factory.support.RegisteredBean;
/**
* Filter that can be used to exclude AOT processing of a
* {@link RegisteredBean}.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 6.0
*/
@FunctionalInterface
public interface BeanRegistrationExcludeFilter {
/**
* Return if the registered bean should be excluded from AOT processing and
* registration.
* @param registeredBean the registered bean
* @return if the registered bean should be excluded
*/
boolean isExcluded(RegisteredBean registeredBean);
}

View File

@@ -0,0 +1,135 @@
/*
* 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.aot;
import java.util.Map;
import javax.lang.model.element.Modifier;
import org.springframework.aot.generate.GeneratedMethod;
import org.springframework.aot.generate.GeneratedMethods;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodGenerator;
import org.springframework.aot.generate.MethodReference;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.JavaFile;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.TypeSpec;
/**
* AOT contribution from a {@link BeanRegistrationsAotProcessor} used to
* register bean definitions.
*
* @author Phillip Webb
* @since 6.0
* @see BeanRegistrationsAotProcessor
*/
class BeanRegistrationsAotContribution
implements BeanFactoryInitializationAotContribution {
private static final String BEAN_FACTORY_PARAMETER_NAME = "beanFactory";
private final Map<String, BeanDefinitionMethodGenerator> registrations;
BeanRegistrationsAotContribution(
Map<String, BeanDefinitionMethodGenerator> registrations) {
this.registrations = registrations;
}
@Override
public void applyTo(GenerationContext generationContext,
BeanFactoryInitializationCode beanFactoryInitializationCode) {
ClassName className = generationContext.getClassNameGenerator()
.generateClassName("BeanFactory", "Registrations");
BeanRegistrationsCodeGenerator codeGenerator = new BeanRegistrationsCodeGenerator(
className);
GeneratedMethod registerMethod = codeGenerator.getMethodGenerator()
.generateMethod("registerBeanDefinitions")
.using(builder -> generateRegisterMethod(builder, generationContext,
codeGenerator));
JavaFile javaFile = codeGenerator.generatedJavaFile(className);
generationContext.getGeneratedFiles().addSourceFile(javaFile);
beanFactoryInitializationCode
.addInitializer(MethodReference.of(className, registerMethod.getName()));
}
private void generateRegisterMethod(MethodSpec.Builder builder,
GenerationContext generationContext,
BeanRegistrationsCode beanRegistrationsCode) {
builder.addJavadoc("Register the bean definitions.");
builder.addModifiers(Modifier.PUBLIC);
builder.addParameter(DefaultListableBeanFactory.class,
BEAN_FACTORY_PARAMETER_NAME);
CodeBlock.Builder code = CodeBlock.builder();
this.registrations.forEach((beanName, beanDefinitionMethodGenerator) -> {
MethodReference beanDefinitionMethod = beanDefinitionMethodGenerator
.generateBeanDefinitionMethod(generationContext,
beanRegistrationsCode);
code.addStatement("$L.registerBeanDefinition($S, $L)",
BEAN_FACTORY_PARAMETER_NAME, beanName,
beanDefinitionMethod.toInvokeCodeBlock());
});
builder.addCode(code.build());
}
/**
* {@link BeanRegistrationsCode} with generation support.
*/
static class BeanRegistrationsCodeGenerator implements BeanRegistrationsCode {
private final ClassName className;
private final GeneratedMethods generatedMethods = new GeneratedMethods();
public BeanRegistrationsCodeGenerator(ClassName className) {
this.className = className;
}
@Override
public ClassName getClassName() {
return this.className;
}
@Override
public MethodGenerator getMethodGenerator() {
return this.generatedMethods;
}
JavaFile generatedJavaFile(ClassName className) {
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className);
classBuilder.addJavadoc("Register bean definitions for the bean factory.");
classBuilder.addModifiers(Modifier.PUBLIC);
this.generatedMethods.doWithMethodSpecs(classBuilder::addMethod);
return JavaFile.builder(className.packageName(), classBuilder.build())
.build();
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.aot;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
/**
* {@link BeanFactoryInitializationAotProcessor} that contributes code to
* register beans.
*
* @author Phillip Webb
* @since 6.0
*/
class BeanRegistrationsAotProcessor implements BeanFactoryInitializationAotProcessor {
@Override
public BeanRegistrationsAotContribution processAheadOfTime(
ConfigurableListableBeanFactory beanFactory) {
BeanDefinitionMethodGeneratorFactory beanDefinitionMethodGeneratorFactory =
new BeanDefinitionMethodGeneratorFactory(beanFactory);
Map<String, BeanDefinitionMethodGenerator> registrations = new LinkedHashMap<>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
RegisteredBean registeredBean = RegisteredBean.of(beanFactory, beanName);
BeanDefinitionMethodGenerator beanDefinitionMethodGenerator = beanDefinitionMethodGeneratorFactory
.getBeanDefinitionMethodGenerator(registeredBean, null);
if (beanDefinitionMethodGenerator != null) {
registrations.put(beanName, beanDefinitionMethodGenerator);
}
}
if (registrations.isEmpty()) {
return null;
}
return new BeanRegistrationsAotContribution(registrations);
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.aot;
import org.springframework.aot.generate.MethodGenerator;
import org.springframework.javapoet.ClassName;
/**
* Interface that can be used to configure the code that will be generated to
* register beans.
*
* @author Phillip Webb
* @since 6.0
*/
public interface BeanRegistrationsCode {
/**
* Return the name of the class being used for registrations.
* @return the generated class name.
*/
ClassName getClassName();
/**
* Return a {@link MethodGenerator} that can be used to add more methods to
* the registrations code.
* @return the method generator
*/
MethodGenerator getMethodGenerator();
}

View File

@@ -0,0 +1,450 @@
/*
* 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.aot;
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.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.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* Resolves the {@link Executable} (factory method or constructor) that should
* be used to create a bean. This class is similar to
* {@code org.springframework.beans.factory.support.ConstructorResolver} but it
* doesn't need bean initialization.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 6.0
*/
class ConstructorOrFactoryMethodResolver {
private static final Log logger = LogFactory
.getLog(ConstructorOrFactoryMethodResolver.class);
private final ConfigurableBeanFactory beanFactory;
private final ClassLoader classLoader;
ConstructorOrFactoryMethodResolver(ConfigurableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
this.classLoader = (beanFactory.getBeanClassLoader() != null)
? beanFactory.getBeanClassLoader() : ClassUtils.getDefaultClassLoader();
}
Executable resolve(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);
Assert.state(isCompatible,
() -> String.format(
"Incompatible target type '%s' for factory bean '%s'",
resolvableType.toClass().getName(),
factoryBeanClass.getName()));
return resolveConstructor(() -> ResolvableType.forClass(factoryBeanClass),
valueTypes);
}
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()) {
parameterTypes.add(determineParameterValueType(valueHolder));
}
return parameterTypes;
}
private ResolvableType determineParameterValueType(ValueHolder valueHolder) {
if (valueHolder.getType() != null) {
return ResolvableType.forClass(loadClass(valueHolder.getType()));
}
Object value = valueHolder.getValue();
if (value instanceof BeanReference) {
return ResolvableType.forClass(this.beanFactory
.getType(((BeanReference) value).getBeanName(), false));
}
if (value instanceof BeanDefinition) {
return extractTypeFromBeanDefinition(getBeanType((BeanDefinition) value));
}
if (value instanceof Class<?>) {
return ResolvableType.forClassWithGenerics(Class.class, (Class<?>) value);
}
return ResolvableType.forInstance(value);
}
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) {
String factoryBeanName = beanDefinition.getFactoryBeanName();
Class<?> beanClass = getBeanClass((factoryBeanName != null)
? this.beanFactory.getMergedBeanDefinition(factoryBeanName)
: beanDefinition);
List<Method> methods = new ArrayList<>();
Assert.state(beanClass != null,
() -> "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,
ConstructorOrFactoryMethodResolver.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, ConstructorOrFactoryMethodResolver.FallbackMode.NONE))
.toList();
if (matches.size() == 1) {
return matches.get(0);
}
List<? extends Executable> assignableElementFallbackMatches = executables.stream()
.filter(executable -> match(parameterTypesFactory.apply(executable),
valueTypes,
ConstructorOrFactoryMethodResolver.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,
ConstructorOrFactoryMethodResolver.FallbackMode.TYPE_CONVERSION))
.toList();
Assert.state(typeConversionFallbackMatches.size() <= 1,
() -> "Multiple matches with parameters '" + valueTypes + "': "
+ typeConversionFallbackMatches);
return (typeConversionFallbackMatches.size() == 1)
? typeConversionFallbackMatches.get(0) : null;
}
private boolean match(List<ResolvableType> parameterTypes,
List<ResolvableType> valueTypes,
ConstructorOrFactoryMethodResolver.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,
ConstructorOrFactoryMethodResolver.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 -> 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));
}
private 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;
}
static Executable resolve(RegisteredBean registeredBean) {
return new ConstructorOrFactoryMethodResolver(registeredBean.getBeanFactory())
.resolve(registeredBean.getMergedBeanDefinition());
}
enum FallbackMode {
NONE,
ASSIGNABLE_ELEMENT,
TYPE_CONVERSION
}
}

View File

@@ -0,0 +1,185 @@
/*
* 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.aot;
import java.lang.reflect.Executable;
import java.util.List;
import java.util.function.Predicate;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.InstanceSupplier;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.ParameterizedTypeName;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Internal {@link BeanRegistrationCodeFragments} implementation used by
* default.
*
* @author Phillip Webb
*/
class DefaultBeanRegistrationCodeFragments extends BeanRegistrationCodeFragments {
/**
* The variable name used to hold the bean type.
*/
private static final String BEAN_TYPE_VARIABLE = "beanType";
private final BeanRegistrationsCode beanRegistrationsCode;
private final RegisteredBean registeredBean;
private final BeanDefinitionMethodGeneratorFactory beanDefinitionMethodGeneratorFactory;
DefaultBeanRegistrationCodeFragments(BeanRegistrationsCode beanRegistrationsCode,
RegisteredBean registeredBean,
BeanDefinitionMethodGeneratorFactory beanDefinitionMethodGeneratorFactory) {
this.beanRegistrationsCode = beanRegistrationsCode;
this.registeredBean = registeredBean;
this.beanDefinitionMethodGeneratorFactory = beanDefinitionMethodGeneratorFactory;
}
@Override
public Class<?> getTarget(RegisteredBean registeredBean,
Executable constructorOrFactoryMethod) {
Class<?> target = ClassUtils
.getUserClass(constructorOrFactoryMethod.getDeclaringClass());
while (target.getName().startsWith("java.") && registeredBean.isInnerBean()) {
target = registeredBean.getParent().getBeanClass();
}
return target;
}
@Override
public CodeBlock generateNewBeanDefinitionCode(GenerationContext generationContext,
ResolvableType beanType, BeanRegistrationCode beanRegistrationCode) {
CodeBlock.Builder builder = CodeBlock.builder();
builder.addStatement(generateBeanTypeCode(beanType));
builder.addStatement("$T $L = new $T($L)", RootBeanDefinition.class,
BEAN_DEFINITION_VARIABLE, RootBeanDefinition.class, BEAN_TYPE_VARIABLE);
return builder.build();
}
private CodeBlock generateBeanTypeCode(ResolvableType beanType) {
if (!beanType.hasGenerics()) {
return CodeBlock.of("$T<?> $L = $T.class", Class.class, BEAN_TYPE_VARIABLE,
ClassUtils.getUserClass(beanType.toClass()));
}
return CodeBlock.of("$T $L = $L", ResolvableType.class, BEAN_TYPE_VARIABLE,
ResolvableTypeCodeGenerator.generateCode(beanType));
}
@Override
public CodeBlock generateSetBeanDefinitionPropertiesCode(
GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
Predicate<String> attributeFilter) {
return new BeanDefinitionPropertiesCodeGenerator(
generationContext.getRuntimeHints(), attributeFilter,
beanRegistrationCode.getMethodGenerator(),
(name, value) -> generateValueCode(generationContext, name, value))
.generateCode(beanDefinition);
}
@Nullable
protected CodeBlock generateValueCode(GenerationContext generationContext,
String name, Object value) {
RegisteredBean innerRegisteredBean = getInnerRegisteredBean(value);
if (innerRegisteredBean != null) {
BeanDefinitionMethodGenerator methodGenerator = this.beanDefinitionMethodGeneratorFactory
.getBeanDefinitionMethodGenerator(innerRegisteredBean, name);
Assert.state(methodGenerator != null, "Unexpected filtering of inner-bean");
MethodReference generatedMethod = methodGenerator
.generateBeanDefinitionMethod(generationContext,
this.beanRegistrationsCode);
return generatedMethod.toInvokeCodeBlock();
}
return null;
}
@Nullable
private RegisteredBean getInnerRegisteredBean(Object value) {
if (value instanceof BeanDefinitionHolder beanDefinitionHolder) {
return RegisteredBean.ofInnerBean(this.registeredBean, beanDefinitionHolder);
}
if (value instanceof BeanDefinition beanDefinition) {
return RegisteredBean.ofInnerBean(this.registeredBean, beanDefinition);
}
return null;
}
public CodeBlock generateSetBeanInstanceSupplierCode(
GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, CodeBlock instanceSupplierCode,
List<MethodReference> postProcessors) {
CodeBlock.Builder builder = CodeBlock.builder();
if (postProcessors.isEmpty()) {
builder.addStatement("$L.setInstanceSupplier($L)", BEAN_DEFINITION_VARIABLE,
instanceSupplierCode);
return builder.build();
}
builder.addStatement("$T $L = $L",
ParameterizedTypeName.get(InstanceSupplier.class,
this.registeredBean.getBeanClass()),
INSTANCE_SUPPLIER_VARIABLE, instanceSupplierCode);
for (MethodReference postProcessor : postProcessors) {
builder.addStatement("$L = $L.andThen($L)", INSTANCE_SUPPLIER_VARIABLE,
INSTANCE_SUPPLIER_VARIABLE, postProcessor.toCodeBlock());
}
builder.addStatement("$L.setInstanceSupplier($L)", BEAN_DEFINITION_VARIABLE,
INSTANCE_SUPPLIER_VARIABLE);
return builder.build();
}
@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode,
Executable constructorOrFactoryMethod, boolean allowDirectSupplierShortcut) {
return new InstanceSupplierCodeGenerator(generationContext,
beanRegistrationCode.getClassName(),
beanRegistrationCode.getMethodGenerator(), allowDirectSupplierShortcut)
.generateCode(this.registeredBean, constructorOrFactoryMethod);
}
@Override
public CodeBlock generateReturnCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode) {
CodeBlock.Builder builder = CodeBlock.builder();
builder.addStatement("return $L", BEAN_DEFINITION_VARIABLE);
return builder.build();
}
}

View File

@@ -0,0 +1,364 @@
/*
* 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.aot;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.function.Consumer;
import org.springframework.aot.generate.AccessVisibility;
import org.springframework.aot.generate.GeneratedMethod;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodGenerator;
import org.springframework.aot.hint.ExecutableHint;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.beans.factory.annotation.AutowiredArgumentsCodeGenerator;
import org.springframework.beans.factory.annotation.AutowiredInstantiationArgumentsResolver;
import org.springframework.beans.factory.support.InstanceSupplier;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.core.ResolvableType;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.MethodSpec;
import org.springframework.util.ClassUtils;
import org.springframework.util.function.ThrowingSupplier;
/**
* Internal code generator to create an {@link InstanceSupplier}.
* <p>
* Generates code in the form:<pre class="code">{@code
* InstanceSupplier.of(TheGeneratedClass::getMyBeanInstance);
* }</pre>
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 6.0
*/
class InstanceSupplierCodeGenerator {
private static final String REGISTERED_BEAN_PARAMETER_NAME = "registeredBean";
private static final javax.lang.model.element.Modifier[] PRIVATE_STATIC = {
javax.lang.model.element.Modifier.PRIVATE,
javax.lang.model.element.Modifier.STATIC };
private static final CodeBlock NO_ARGS = CodeBlock.of("");
private static final Consumer<ExecutableHint.Builder> INTROSPECT = builder -> builder
.withMode(ExecutableMode.INTROSPECT);
private final GenerationContext generationContext;
private final ClassName className;
private final MethodGenerator methodGenerator;
private final boolean allowDirectSupplierShortcut;
InstanceSupplierCodeGenerator(GenerationContext generationContext,
ClassName className, MethodGenerator methodGenerator,
boolean allowDirectSupplierShortcut) {
this.generationContext = generationContext;
this.className = className;
this.methodGenerator = methodGenerator;
this.allowDirectSupplierShortcut = allowDirectSupplierShortcut;
}
CodeBlock generateCode(RegisteredBean registeredBean,
Executable constructorOrFactoryMethod) {
if (constructorOrFactoryMethod instanceof Constructor<?> constructor) {
return generateCodeForConstructor(registeredBean, constructor);
}
if (constructorOrFactoryMethod instanceof Method method) {
return generateCodeForFactoryMethod(registeredBean, method);
}
throw new IllegalStateException(
"No suitable executor found for " + registeredBean.getBeanName());
}
private CodeBlock generateCodeForConstructor(RegisteredBean registeredBean,
Constructor<?> constructor) {
String name = registeredBean.getBeanName();
Class<?> declaringClass = ClassUtils
.getUserClass(constructor.getDeclaringClass());
boolean dependsOnBean = ClassUtils.isInnerClass(declaringClass);
AccessVisibility accessVisibility = getAccessVisibility(registeredBean,
constructor);
if (accessVisibility == AccessVisibility.PUBLIC
|| accessVisibility == AccessVisibility.PACKAGE_PRIVATE) {
return generateCodeForAccessibleConstructor(name, constructor, declaringClass,
dependsOnBean);
}
return generateCodeForInaccessibleConstructor(name, constructor, declaringClass,
dependsOnBean);
}
private CodeBlock generateCodeForAccessibleConstructor(String name,
Constructor<?> constructor, Class<?> declaringClass, boolean dependsOnBean) {
this.generationContext.getRuntimeHints().reflection()
.registerConstructor(constructor, INTROSPECT);
if (!dependsOnBean && constructor.getParameterCount() == 0) {
if (!this.allowDirectSupplierShortcut) {
return CodeBlock.of("$T.using($T::new)", InstanceSupplier.class,
declaringClass);
}
if (!isThrowingCheckedException(constructor)) {
return CodeBlock.of("$T::new", declaringClass);
}
return CodeBlock.of("$T.of($T::new)", ThrowingSupplier.class,
declaringClass);
}
GeneratedMethod getInstanceMethod = generateGetInstanceMethod()
.using(builder -> buildGetInstanceMethodForConstructor(builder, name,
constructor, declaringClass, dependsOnBean, PRIVATE_STATIC));
return CodeBlock.of("$T.of($T::$L)", InstanceSupplier.class, this.className,
getInstanceMethod.getName());
}
private CodeBlock generateCodeForInaccessibleConstructor(String name,
Constructor<?> constructor, Class<?> declaringClass, boolean dependsOnBean) {
this.generationContext.getRuntimeHints().reflection()
.registerConstructor(constructor);
GeneratedMethod getInstanceMethod = generateGetInstanceMethod().using(builder -> {
builder.addJavadoc("Instantiate the bean instance for '$L'.", name);
builder.addModifiers(PRIVATE_STATIC);
builder.returns(declaringClass);
builder.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER_NAME);
int parameterOffset = (!dependsOnBean) ? 0 : 1;
builder.addStatement(
generateResolverForConstructor(constructor, parameterOffset));
builder.addStatement("return resolver.resolveAndInstantiate($L)",
REGISTERED_BEAN_PARAMETER_NAME);
});
return CodeBlock.of("$T.of($T::$L)", InstanceSupplier.class, this.className,
getInstanceMethod.getName());
}
private void buildGetInstanceMethodForConstructor(MethodSpec.Builder builder,
String name, Constructor<?> constructor, Class<?> declaringClass,
boolean dependsOnBean, javax.lang.model.element.Modifier... modifiers) {
builder.addJavadoc("Create the bean instance for '$L'.", name);
builder.addModifiers(modifiers);
builder.returns(declaringClass);
builder.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER_NAME);
if (constructor.getParameterCount() == 0) {
CodeBlock instantiationCode = generateNewInstanceCodeForConstructor(
dependsOnBean, declaringClass, NO_ARGS);
builder.addCode(generateReturnStatement(instantiationCode));
}
else {
int parameterOffset = (!dependsOnBean) ? 0 : 1;
CodeBlock.Builder code = CodeBlock.builder();
code.addStatement(
generateResolverForConstructor(constructor, parameterOffset));
CodeBlock arguments = new AutowiredArgumentsCodeGenerator(declaringClass,
constructor).generateCode(constructor.getParameterTypes(),
parameterOffset);
CodeBlock newInstance = generateNewInstanceCodeForConstructor(dependsOnBean,
declaringClass, arguments);
code.addStatement("return resolver.resolve($L, (args) -> $L)",
REGISTERED_BEAN_PARAMETER_NAME, newInstance);
builder.addCode(code.build());
}
}
private CodeBlock generateResolverForConstructor(Constructor<?> constructor,
int parameterOffset) {
CodeBlock parameterTypes = generateParameterTypesCode(
constructor.getParameterTypes(), parameterOffset);
return CodeBlock.of("$T resolver = $T.forConstructor($L)",
AutowiredInstantiationArgumentsResolver.class,
AutowiredInstantiationArgumentsResolver.class, parameterTypes);
}
private CodeBlock generateNewInstanceCodeForConstructor(boolean dependsOnBean,
Class<?> declaringClass, CodeBlock args) {
if (!dependsOnBean) {
return CodeBlock.of("new $T($L)", declaringClass, args);
}
return CodeBlock.of("$L.getBeanFactory().getBean($T.class).new $L($L)",
REGISTERED_BEAN_PARAMETER_NAME, declaringClass.getEnclosingClass(),
declaringClass.getSimpleName(), args);
}
private CodeBlock generateCodeForFactoryMethod(RegisteredBean registeredBean,
Method factoryMethod) {
String name = registeredBean.getBeanName();
Class<?> declaringClass = ClassUtils
.getUserClass(factoryMethod.getDeclaringClass());
boolean dependsOnBean = !Modifier.isStatic(factoryMethod.getModifiers());
AccessVisibility accessVisibility = getAccessVisibility(registeredBean,
factoryMethod);
if (accessVisibility == AccessVisibility.PUBLIC
|| accessVisibility == AccessVisibility.PACKAGE_PRIVATE) {
return generateCodeForAccessibleFactoryMethod(name, factoryMethod,
declaringClass, dependsOnBean);
}
return generateCodeForInaccessibleFactoryMethod(name, factoryMethod,
declaringClass);
}
private CodeBlock generateCodeForAccessibleFactoryMethod(String name,
Method factoryMethod, Class<?> declaringClass, boolean dependsOnBean) {
this.generationContext.getRuntimeHints().reflection()
.registerMethod(factoryMethod, INTROSPECT);
if (!dependsOnBean && factoryMethod.getParameterCount() == 0) {
if (!this.allowDirectSupplierShortcut) {
return CodeBlock.of("$T.using($T::$L)", InstanceSupplier.class,
declaringClass, factoryMethod.getName());
}
if (!isThrowingCheckedException(factoryMethod)) {
return CodeBlock.of("$T::$L", declaringClass, factoryMethod.getName());
}
return CodeBlock.of("$T.of($T::$L)", ThrowingSupplier.class, declaringClass,
factoryMethod.getName());
}
GeneratedMethod getInstanceMethod = generateGetInstanceMethod()
.using(builder -> buildGetInstanceMethodForFactoryMethod(builder, name,
factoryMethod, declaringClass, dependsOnBean, PRIVATE_STATIC));
return CodeBlock.of("$T.of($T::$L)", InstanceSupplier.class, this.className,
getInstanceMethod.getName());
}
private CodeBlock generateCodeForInaccessibleFactoryMethod(String name,
Method factoryMethod, Class<?> declaringClass) {
this.generationContext.getRuntimeHints().reflection()
.registerMethod(factoryMethod);
GeneratedMethod getInstanceMethod = generateGetInstanceMethod().using(builder -> {
builder.addJavadoc("Instantiate the bean instance for '$L'.", name);
builder.addModifiers(PRIVATE_STATIC);
builder.returns(factoryMethod.getReturnType());
builder.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER_NAME);
builder.addStatement(generateResolverForFactoryMethod(factoryMethod,
declaringClass, factoryMethod.getName()));
builder.addStatement("return resolver.resolveAndInstantiate($L)",
REGISTERED_BEAN_PARAMETER_NAME);
});
return CodeBlock.of("$T.of($T::$L)", InstanceSupplier.class, this.className,
getInstanceMethod.getName());
}
private void buildGetInstanceMethodForFactoryMethod(MethodSpec.Builder builder,
String name, Method factoryMethod, Class<?> declaringClass,
boolean dependsOnBean, javax.lang.model.element.Modifier... modifiers) {
String factoryMethodName = factoryMethod.getName();
builder.addJavadoc("Get the bean instance for '$L'.", name);
builder.addModifiers(modifiers);
builder.returns(factoryMethod.getReturnType());
if (isThrowingCheckedException(factoryMethod)) {
builder.addException(Exception.class);
}
builder.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER_NAME);
if (factoryMethod.getParameterCount() == 0) {
CodeBlock instantiationCode = generateNewInstanceCodeForMethod(dependsOnBean,
declaringClass, factoryMethodName, NO_ARGS);
builder.addCode(generateReturnStatement(instantiationCode));
}
else {
CodeBlock.Builder code = CodeBlock.builder();
code.addStatement(generateResolverForFactoryMethod(factoryMethod,
declaringClass, factoryMethodName));
CodeBlock arguments = new AutowiredArgumentsCodeGenerator(declaringClass,
factoryMethod).generateCode(factoryMethod.getParameterTypes());
CodeBlock newInstance = generateNewInstanceCodeForMethod(dependsOnBean,
declaringClass, factoryMethodName, arguments);
code.addStatement("return resolver.resolve($L, (args) -> $L)",
REGISTERED_BEAN_PARAMETER_NAME, newInstance);
builder.addCode(code.build());
}
}
private CodeBlock generateResolverForFactoryMethod(Method factoryMethod,
Class<?> declaringClass, String factoryMethodName) {
if (factoryMethod.getParameterCount() == 0) {
return CodeBlock.of("$T resolver = $T.forFactoryMethod($T.class, $S)",
AutowiredInstantiationArgumentsResolver.class,
AutowiredInstantiationArgumentsResolver.class, declaringClass,
factoryMethodName);
}
CodeBlock parameterTypes = generateParameterTypesCode(
factoryMethod.getParameterTypes(), 0);
return CodeBlock.of("$T resolver = $T.forFactoryMethod($T.class, $S, $L)",
AutowiredInstantiationArgumentsResolver.class,
AutowiredInstantiationArgumentsResolver.class, declaringClass,
factoryMethodName, parameterTypes);
}
private CodeBlock generateNewInstanceCodeForMethod(boolean dependsOnBean,
Class<?> declaringClass, String factoryMethodName, CodeBlock args) {
if (!dependsOnBean) {
return CodeBlock.of("$T.$L($L)", declaringClass, factoryMethodName, args);
}
return CodeBlock.of("$L.getBeanFactory().getBean($T.class).$L($L)",
REGISTERED_BEAN_PARAMETER_NAME, declaringClass, factoryMethodName, args);
}
private CodeBlock generateReturnStatement(CodeBlock instantiationCode) {
CodeBlock.Builder code = CodeBlock.builder();
code.addStatement("return $L", instantiationCode);
return code.build();
}
protected AccessVisibility getAccessVisibility(RegisteredBean registeredBean,
Member member) {
AccessVisibility beanTypeAccessVisibility = AccessVisibility
.forResolvableType(registeredBean.getBeanType());
AccessVisibility memberAccessVisibility = AccessVisibility.forMember(member);
return AccessVisibility.lowest(beanTypeAccessVisibility, memberAccessVisibility);
}
private CodeBlock generateParameterTypesCode(Class<?>[] parameterTypes, int offset) {
CodeBlock.Builder builder = CodeBlock.builder();
for (int i = offset; i < parameterTypes.length; i++) {
builder.add(i != offset ? ", " : "");
builder.add("$T.class", parameterTypes[i]);
}
return builder.build();
}
private GeneratedMethod generateGetInstanceMethod() {
return this.methodGenerator.generateMethod("get", "instance");
}
private boolean isThrowingCheckedException(Executable executable) {
return Arrays.stream(executable.getGenericExceptionTypes())
.map(ResolvableType::forType).map(ResolvableType::toClass)
.anyMatch(Exception.class::isAssignableFrom);
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.aot;
import java.util.Arrays;
import org.springframework.core.ResolvableType;
import org.springframework.javapoet.CodeBlock;
import org.springframework.util.ClassUtils;
/**
* Internal code generator used to support {@link ResolvableType}.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 6.0
*/
final class ResolvableTypeCodeGenerator {
private ResolvableTypeCodeGenerator() {
}
public static CodeBlock generateCode(ResolvableType resolvableType) {
return generateCode(resolvableType, false);
}
private static CodeBlock generateCode(ResolvableType resolvableType, boolean allowClassResult) {
if (ResolvableType.NONE.equals(resolvableType)) {
return CodeBlock.of("$T.NONE", ResolvableType.class);
}
Class<?> type = ClassUtils.getUserClass(resolvableType.toClass());
if (resolvableType.hasGenerics()) {
return generateCodeWithGenerics(resolvableType, type);
}
if (allowClassResult) {
return CodeBlock.of("$T.class", type);
}
return CodeBlock.of("$T.forClass($T.class)", ResolvableType.class, type);
}
private static CodeBlock generateCodeWithGenerics(ResolvableType target, Class<?> type) {
ResolvableType[] generics = target.getGenerics();
boolean hasNoNestedGenerics = Arrays.stream(generics).noneMatch(ResolvableType::hasGenerics);
CodeBlock.Builder builder = CodeBlock.builder();
builder.add("$T.forClassWithGenerics($T.class", ResolvableType.class, type);
for (ResolvableType generic : generics) {
builder.add(", $L", generateCode(generic, hasNoNestedGenerics));
}
builder.add(")");
return builder.build();
}
}

View File

@@ -0,0 +1,9 @@
/**
* AOT support for bean factories.
*/
@NonNullApi
@NonNullFields
package org.springframework.beans.factory.aot;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;

View File

@@ -0,0 +1,2 @@
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
org.springframework.beans.factory.aot.BeanRegistrationsAotProcessor