Complete refactor of AOT concepts

Remove the AOT code that now has an alternative API.

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

View File

@@ -43,7 +43,6 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.aot.generate.AccessVisibility;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
import org.springframework.aot.generator.CodeContribution;
import org.springframework.aot.hint.ExecutableHint;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.FieldHint;
@@ -59,16 +58,12 @@ import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.generator.AotContributingBeanPostProcessor;
import org.springframework.beans.factory.generator.BeanInstantiationContribution;
import org.springframework.beans.factory.generator.InjectionGenerator;
import org.springframework.beans.factory.support.LookupOverride;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RegisteredBean;
@@ -157,8 +152,7 @@ import org.springframework.util.StringUtils;
* @see Value
*/
public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
MergedBeanDefinitionPostProcessor, AotContributingBeanPostProcessor, BeanRegistrationAotProcessor,
PriorityOrdered, BeanFactoryAware {
MergedBeanDefinitionPostProcessor, BeanRegistrationAotProcessor, PriorityOrdered, BeanFactoryAware {
protected final Log logger = LogFactory.getLog(getClass());
@@ -285,15 +279,6 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
findInjectionMetadata(beanName, beanType, beanDefinition);
}
@Override
public BeanInstantiationContribution contribute(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
InjectionMetadata metadata = findInjectionMetadata(beanName, beanType, beanDefinition);
Collection<InjectedElement> injectedElements = metadata.getInjectedElements();
return (!ObjectUtils.isEmpty(injectedElements)
? new AutowiredAnnotationBeanInstantiationContribution(injectedElements)
: null);
}
@Override
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
Class<?> beanClass = registeredBean.getBeanClass();
@@ -866,52 +851,6 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
}
private static final class AutowiredAnnotationBeanInstantiationContribution implements BeanInstantiationContribution {
private final Collection<InjectedElement> injectedElements;
private final InjectionGenerator generator;
AutowiredAnnotationBeanInstantiationContribution(Collection<InjectedElement> injectedElements) {
this.injectedElements = injectedElements;
this.generator = new InjectionGenerator();
}
@Override
public void applyTo(CodeContribution contribution) {
this.injectedElements.forEach(element -> {
boolean isRequired = isRequired(element);
Member member = element.getMember();
analyzeMember(contribution, member);
contribution.statements().addStatement(this.generator.generateInjection(member, isRequired));
});
}
private boolean isRequired(InjectedElement element) {
if (element instanceof AutowiredMethodElement injectedMethod) {
return injectedMethod.required;
}
else if (element instanceof AutowiredFieldElement injectedField) {
return injectedField.required;
}
return true;
}
private void analyzeMember(CodeContribution contribution, Member member) {
if (member instanceof Method method) {
contribution.runtimeHints().reflection().registerMethod(method,
hint -> hint.setModes(ExecutableMode.INTROSPECT));
contribution.protectedAccess().analyze(member,
this.generator.getProtectedAccessInjectionOptions(member));
}
else if (member instanceof Field field) {
contribution.runtimeHints().reflection().registerField(field);
contribution.protectedAccess().analyze(member,
this.generator.getProtectedAccessInjectionOptions(member));
}
}
}
/**
* DependencyDescriptor variant with a pre-resolved target bean name.

View File

@@ -42,8 +42,6 @@ import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.beans.factory.generator.AotContributingBeanPostProcessor;
import org.springframework.beans.factory.generator.BeanInstantiationContribution;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -87,8 +85,7 @@ import org.springframework.util.ReflectionUtils;
*/
@SuppressWarnings("serial")
public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor,
MergedBeanDefinitionPostProcessor, AotContributingBeanPostProcessor, BeanRegistrationAotProcessor,
PriorityOrdered, Serializable {
MergedBeanDefinitionPostProcessor, BeanRegistrationAotProcessor, PriorityOrdered, Serializable {
private final transient LifecycleMetadata emptyLifecycleMetadata =
new LifecycleMetadata(Object.class, Collections.emptyList(), Collections.emptyList()) {
@@ -159,22 +156,6 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
findInjectionMetadata(beanDefinition, beanType);
}
@Override
public BeanInstantiationContribution contribute(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
LifecycleMetadata metadata = findInjectionMetadata(beanDefinition, beanType);
if (!CollectionUtils.isEmpty(metadata.initMethods)) {
String[] initMethodNames = safeMerge(
beanDefinition.getInitMethodNames(), metadata.initMethods);
beanDefinition.setInitMethodNames(initMethodNames);
}
if (!CollectionUtils.isEmpty(metadata.destroyMethods)) {
String[] destroyMethodNames = safeMerge(
beanDefinition.getDestroyMethodNames(), metadata.destroyMethods);
beanDefinition.setDestroyMethodNames(destroyMethodNames);
}
return null;
}
@Override
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition();

View File

@@ -1,48 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.lang.Nullable;
/**
* Specialization of {@link BeanFactoryPostProcessor} that contributes bean
* factory optimizations ahead of time, using generated code that replaces
* runtime behavior.
*
* @author Stephane Nicoll
* @since 6.0
*/
public interface AotContributingBeanFactoryPostProcessor extends BeanFactoryPostProcessor {
/**
* Contribute a {@link BeanFactoryContribution} for the given bean factory,
* if applicable.
* @param beanFactory the bean factory to optimize
* @return the contribution to use or {@code null}
*/
@Nullable
BeanFactoryContribution contribute(ConfigurableListableBeanFactory beanFactory);
@Override
default void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.PriorityOrdered;
import org.springframework.lang.Nullable;
/**
* Specialization of a priority ordered {@link BeanPostProcessor} that
* contributes to bean instantiation ahead of time, providing generated code
* that is equivalent to its runtime behavior.
*
* <p>Contrary to other bean post processors, implementations of this interface
* are instantiated at build-time and should not rely on other beans in the
* context.
*
* @author Stephane Nicoll
* @since 6.0
*/
public interface AotContributingBeanPostProcessor extends BeanPostProcessor, PriorityOrdered {
/**
* Contribute a {@link BeanInstantiationContribution} for the given bean definition,
* if applicable.
* @param beanDefinition the merged bean definition for the bean
* @param beanType the inferred type of the bean
* @param beanName the name of the bean
* @return the contribution to use or {@code null} if the bean should not be processed
*/
@Nullable
BeanInstantiationContribution contribute(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);
}

View File

@@ -1,62 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import org.springframework.beans.factory.config.BeanDefinition;
/**
* Thrown when a bean definition could not be generated.
*
* @author Stephane Nicoll
* @since 6.0
*/
@SuppressWarnings("serial")
public class BeanDefinitionGenerationException extends RuntimeException {
private final String beanName;
private final BeanDefinition beanDefinition;
public BeanDefinitionGenerationException(String beanName, BeanDefinition beanDefinition, String message, Throwable cause) {
super(message, cause);
this.beanName = beanName;
this.beanDefinition = beanDefinition;
}
public BeanDefinitionGenerationException(String beanName, BeanDefinition beanDefinition, String message) {
super(message);
this.beanName = beanName;
this.beanDefinition = beanDefinition;
}
/**
* Return the bean name that could not be generated.
* @return the bean name
*/
public String getBeanName() {
return this.beanName;
}
/**
* Return the bean definition that could not be generated.
* @return the bean definition
*/
public BeanDefinition getBeanDefinition() {
return this.beanDefinition;
}
}

View File

@@ -1,127 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.io.support.SpringFactoriesLoader.ArgumentResolver;
/**
* A {@link BeanFactoryContribution} that generates the bean definitions of a
* bean factory, using {@link BeanRegistrationContributionProvider} to use
* appropriate customizations if necessary.
*
* <p>{@link BeanRegistrationContributionProvider} can be ordered, with the default
* implementation always coming last.
*
* @author Stephane Nicoll
* @since 6.0
* @see DefaultBeanRegistrationContributionProvider
*/
public class BeanDefinitionsContribution implements BeanFactoryContribution {
private final DefaultListableBeanFactory beanFactory;
private final List<BeanRegistrationContributionProvider> contributionProviders;
private final Map<String, BeanFactoryContribution> contributions;
BeanDefinitionsContribution(DefaultListableBeanFactory beanFactory,
List<BeanRegistrationContributionProvider> contributionProviders) {
this.beanFactory = beanFactory;
this.contributionProviders = contributionProviders;
this.contributions = new HashMap<>();
}
public BeanDefinitionsContribution(DefaultListableBeanFactory beanFactory) {
this(beanFactory, initializeProviders(beanFactory));
}
private static List<BeanRegistrationContributionProvider> initializeProviders(DefaultListableBeanFactory beanFactory) {
List<BeanRegistrationContributionProvider> providers = new ArrayList<>(
SpringFactoriesLoader.forDefaultResourceLocation(beanFactory.getBeanClassLoader()).load(
BeanRegistrationContributionProvider.class,
ArgumentResolver.from(type -> type.isInstance(beanFactory) ? beanFactory : null)));
providers.add(new DefaultBeanRegistrationContributionProvider(beanFactory));
return providers;
}
@Override
public void applyTo(BeanFactoryInitialization initialization) {
writeBeanDefinitions(initialization);
}
@Override
public BiPredicate<String, BeanDefinition> getBeanDefinitionExcludeFilter() {
List<BiPredicate<String, BeanDefinition>> predicates = new ArrayList<>();
for (String beanName : this.beanFactory.getBeanDefinitionNames()) {
handleMergedBeanDefinition(beanName, beanDefinition -> predicates.add(
getBeanRegistrationContribution(beanName, beanDefinition).getBeanDefinitionExcludeFilter()));
}
return predicates.stream().filter(Objects::nonNull).reduce((n, d) -> false, BiPredicate::or);
}
private void writeBeanDefinitions(BeanFactoryInitialization initialization) {
for (String beanName : this.beanFactory.getBeanDefinitionNames()) {
handleMergedBeanDefinition(beanName, beanDefinition -> {
BeanFactoryContribution registrationContribution = getBeanRegistrationContribution(
beanName, beanDefinition);
registrationContribution.applyTo(initialization);
});
}
}
private BeanFactoryContribution getBeanRegistrationContribution(
String beanName, RootBeanDefinition beanDefinition) {
return this.contributions.computeIfAbsent(beanName, name -> {
for (BeanRegistrationContributionProvider provider : this.contributionProviders) {
BeanFactoryContribution contribution = provider.getContributionFor(
beanName, beanDefinition);
if (contribution != null) {
return contribution;
}
}
throw new BeanRegistrationContributionNotFoundException(beanName, beanDefinition);
});
}
private void handleMergedBeanDefinition(String beanName, Consumer<RootBeanDefinition> consumer) {
RootBeanDefinition beanDefinition = (RootBeanDefinition) this.beanFactory.getMergedBeanDefinition(beanName);
try {
consumer.accept(beanDefinition);
}
catch (BeanDefinitionGenerationException ex) {
throw ex;
}
catch (Exception ex) {
String msg = String.format("Failed to handle bean with name '%s' and type '%s'",
beanName, beanDefinition.getResolvableType());
throw new BeanDefinitionGenerationException(beanName, beanDefinition, msg, ex);
}
}
}

View File

@@ -1,48 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import java.util.function.BiPredicate;
import org.springframework.beans.factory.config.BeanDefinition;
/**
* Contribute optimizations ahead of time to initialize a bean factory.
*
* @author Stephane Nicoll
* @since 6.0
*/
public interface BeanFactoryContribution {
/**
* Contribute ahead of time optimizations to the specific
* {@link BeanFactoryInitialization}.
* @param initialization {@link BeanFactoryInitialization} to contribute to
*/
void applyTo(BeanFactoryInitialization initialization);
/**
* Return a predicate that determines if a particular bean definition
* should be excluded from processing. Can be used to exclude infrastructure
* that has been optimized using generated code.
* @return the predicate to use
*/
default BiPredicate<String, BeanDefinition> getBeanDefinitionExcludeFilter() {
return (beanName, beanDefinition) -> false;
}
}

View File

@@ -1,110 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import java.util.function.Consumer;
import java.util.function.Supplier;
import javax.lang.model.element.Modifier;
import org.springframework.aot.generator.GeneratedType;
import org.springframework.aot.generator.GeneratedTypeContext;
import org.springframework.aot.generator.ProtectedAccess;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.CodeBlock.Builder;
import org.springframework.javapoet.MethodSpec;
/**
* The initialization of a {@link BeanFactory}.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
* @since 6.0
*/
public class BeanFactoryInitialization {
private final GeneratedTypeContext generatedTypeContext;
private final CodeBlock.Builder codeContributions;
public BeanFactoryInitialization(GeneratedTypeContext generatedTypeContext) {
this.generatedTypeContext = generatedTypeContext;
this.codeContributions = CodeBlock.builder();
}
/**
* Return the {@link GeneratedTypeContext} to use to contribute
* additional methods or hints.
* @return the generation context
*/
public GeneratedTypeContext generatedTypeContext() {
return this.generatedTypeContext;
}
/**
* Contribute code that initializes the bean factory and that does not
* require any privileged access.
* @param code the code to contribute
*/
public void contribute(Consumer<Builder> code) {
CodeBlock.Builder builder = CodeBlock.builder();
code.accept(builder);
CodeBlock codeBlock = builder.build();
this.codeContributions.add(codeBlock);
if (!codeBlock.toString().endsWith("\n")) {
this.codeContributions.add("\n");
}
}
/**
* Contribute code that initializes the bean factory. If privileged access
* is required, a public method in the target package is created and
* invoked, rather than contributing the code directly.
* @param protectedAccess the {@link ProtectedAccess} instance to use
* @param methodName a method name to use if privileged access is required
* @param methodBody the contribution
*/
public void contribute(ProtectedAccess protectedAccess, Supplier<String> methodName,
Consumer<Builder> methodBody) {
String targetPackageName = this.generatedTypeContext.getMainGeneratedType().getClassName().packageName();
String protectedPackageName = protectedAccess.getPrivilegedPackageName(targetPackageName);
if (protectedPackageName != null) {
GeneratedType type = this.generatedTypeContext.getGeneratedType(protectedPackageName);
MethodSpec.Builder method = MethodSpec.methodBuilder(methodName.get())
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(DefaultListableBeanFactory.class, "beanFactory");
CodeBlock.Builder code = CodeBlock.builder();
methodBody.accept(code);
method.addCode(code.build());
contribute(main -> main.addStatement("$T.$N(beanFactory)", type.getClassName(), type.addMethod(method)));
}
else {
contribute(methodBody);
}
}
/**
* Return the code that has been contributed to this instance.
* @return the code
*/
public CodeBlock toCodeBlock() {
return this.codeContributions.build();
}
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import org.springframework.aot.generator.ProtectedAccess.Options;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.support.MultiStatement;
import org.springframework.util.ReflectionUtils;
/**
* Support for generating {@link Field} access.
*
* @author Stephane Nicoll
* @since 6.0
*/
public class BeanFieldGenerator {
/**
* The {@link Options} to use to access a field.
*/
public static final Options FIELD_OPTIONS = Options.defaults()
.useReflection(member -> Modifier.isPrivate(member.getModifiers())).build();
/**
* Generate the necessary code to set the specified field. Use reflection
* using {@link ReflectionUtils} if necessary.
* @param field the field to set
* @param value a code representation of the field value
* @return the code to set the specified field
*/
public MultiStatement generateSetValue(String target, Field field, CodeBlock value) {
MultiStatement statement = new MultiStatement();
boolean useReflection = Modifier.isPrivate(field.getModifiers());
if (useReflection) {
String fieldName = String.format("%sField", field.getName());
statement.addStatement("$T $L = $T.findField($T.class, $S)", Field.class, fieldName, ReflectionUtils.class,
field.getDeclaringClass(), field.getName());
statement.addStatement("$T.makeAccessible($L)", ReflectionUtils.class, fieldName);
statement.addStatement("$T.setField($L, $L, $L)", ReflectionUtils.class, fieldName, target, value);
}
else {
statement.addStatement("$L.$L = $L", target, field.getName(), value);
}
return statement;
}
}

View File

@@ -1,44 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import org.springframework.aot.generator.CodeContribution;
/**
* A contribution to the instantiation of a bean following ahead of time
* processing.
*
* @author Stephane Nicoll
* @since 6.0
*/
@FunctionalInterface
public interface BeanInstantiationContribution {
/**
* Contribute bean instantiation to the specified {@link CodeContribution}.
* <p>Implementations of this interface can assume the following variables
* to be accessible:
* <ul>
* <li>{@code beanFactory}: the general {@code DefaultListableBeanFactory}</li>
* <li>{@code instanceContext}: the {@code BeanInstanceContext} callback</li>
* <li>{@code bean}: the variable that refers to the bean instance</li>
* </ul>
* @param contribution the {@link CodeContribution} to use
*/
void applyTo(CodeContribution contribution);
}

View File

@@ -1,47 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import java.lang.reflect.Executable;
import org.springframework.aot.generator.CodeContribution;
import org.springframework.aot.hint.RuntimeHints;
/**
* Generate code that instantiate a particular bean.
*
* @author Stephane Nicoll
* @since 6.0
*/
public interface BeanInstantiationGenerator {
/**
* Return the {@link Executable} that is used to create the bean instance
* for further metadata processing.
* @return the executable that is used to create the bean instance
*/
Executable getInstanceCreator();
/**
* Return the necessary code to instantiate a bean.
* @param runtimeHints the runtime hints instance to use
* @return a code contribution that provides an initialized bean instance
*/
CodeContribution generateBeanInstantiation(RuntimeHints runtimeHints);
}

View File

@@ -1,222 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import java.lang.reflect.Executable;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.springframework.aot.generator.ResolvableTypeGenerator;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedSet;
import org.springframework.core.ResolvableType;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.CodeBlock.Builder;
import org.springframework.javapoet.support.MultiCodeBlock;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
* Support for generating parameters.
*
* @author Stephane Nicoll
* @since 6.0
*/
public final class BeanParameterGenerator {
/**
* A default instance that does not handle inner bean definitions.
*/
public static final BeanParameterGenerator INSTANCE = new BeanParameterGenerator();
private final ResolvableTypeGenerator typeGenerator = new ResolvableTypeGenerator();
private final Function<BeanDefinition, CodeBlock> innerBeanDefinitionGenerator;
/**
* Create an instance with the callback to use to generate an inner bean
* definition.
* @param innerBeanDefinitionGenerator the inner bean definition generator
*/
public BeanParameterGenerator(Function<BeanDefinition, CodeBlock> innerBeanDefinitionGenerator) {
this.innerBeanDefinitionGenerator = innerBeanDefinitionGenerator;
}
/**
* Create an instance with no support for inner bean definitions.
*/
public BeanParameterGenerator() {
this(beanDefinition -> {
throw new IllegalStateException("Inner bean definition is not supported by this instance");
});
}
/**
* Generate the specified parameter {@code value}.
* @param value the value of the parameter
* @return the value of the parameter
*/
public CodeBlock generateParameterValue(@Nullable Object value) {
return generateParameterValue(value, () -> ResolvableType.forInstance(value));
}
/**
* Generate the specified parameter {@code value}.
* @param value the value of the parameter
* @param parameterType the type of the parameter
* @return the value of the parameter
*/
public CodeBlock generateParameterValue(@Nullable Object value, Supplier<ResolvableType> parameterType) {
Builder code = CodeBlock.builder();
generateParameterValue(code, value, parameterType);
return code.build();
}
/**
* Generate the parameter types of the specified {@link Executable}.
* @param executable the executable
* @return the parameter types of the executable as a comma separated list
*/
public CodeBlock generateExecutableParameterTypes(Executable executable) {
Class<?>[] parameterTypes = Arrays.stream(executable.getParameters())
.map(Parameter::getType).toArray(Class<?>[]::new);
return CodeBlock.of(Arrays.stream(parameterTypes).map(d -> "$T.class")
.collect(Collectors.joining(", ")), (Object[]) parameterTypes);
}
private void generateParameterValue(Builder code, @Nullable Object value, Supplier<ResolvableType> parameterTypeSupplier) {
if (value == null) {
code.add("null");
return;
}
ResolvableType parameterType = parameterTypeSupplier.get();
if (parameterType.isArray()) {
code.add("new $T { ", parameterType.toClass());
code.add(generateAll(Arrays.asList(ObjectUtils.toObjectArray(value)),
item -> parameterType.getComponentType()));
code.add(" }");
}
else if (value instanceof List<?> list) {
if (list.isEmpty()) {
code.add("$T.emptyList()", Collections.class);
}
else {
Class<?> listType = (value instanceof ManagedList ? ManagedList.class : List.class);
code.add("$T.of(", listType);
ResolvableType collectionType = parameterType.as(List.class).getGenerics()[0];
code.add(generateAll(list, item -> collectionType));
code.add(")");
}
}
else if (value instanceof Set<?> set) {
if (set.isEmpty()) {
code.add("$T.emptySet()", Collections.class);
}
else {
Class<?> setType = (value instanceof ManagedSet ? ManagedSet.class : Set.class);
code.add("$T.of(", setType);
ResolvableType collectionType = parameterType.as(Set.class).getGenerics()[0];
code.add(generateAll(set, item -> collectionType));
code.add(")");
}
}
else if (value instanceof Map<?, ?> map) {
if (map.size() <= 10) {
code.add("$T.of(", Map.class);
List<Object> parameters = new ArrayList<>();
map.forEach((mapKey, mapValue) -> {
parameters.add(mapKey);
parameters.add(mapValue);
});
code.add(generateAll(parameters, ResolvableType::forInstance));
code.add(")");
}
}
else if (value instanceof Character character) {
String result = '\'' + characterLiteralWithoutSingleQuotes(character) + '\'';
code.add(result);
}
else if (isPrimitiveOrWrapper(value)) {
code.add("$L", value);
}
else if (value instanceof String) {
code.add("$S", value);
}
else if (value instanceof Enum<?> enumValue) {
code.add("$T.$N", enumValue.getClass(), enumValue.name());
}
else if (value instanceof Class) {
code.add("$T.class", value);
}
else if (value instanceof ResolvableType) {
code.add(this.typeGenerator.generateTypeFor((ResolvableType) value));
}
else if (value instanceof BeanDefinition bd) {
code.add(this.innerBeanDefinitionGenerator.apply(bd));
}
else if (value instanceof BeanReference) {
code.add("new $T($S)", RuntimeBeanReference.class, ((BeanReference) value).getBeanName());
}
else {
throw new IllegalArgumentException("Parameter of type " + parameterType + " is not supported");
}
}
private <T> CodeBlock generateAll(Iterable<T> items, Function<T, ResolvableType> elementType) {
MultiCodeBlock multi = new MultiCodeBlock();
items.forEach(item -> multi.add(code ->
generateParameterValue(code, item, () -> elementType.apply(item))));
return multi.join(", ");
}
private boolean isPrimitiveOrWrapper(Object value) {
Class<?> valueType = value.getClass();
return (valueType.isPrimitive() || valueType == Double.class || valueType == Float.class
|| valueType == Long.class || valueType == Integer.class || valueType == Short.class
|| valueType == Character.class || valueType == Byte.class || valueType == Boolean.class);
}
// Copied from com.squareup.javapoet.Util
private static String characterLiteralWithoutSingleQuotes(char c) {
// see https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6
return switch (c) {
case '\b' -> "\\b"; /* \u0008: backspace (BS) */
case '\t' -> "\\t"; /* \u0009: horizontal tab (HT) */
case '\n' -> "\\n"; /* \u000a: linefeed (LF) */
case '\f' -> "\\f"; /* \u000c: form feed (FF) */
case '\r' -> "\\r"; /* \u000d: carriage return (CR) */
case '\"' -> "\""; /* \u0022: double quote (") */
case '\'' -> "\\'"; /* \u0027: single quote (') */
case '\\' -> "\\\\"; /* \u005c: backslash (\) */
default -> Character.isISOControl(c) ? String.format("\\u%04x", (int) c) : Character.toString(c);
};
}
}

View File

@@ -1,514 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.function.Predicate;
import javax.lang.model.SourceVersion;
import org.springframework.aot.generator.CodeContribution;
import org.springframework.aot.generator.ProtectedAccess;
import org.springframework.aot.generator.ResolvableTypeGenerator;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.beans.BeanInfoFactory;
import org.springframework.beans.ExtendedBeanInfoFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.generator.config.BeanDefinitionRegistrar;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.AttributeAccessor;
import org.springframework.core.ResolvableType;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.CodeBlock.Builder;
import org.springframework.javapoet.support.MultiStatement;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* A {@link BeanFactoryContribution} that registers a bean with the bean
* factory.
*
* @author Stephane Nicoll
* @since 6.0
*/
public class BeanRegistrationBeanFactoryContribution implements BeanFactoryContribution {
private static final BeanInfoFactory beanInfoFactory = new ExtendedBeanInfoFactory();
private static final ResolvableTypeGenerator typeGenerator = new ResolvableTypeGenerator();
private final String beanName;
private final RootBeanDefinition beanDefinition;
private final BeanInstantiationGenerator beanInstantiationGenerator;
@Nullable
private final DefaultBeanRegistrationContributionProvider innerBeanRegistrationContributionProvider;
private int nesting = 0;
BeanRegistrationBeanFactoryContribution(String beanName, RootBeanDefinition beanDefinition,
BeanInstantiationGenerator beanInstantiationGenerator,
@Nullable DefaultBeanRegistrationContributionProvider innerBeanRegistrationContributionProvider) {
this.beanName = beanName;
this.beanDefinition = beanDefinition;
this.beanInstantiationGenerator = beanInstantiationGenerator;
this.innerBeanRegistrationContributionProvider = innerBeanRegistrationContributionProvider;
}
public BeanRegistrationBeanFactoryContribution(String beanName, RootBeanDefinition beanDefinition,
BeanInstantiationGenerator beanInstantiationGenerator) {
this(beanName, beanDefinition, beanInstantiationGenerator, null);
}
String getBeanName() {
return this.beanName;
}
RootBeanDefinition getBeanDefinition() {
return this.beanDefinition;
}
@Override
public void applyTo(BeanFactoryInitialization initialization) {
RuntimeHints runtimeHints = initialization.generatedTypeContext().runtimeHints();
registerRuntimeHints(runtimeHints);
CodeContribution beanInstanceContribution = generateBeanInstance(runtimeHints);
// Write everything in one place
ProtectedAccess protectedAccess = beanInstanceContribution.protectedAccess();
protectedAccess.analyze(this.beanDefinition.getResolvableType());
initialization.contribute(protectedAccess, this::registerBeanMethodName, code ->
code.add(generateBeanRegistration(runtimeHints, beanInstanceContribution.statements())));
}
/**
* Register the necessary hints that are required to process the bean
* registration generated by this instance.
* @param runtimeHints the runtime hints to use
*/
void registerRuntimeHints(RuntimeHints runtimeHints) {
String[] initMethodNames = this.beanDefinition.getInitMethodNames();
if (!ObjectUtils.isEmpty(initMethodNames)) {
registerInitDestroyMethodsRuntimeHints(initMethodNames, runtimeHints);
}
String[] destroyMethodNames = this.beanDefinition.getDestroyMethodNames();
if (!ObjectUtils.isEmpty(destroyMethodNames)) {
registerInitDestroyMethodsRuntimeHints(destroyMethodNames, runtimeHints);
}
registerPropertyValuesRuntimeHints(runtimeHints);
}
/**
* Generate the necessary code to register a {@link BeanDefinition} in the
* bean registry.
* @param runtimeHints the hints to use
* @param beanInstanceStatements the {@linkplain MultiStatement statements}
* to create and initialize the bean instance
* @return bean registration code
*/
CodeBlock generateBeanRegistration(RuntimeHints runtimeHints, MultiStatement beanInstanceStatements) {
BeanParameterGenerator parameterGenerator = createBeanParameterGenerator(runtimeHints);
Generator generator = new Generator(parameterGenerator);
return generator.generateBeanRegistration(beanInstanceStatements);
}
/**
* Generate the necessary code to create a {@link BeanDefinition}.
* @param runtimeHints the hints to use
* @return bean definition code
*/
CodeBlock generateBeanDefinition(RuntimeHints runtimeHints) {
CodeContribution beanInstanceContribution = generateBeanInstance(runtimeHints);
BeanParameterGenerator parameterGenerator = createBeanParameterGenerator(runtimeHints);
Generator generator = new Generator(parameterGenerator);
return generator.generateBeanDefinition(beanInstanceContribution.statements());
}
private BeanParameterGenerator createBeanParameterGenerator(RuntimeHints runtimeHints) {
return new BeanParameterGenerator(beanDefinition ->
generateInnerBeanDefinition(beanDefinition, runtimeHints));
}
/**
* Return the predicate to use to include Bean Definition
* {@link AttributeAccessor attributes}.
* @return the bean definition's attributes include filter
*/
protected Predicate<String> getAttributeFilter() {
return candidate -> false;
}
/**
* Specify if the creator {@link Executable} should be defined. By default,
* a creator is specified if the {@code instanceSupplier} callback is used
* with an {@code instanceContext} callback.
* @param instanceCreator the executable to use to instantiate the bean
* @return {@code true} to declare the creator
*/
protected boolean shouldDeclareCreator(Executable instanceCreator) {
if (instanceCreator instanceof Method) {
return true;
}
if (instanceCreator instanceof Constructor<?> constructor) {
int minArgs = isInnerClass(constructor.getDeclaringClass()) ? 2 : 1;
return instanceCreator.getParameterCount() >= minArgs;
}
return false;
}
/**
* Return the necessary code to instantiate and post-process a bean.
* @param runtimeHints the {@link RuntimeHints} to use
* @return a code contribution that provides an initialized bean instance
*/
protected CodeContribution generateBeanInstance(RuntimeHints runtimeHints) {
return this.beanInstantiationGenerator.generateBeanInstantiation(runtimeHints);
}
private void registerInitDestroyMethodsRuntimeHints(String[] methodNames, RuntimeHints runtimeHints) {
for (String methodName : methodNames) {
Method method = ReflectionUtils.findMethod(getUserBeanClass(), methodName);
if (method != null) {
runtimeHints.reflection().registerMethod(method, hint -> hint.withMode(ExecutableMode.INVOKE));
}
}
}
private void registerPropertyValuesRuntimeHints(RuntimeHints runtimeHints) {
if (!this.beanDefinition.hasPropertyValues()) {
return;
}
BeanInfo beanInfo = getBeanInfo(this.beanDefinition.getResolvableType().toClass());
if (beanInfo != null) {
ReflectionHints reflectionHints = runtimeHints.reflection();
this.beanDefinition.getPropertyValues().getPropertyValueList().forEach(propertyValue -> {
Method writeMethod = findWriteMethod(beanInfo, propertyValue.getName());
if (writeMethod != null) {
reflectionHints.registerMethod(writeMethod, hint -> hint.withMode(ExecutableMode.INVOKE));
}
});
}
}
@Nullable
private BeanInfo getBeanInfo(Class<?> beanType) {
try {
BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanType);
if (beanInfo != null) {
return beanInfo;
}
return Introspector.getBeanInfo(beanType, Introspector.IGNORE_ALL_BEANINFO);
}
catch (IntrospectionException ex) {
return null;
}
}
@Nullable
private Method findWriteMethod(BeanInfo beanInfo, String propertyName) {
return Arrays.stream(beanInfo.getPropertyDescriptors())
.filter(pd -> propertyName.equals(pd.getName()))
.map(java.beans.PropertyDescriptor::getWriteMethod)
.filter(Objects::nonNull).findFirst().orElse(null);
}
protected CodeBlock initializeBeanDefinitionRegistrar() {
return CodeBlock.of("$T.of($S, ", BeanDefinitionRegistrar.class, this.beanName);
}
private Class<?> getUserBeanClass() {
return ClassUtils.getUserClass(this.beanDefinition.getResolvableType().toClass());
}
private void handleCreatorReference(Builder code, Executable creator) {
if (creator instanceof Method) {
code.add(".withFactoryMethod($T.class, $S", creator.getDeclaringClass(), creator.getName());
if (creator.getParameterCount() > 0) {
code.add(", ");
}
}
else {
code.add(".withConstructor(");
}
code.add(BeanParameterGenerator.INSTANCE.generateExecutableParameterTypes(creator));
code.add(")");
}
private CodeBlock generateInnerBeanDefinition(BeanDefinition beanDefinition, RuntimeHints runtimeHints) {
if (this.innerBeanRegistrationContributionProvider == null) {
throw new IllegalStateException("This generator does not handle inner bean definition " + beanDefinition);
}
BeanRegistrationBeanFactoryContribution innerBeanRegistrationContribution = this.innerBeanRegistrationContributionProvider
.getInnerBeanRegistrationContribution(this, beanDefinition);
innerBeanRegistrationContribution.nesting = this.nesting + 1;
innerBeanRegistrationContribution.registerRuntimeHints(runtimeHints);
return innerBeanRegistrationContribution.generateBeanDefinition(runtimeHints);
}
private String registerBeanMethodName() {
Executable instanceCreator = this.beanInstantiationGenerator.getInstanceCreator();
if (instanceCreator instanceof Method method) {
String target = (isValidName(this.beanName)) ? this.beanName : method.getName();
return String.format("register%s_%s", method.getDeclaringClass().getSimpleName(), target);
}
else if (instanceCreator.getDeclaringClass().getEnclosingClass() != null) {
String target = (isValidName(this.beanName)) ? this.beanName : getUserBeanClass().getSimpleName();
Class<?> enclosingClass = instanceCreator.getDeclaringClass().getEnclosingClass();
return String.format("register%s_%s", enclosingClass.getSimpleName(), target);
}
else {
String target = (isValidName(this.beanName)) ? this.beanName : getUserBeanClass().getSimpleName();
return "register" + StringUtils.capitalize(target);
}
}
private boolean isValidName(@Nullable String name) {
return name != null && SourceVersion.isIdentifier(name) && !SourceVersion.isKeyword(name);
}
private String determineVariableName(String name) {
return name + "_".repeat(this.nesting);
}
private static boolean isInnerClass(Class<?> type) {
return type.isMemberClass() && !java.lang.reflect.Modifier.isStatic(type.getModifiers());
}
class Generator {
private final BeanParameterGenerator parameterGenerator;
private final RootBeanDefinition beanDefinition;
Generator(BeanParameterGenerator parameterGenerator) {
this.parameterGenerator = parameterGenerator;
this.beanDefinition = BeanRegistrationBeanFactoryContribution.this.beanDefinition;
}
CodeBlock generateBeanRegistration(MultiStatement instanceStatements) {
CodeBlock.Builder code = CodeBlock.builder();
initializeBeanDefinitionRegistrar(instanceStatements, code);
code.addStatement(".register(beanFactory)");
return code.build();
}
CodeBlock generateBeanDefinition(MultiStatement instanceStatements) {
CodeBlock.Builder code = CodeBlock.builder();
initializeBeanDefinitionRegistrar(instanceStatements, code);
code.add(".toBeanDefinition()");
return code.build();
}
private void initializeBeanDefinitionRegistrar(MultiStatement instanceStatements, Builder code) {
Executable instanceCreator = BeanRegistrationBeanFactoryContribution.this.beanInstantiationGenerator.getInstanceCreator();
code.add(BeanRegistrationBeanFactoryContribution.this.initializeBeanDefinitionRegistrar());
generateBeanType(code);
code.add(")");
boolean shouldDeclareCreator = shouldDeclareCreator(instanceCreator);
if (shouldDeclareCreator) {
handleCreatorReference(code, instanceCreator);
}
code.add("\n").indent().indent();
code.add(".instanceSupplier(");
code.add(instanceStatements.toLambdaBody());
code.add(")").unindent().unindent();
handleBeanDefinitionMetadata(code);
}
private void generateBeanType(Builder code) {
ResolvableType resolvableType = this.beanDefinition.getResolvableType();
if (resolvableType.hasGenerics() && !hasUnresolvedGenerics(resolvableType)) {
code.add(typeGenerator.generateTypeFor(resolvableType));
}
else {
code.add("$T.class", getUserBeanClass());
}
}
private boolean hasUnresolvedGenerics(ResolvableType resolvableType) {
if (resolvableType.hasUnresolvableGenerics()) {
return true;
}
for (ResolvableType generic : resolvableType.getGenerics()) {
if (hasUnresolvedGenerics(generic)) {
return true;
}
}
return false;
}
private void handleBeanDefinitionMetadata(Builder code) {
String bdVariable = determineVariableName("bd");
MultiStatement statements = new MultiStatement();
String[] initMethodNames = this.beanDefinition.getInitMethodNames();
if (!ObjectUtils.isEmpty(initMethodNames)) {
handleInitMethodNames(statements, bdVariable, initMethodNames);
}
String[] destroyMethodNames = this.beanDefinition.getDestroyMethodNames();
if (!ObjectUtils.isEmpty(destroyMethodNames)) {
handleDestroyMethodNames(statements, bdVariable, destroyMethodNames);
}
if (this.beanDefinition.isPrimary()) {
statements.addStatement("$L.setPrimary(true)", bdVariable);
}
String scope = this.beanDefinition.getScope();
if (StringUtils.hasText(scope) && !ConfigurableBeanFactory.SCOPE_SINGLETON.equals(scope)) {
statements.addStatement("$L.setScope($S)", bdVariable, scope);
}
String[] dependsOn = this.beanDefinition.getDependsOn();
if (!ObjectUtils.isEmpty(dependsOn)) {
statements.addStatement("$L.setDependsOn($L)", bdVariable,
this.parameterGenerator.generateParameterValue(dependsOn));
}
if (this.beanDefinition.isLazyInit()) {
statements.addStatement("$L.setLazyInit(true)", bdVariable);
}
if (!this.beanDefinition.isAutowireCandidate()) {
statements.addStatement("$L.setAutowireCandidate(false)", bdVariable);
}
if (this.beanDefinition.isSynthetic()) {
statements.addStatement("$L.setSynthetic(true)", bdVariable);
}
if (this.beanDefinition.getRole() != BeanDefinition.ROLE_APPLICATION) {
statements.addStatement("$L.setRole($L)", bdVariable, this.beanDefinition.getRole());
}
Map<Integer, ValueHolder> indexedArgumentValues = this.beanDefinition.getConstructorArgumentValues()
.getIndexedArgumentValues();
if (!indexedArgumentValues.isEmpty()) {
handleArgumentValues(statements, bdVariable, indexedArgumentValues);
}
if (this.beanDefinition.hasPropertyValues()) {
handlePropertyValues(statements, bdVariable, this.beanDefinition.getPropertyValues());
}
if (this.beanDefinition.attributeNames().length > 0) {
handleAttributes(statements, bdVariable);
}
if (statements.isEmpty()) {
return;
}
code.add(statements.toLambda(".customize((" + bdVariable + ") ->"));
code.add(")");
}
private void handleInitMethodNames(MultiStatement statements, String bdVariable, String[] initMethodNames) {
if (initMethodNames.length == 1) {
statements.addStatement("$L.setInitMethodName($S)", bdVariable, initMethodNames[0]);
}
else {
statements.addStatement("$L.setInitMethodNames($L)", bdVariable,
this.parameterGenerator.generateParameterValue(initMethodNames));
}
}
private void handleDestroyMethodNames(MultiStatement statements, String bdVariable, String[] destroyMethodNames) {
if (destroyMethodNames.length == 1) {
statements.addStatement("$L.setDestroyMethodName($S)", bdVariable, destroyMethodNames[0]);
}
else {
statements.addStatement("$L.setDestroyMethodNames($L)", bdVariable,
this.parameterGenerator.generateParameterValue(destroyMethodNames));
}
}
private void handleArgumentValues(MultiStatement statements, String bdVariable,
Map<Integer, ValueHolder> indexedArgumentValues) {
if (indexedArgumentValues.size() == 1) {
Entry<Integer, ValueHolder> entry = indexedArgumentValues.entrySet().iterator().next();
statements.addStatement(generateArgumentValue(bdVariable + ".getConstructorArgumentValues().",
entry.getKey(), entry.getValue()));
}
else {
String avVariable = determineVariableName("argumentValues");
statements.addStatement("$T $L = $L.getConstructorArgumentValues()", ConstructorArgumentValues.class, avVariable, bdVariable);
statements.addAll(indexedArgumentValues.entrySet(), entry -> generateArgumentValue(avVariable + ".",
entry.getKey(), entry.getValue()));
}
}
private CodeBlock generateArgumentValue(String prefix, Integer index, ValueHolder valueHolder) {
Builder code = CodeBlock.builder();
code.add(prefix);
code.add("addIndexedArgumentValue($L, ", index);
Object value = valueHolder.getValue();
code.add(this.parameterGenerator.generateParameterValue(value));
code.add(")");
return code.build();
}
private void handlePropertyValues(MultiStatement statements, String bdVariable,
PropertyValues propertyValues) {
PropertyValue[] properties = propertyValues.getPropertyValues();
if (properties.length == 1) {
statements.addStatement(generatePropertyValue(bdVariable + ".getPropertyValues().", properties[0]));
}
else {
String pvVariable = determineVariableName("propertyValues");
statements.addStatement("$T $L = $L.getPropertyValues()", MutablePropertyValues.class, pvVariable, bdVariable);
for (PropertyValue property : properties) {
statements.addStatement(generatePropertyValue(pvVariable + ".", property));
}
}
}
private CodeBlock generatePropertyValue(String prefix, PropertyValue property) {
Builder code = CodeBlock.builder();
code.add(prefix);
code.add("addPropertyValue($S, ", property.getName());
Object value = property.getValue();
code.add(this.parameterGenerator.generateParameterValue(value));
code.add(")");
return code.build();
}
private void handleAttributes(MultiStatement statements, String bdVariable) {
String[] attributeNames = this.beanDefinition.attributeNames();
Predicate<String> filter = getAttributeFilter();
for (String attributeName : attributeNames) {
if (filter.test(attributeName)) {
Object value = this.beanDefinition.getAttribute(attributeName);
Builder code = CodeBlock.builder();
code.add("$L.setAttribute($S, ", bdVariable, attributeName);
code.add((this.parameterGenerator.generateParameterValue(value)));
code.add(")");
statements.addStatement(code.build());
}
}
}
}
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import org.springframework.beans.factory.config.BeanDefinition;
/**
* Thrown when no suitable {@link BeanFactoryContribution} can be provided
* for the registration of a given bean definition.
*
* @author Stephane Nicoll
* @since 6.0
*/
@SuppressWarnings("serial")
public class BeanRegistrationContributionNotFoundException extends BeanDefinitionGenerationException {
public BeanRegistrationContributionNotFoundException(String beanName, BeanDefinition beanDefinition) {
super(beanName, beanDefinition, String.format(
"No suitable contribution found for bean with name '%s' and type '%s'",
beanName, beanDefinition.getResolvableType()));
}
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.lang.Nullable;
/**
* Strategy interface to be implemented by components that require custom
* contribution for a bean definition.
*
* @author Stephane Nicoll
* @since 6.0
*/
@FunctionalInterface
public interface BeanRegistrationContributionProvider {
/**
* Return the {@link BeanFactoryContribution} that is capable of contributing
* the registration of a bean for the given {@link RootBeanDefinition} or
* {@code null} if the specified bean definition is not supported.
* @param beanName the bean name to handle
* @param beanDefinition the merged bean definition
* @return a contribution for the specified bean definition or {@code null}
*/
@Nullable
BeanFactoryContribution getContributionFor(String beanName, RootBeanDefinition beanDefinition);
}

View File

@@ -1,168 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.aot.generator.CodeContribution;
import org.springframework.aot.generator.DefaultCodeContribution;
import org.springframework.aot.generator.ProtectedAccess.Options;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.javapoet.CodeBlock;
import org.springframework.util.ClassUtils;
/**
* Default {@link BeanInstantiationGenerator} implementation.
*
* @author Stephane Nicoll
* @see BeanInstantiationContribution
*/
class DefaultBeanInstantiationGenerator implements BeanInstantiationGenerator {
private final Executable instanceCreator;
private final List<BeanInstantiationContribution> contributions;
private final InjectionGenerator injectionGenerator;
private final Options beanInstanceOptions;
DefaultBeanInstantiationGenerator(Executable instanceCreator, List<BeanInstantiationContribution> contributions) {
this.instanceCreator = instanceCreator;
this.contributions = List.copyOf(contributions);
this.injectionGenerator = new InjectionGenerator();
this.beanInstanceOptions = Options.defaults().useReflection(member -> false)
.assignReturnType(member -> !this.contributions.isEmpty()).build();
}
@Override
public Executable getInstanceCreator() {
return this.instanceCreator;
}
@Override
public CodeContribution generateBeanInstantiation(RuntimeHints runtimeHints) {
DefaultCodeContribution codeContribution = new DefaultCodeContribution(runtimeHints);
codeContribution.protectedAccess().analyze(this.instanceCreator, this.beanInstanceOptions);
if (this.instanceCreator instanceof Constructor<?> constructor) {
generateBeanInstantiation(codeContribution, constructor);
}
else if (this.instanceCreator instanceof Method method) {
generateBeanInstantiation(codeContribution, method);
}
return codeContribution;
}
private void generateBeanInstantiation(CodeContribution codeContribution, Constructor<?> constructor) {
Class<?> declaringType = ClassUtils.getUserClass(constructor.getDeclaringClass());
boolean innerClass = isInnerClass(declaringType);
boolean multiStatements = !this.contributions.isEmpty();
int minArgs = isInnerClass(declaringType) ? 2 : 1;
CodeBlock.Builder code = CodeBlock.builder();
// Shortcut for common case
if (!multiStatements && constructor.getParameterTypes().length < minArgs) {
if (innerClass) {
code.add("() -> beanFactory.getBean($T.class).new $L()",
declaringType.getEnclosingClass(), declaringType.getSimpleName());
}
else {
// Only apply the shortcut if there's one candidate
if (declaringType.getDeclaredConstructors().length > 1) {
code.add("() -> new $T()", declaringType);
}
else {
code.add("$T::new", declaringType);
}
}
codeContribution.statements().addStatement(code.build());
return;
}
codeContribution.runtimeHints().reflection().registerConstructor(constructor,
hint -> hint.withMode(ExecutableMode.INTROSPECT));
code.add("(instanceContext) ->");
branch(multiStatements, () -> code.beginControlFlow(""), () -> code.add(" "));
if (multiStatements) {
code.add("$T bean = ", declaringType);
}
code.add(this.injectionGenerator.generateInstantiation(constructor));
codeContribution.statements().addStatement(code.build());
if (multiStatements) {
for (BeanInstantiationContribution contribution : this.contributions) {
contribution.applyTo(codeContribution);
}
codeContribution.statements().addStatement("return bean")
.add(codeBlock -> codeBlock.unindent().add("}"));
}
}
private static boolean isInnerClass(Class<?> type) {
return type.isMemberClass() && !Modifier.isStatic(type.getModifiers());
}
private void generateBeanInstantiation(CodeContribution codeContribution, Method method) {
// Factory method can be introspected
codeContribution.runtimeHints().reflection().registerMethod(method,
hint -> hint.withMode(ExecutableMode.INTROSPECT));
List<Class<?>> parameterTypes = new ArrayList<>(Arrays.asList(method.getParameterTypes()));
boolean multiStatements = !this.contributions.isEmpty();
Class<?> declaringType = method.getDeclaringClass();
CodeBlock.Builder code = CodeBlock.builder();
// Shortcut for common case
if (!multiStatements && parameterTypes.isEmpty()) {
code.add("() -> ");
branch(Modifier.isStatic(method.getModifiers()),
() -> code.add("$T", declaringType),
() -> code.add("beanFactory.getBean($T.class)", declaringType));
code.add(".$L()", method.getName());
codeContribution.statements().addStatement(code.build());
return;
}
code.add("(instanceContext) ->");
branch(multiStatements, () -> code.beginControlFlow(""), () -> code.add(" "));
if (multiStatements) {
code.add("$T bean = ", method.getReturnType());
}
code.add(this.injectionGenerator.generateInstantiation(method));
codeContribution.statements().addStatement(code.build());
if (multiStatements) {
for (BeanInstantiationContribution contribution : this.contributions) {
contribution.applyTo(codeContribution);
}
codeContribution.statements().addStatement("return bean")
.add(codeBlock -> codeBlock.unindent().add("}"));
}
}
private static void branch(boolean condition, Runnable ifTrue, Runnable ifFalse) {
if (condition) {
ifTrue.run();
}
else {
ifFalse.run();
}
}
}

View File

@@ -1,494 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionValueResolver;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.OrderComparator;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.function.SingletonSupplier;
/**
* Default {@link BeanRegistrationContributionProvider} implementation.
*
* @author Stephane Nicoll
* @since 6.0
*/
public final class DefaultBeanRegistrationContributionProvider implements BeanRegistrationContributionProvider {
private final DefaultListableBeanFactory beanFactory;
private final ExecutableProvider executableProvider;
private final Supplier<List<AotContributingBeanPostProcessor>> beanPostProcessors;
public DefaultBeanRegistrationContributionProvider(DefaultListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
this.executableProvider = new ExecutableProvider(beanFactory);
this.beanPostProcessors = new SingletonSupplier<>(null,
() -> loadAotContributingBeanPostProcessors(beanFactory));
}
private static List<AotContributingBeanPostProcessor> loadAotContributingBeanPostProcessors(
DefaultListableBeanFactory beanFactory) {
String[] postProcessorNames = beanFactory.getBeanNamesForType(AotContributingBeanPostProcessor.class, true, false);
List<AotContributingBeanPostProcessor> postProcessors = new ArrayList<>();
for (String ppName : postProcessorNames) {
postProcessors.add(beanFactory.getBean(ppName, AotContributingBeanPostProcessor.class));
}
sortPostProcessors(postProcessors, beanFactory);
return postProcessors;
}
@Override
public BeanRegistrationBeanFactoryContribution getContributionFor(
String beanName, RootBeanDefinition beanDefinition) {
BeanInstantiationGenerator beanInstantiationGenerator = getBeanInstantiationGenerator(
beanName, beanDefinition);
return new BeanRegistrationBeanFactoryContribution(beanName, beanDefinition, beanInstantiationGenerator, this);
}
public BeanInstantiationGenerator getBeanInstantiationGenerator(
String beanName, RootBeanDefinition beanDefinition) {
return new DefaultBeanInstantiationGenerator(determineExecutable(beanDefinition),
determineBeanInstanceContributions(beanName, beanDefinition));
}
/**
* Return a {@link BeanRegistrationBeanFactoryContribution} that is capable of
* contributing the specified inner {@link BeanDefinition}.
* @param parent the contribution of the parent bean definition
* @param innerBeanDefinition the inner bean definition
* @return a contribution for the specified inner bean definition
*/
BeanRegistrationBeanFactoryContribution getInnerBeanRegistrationContribution(
BeanRegistrationBeanFactoryContribution parent, BeanDefinition innerBeanDefinition) {
BeanDefinitionValueResolver bdvr = new BeanDefinitionValueResolver(this.beanFactory,
parent.getBeanName(), parent.getBeanDefinition());
return bdvr.resolveInnerBean(null, innerBeanDefinition, (beanName, bd) ->
new InnerBeanRegistrationBeanFactoryContribution(beanName, bd,
getBeanInstantiationGenerator(beanName, bd), this));
}
private Executable determineExecutable(RootBeanDefinition beanDefinition) {
Executable executable = this.executableProvider.detectBeanInstanceExecutable(beanDefinition);
if (executable == null) {
throw new IllegalStateException("No suitable executor found for " + beanDefinition);
}
return executable;
}
private List<BeanInstantiationContribution> determineBeanInstanceContributions(
String beanName, RootBeanDefinition beanDefinition) {
List<BeanInstantiationContribution> contributions = new ArrayList<>();
for (AotContributingBeanPostProcessor pp : this.beanPostProcessors.get()) {
BeanInstantiationContribution contribution = pp.contribute(beanDefinition,
beanDefinition.getResolvableType().toClass(), beanName);
if (contribution != null) {
contributions.add(contribution);
}
}
return contributions;
}
private static void sortPostProcessors(List<?> postProcessors, ConfigurableListableBeanFactory beanFactory) {
// Nothing to sort?
if (postProcessors.size() <= 1) {
return;
}
Comparator<Object> comparatorToUse = null;
if (beanFactory instanceof DefaultListableBeanFactory) {
comparatorToUse = ((DefaultListableBeanFactory) beanFactory).getDependencyComparator();
}
if (comparatorToUse == null) {
comparatorToUse = OrderComparator.INSTANCE;
}
postProcessors.sort(comparatorToUse);
}
// FIXME: copy-paste from Spring Native that should go away in favor of ConstructorResolver
private static class ExecutableProvider {
private static final Log logger = LogFactory.getLog(ExecutableProvider.class);
private final ConfigurableBeanFactory beanFactory;
private final ClassLoader classLoader;
ExecutableProvider(ConfigurableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
this.classLoader = (beanFactory.getBeanClassLoader() != null
? beanFactory.getBeanClassLoader() : getClass().getClassLoader());
}
@Nullable
Executable detectBeanInstanceExecutable(BeanDefinition beanDefinition) {
Supplier<ResolvableType> beanType = () -> getBeanType(beanDefinition);
List<ResolvableType> valueTypes = beanDefinition.hasConstructorArgumentValues()
? determineParameterValueTypes(beanDefinition.getConstructorArgumentValues()) : Collections.emptyList();
Method resolvedFactoryMethod = resolveFactoryMethod(beanDefinition, valueTypes);
if (resolvedFactoryMethod != null) {
return resolvedFactoryMethod;
}
Class<?> factoryBeanClass = getFactoryBeanClass(beanDefinition);
if (factoryBeanClass != null && !factoryBeanClass.equals(beanDefinition.getResolvableType().toClass())) {
ResolvableType resolvableType = beanDefinition.getResolvableType();
boolean isCompatible = ResolvableType.forClass(factoryBeanClass).as(FactoryBean.class)
.getGeneric(0).isAssignableFrom(resolvableType);
if (isCompatible) {
return resolveConstructor(() -> ResolvableType.forClass(factoryBeanClass), valueTypes);
}
else {
throw new IllegalStateException(String.format("Incompatible target type '%s' for factory bean '%s'",
resolvableType.toClass().getName(), factoryBeanClass.getName()));
}
}
Executable resolvedConstructor = resolveConstructor(beanType, valueTypes);
if (resolvedConstructor != null) {
return resolvedConstructor;
}
Executable resolvedConstructorOrFactoryMethod = getField(beanDefinition,
"resolvedConstructorOrFactoryMethod", Executable.class);
if (resolvedConstructorOrFactoryMethod != null) {
logger.error("resolvedConstructorOrFactoryMethod required for " + beanDefinition);
return resolvedConstructorOrFactoryMethod;
}
return null;
}
private List<ResolvableType> determineParameterValueTypes(ConstructorArgumentValues constructorArgumentValues) {
List<ResolvableType> parameterTypes = new ArrayList<>();
for (ValueHolder valueHolder : constructorArgumentValues.getIndexedArgumentValues().values()) {
if (valueHolder.getType() != null) {
parameterTypes.add(ResolvableType.forClass(loadClass(valueHolder.getType())));
}
else {
Object value = valueHolder.getValue();
if (value instanceof BeanReference) {
parameterTypes.add(ResolvableType.forClass(
this.beanFactory.getType(((BeanReference) value).getBeanName(), false)));
}
else if (value instanceof BeanDefinition) {
parameterTypes.add(extractTypeFromBeanDefinition(getBeanType((BeanDefinition) value)));
}
else {
parameterTypes.add(ResolvableType.forInstance(value));
}
}
}
return parameterTypes;
}
private ResolvableType extractTypeFromBeanDefinition(ResolvableType type) {
if (FactoryBean.class.isAssignableFrom(type.toClass())) {
return type.as(FactoryBean.class).getGeneric(0);
}
return type;
}
@Nullable
private Method resolveFactoryMethod(BeanDefinition beanDefinition, List<ResolvableType> valueTypes) {
if (beanDefinition instanceof RootBeanDefinition rbd) {
Method resolvedFactoryMethod = rbd.getResolvedFactoryMethod();
if (resolvedFactoryMethod != null) {
return resolvedFactoryMethod;
}
}
String factoryMethodName = beanDefinition.getFactoryMethodName();
if (factoryMethodName != null) {
List<Method> methods = new ArrayList<>();
Class<?> beanClass = getBeanClass(beanDefinition);
if (beanClass == null) {
throw new IllegalStateException("Failed to determine bean class of " + beanDefinition);
}
ReflectionUtils.doWithMethods(beanClass, methods::add,
method -> isFactoryMethodCandidate(beanClass, method, factoryMethodName));
if (methods.size() >= 1) {
Function<Method, List<ResolvableType>> parameterTypesFactory = method -> {
List<ResolvableType> types = new ArrayList<>();
for (int i = 0; i < method.getParameterCount(); i++) {
types.add(ResolvableType.forMethodParameter(method, i));
}
return types;
};
return (Method) resolveFactoryMethod(methods, parameterTypesFactory, valueTypes);
}
}
return null;
}
private boolean isFactoryMethodCandidate(Class<?> beanClass, Method method, String factoryMethodName) {
if (method.getName().equals(factoryMethodName)) {
if (Modifier.isStatic(method.getModifiers())) {
return method.getDeclaringClass().equals(beanClass);
}
return !Modifier.isPrivate(method.getModifiers());
}
return false;
}
@Nullable
private Executable resolveConstructor(Supplier<ResolvableType> beanType, List<ResolvableType> valueTypes) {
Class<?> type = ClassUtils.getUserClass(beanType.get().toClass());
Constructor<?>[] constructors = type.getDeclaredConstructors();
if (constructors.length == 1) {
return constructors[0];
}
for (Constructor<?> constructor : constructors) {
if (MergedAnnotations.from(constructor).isPresent(Autowired.class)) {
return constructor;
}
}
Function<Constructor<?>, List<ResolvableType>> parameterTypesFactory = executable -> {
List<ResolvableType> types = new ArrayList<>();
for (int i = 0; i < executable.getParameterCount(); i++) {
types.add(ResolvableType.forConstructorParameter(executable, i));
}
return types;
};
List<? extends Executable> matches = Arrays.stream(constructors)
.filter(executable -> match(parameterTypesFactory.apply(executable),
valueTypes, FallbackMode.NONE)).toList();
if (matches.size() == 1) {
return matches.get(0);
}
List<? extends Executable> assignableElementFallbackMatches = Arrays.stream(constructors)
.filter(executable -> match(parameterTypesFactory.apply(executable),
valueTypes, FallbackMode.ASSIGNABLE_ELEMENT)).toList();
if (assignableElementFallbackMatches.size() == 1) {
return assignableElementFallbackMatches.get(0);
}
List<? extends Executable> typeConversionFallbackMatches = Arrays.stream(constructors)
.filter(executable -> match(parameterTypesFactory.apply(executable),
valueTypes, ExecutableProvider.FallbackMode.TYPE_CONVERSION)).toList();
return (typeConversionFallbackMatches.size() == 1) ? typeConversionFallbackMatches.get(0) : null;
}
private Executable resolveFactoryMethod(List<Method> executables,
Function<Method, List<ResolvableType>> parameterTypesFactory, List<ResolvableType> valueTypes) {
List<? extends Executable> matches = executables.stream()
.filter(executable -> match(parameterTypesFactory.apply(executable),
valueTypes, ExecutableProvider.FallbackMode.NONE)).toList();
if (matches.size() == 1) {
return matches.get(0);
}
List<? extends Executable> assignableElementFallbackMatches = executables.stream()
.filter(executable -> match(parameterTypesFactory.apply(executable),
valueTypes, ExecutableProvider.FallbackMode.ASSIGNABLE_ELEMENT)).toList();
if (assignableElementFallbackMatches.size() == 1) {
return assignableElementFallbackMatches.get(0);
}
List<? extends Executable> typeConversionFallbackMatches = executables.stream()
.filter(executable -> match(parameterTypesFactory.apply(executable),
valueTypes, ExecutableProvider.FallbackMode.TYPE_CONVERSION)).toList();
if (typeConversionFallbackMatches.size() > 1) {
throw new IllegalStateException("Multiple matches with parameters '"
+ valueTypes + "': " + typeConversionFallbackMatches);
}
return (typeConversionFallbackMatches.size() == 1) ? typeConversionFallbackMatches.get(0) : null;
}
private boolean match(List<ResolvableType> parameterTypes, List<ResolvableType> valueTypes,
ExecutableProvider.FallbackMode fallbackMode) {
if (parameterTypes.size() != valueTypes.size()) {
return false;
}
for (int i = 0; i < parameterTypes.size(); i++) {
if (!isMatch(parameterTypes.get(i), valueTypes.get(i), fallbackMode)) {
return false;
}
}
return true;
}
private boolean isMatch(ResolvableType parameterType, ResolvableType valueType,
ExecutableProvider.FallbackMode fallbackMode) {
if (isAssignable(valueType).test(parameterType)) {
return true;
}
return switch (fallbackMode) {
case ASSIGNABLE_ELEMENT -> isAssignable(valueType).test(extractElementType(parameterType));
case TYPE_CONVERSION -> typeConversionFallback(valueType).test(parameterType);
default -> false;
};
}
private Predicate<ResolvableType> isAssignable(ResolvableType valueType) {
return parameterType -> {
if (valueType.hasUnresolvableGenerics()) {
return parameterType.toClass().isAssignableFrom(valueType.toClass());
}
else {
return parameterType.isAssignableFrom(valueType);
}
};
}
private ResolvableType extractElementType(ResolvableType parameterType) {
if (parameterType.isArray()) {
return parameterType.getComponentType();
}
if (Collection.class.isAssignableFrom(parameterType.toClass())) {
return parameterType.as(Collection.class).getGeneric(0);
}
return ResolvableType.NONE;
}
private Predicate<ResolvableType> typeConversionFallback(ResolvableType valueType) {
return parameterType -> {
if (valueOrCollection(valueType, this::isStringForClassFallback).test(parameterType)) {
return true;
}
return valueOrCollection(valueType, this::isSimpleConvertibleType).test(parameterType);
};
}
private Predicate<ResolvableType> valueOrCollection(ResolvableType valueType,
Function<ResolvableType, Predicate<ResolvableType>> predicateProvider) {
return parameterType -> {
if (predicateProvider.apply(valueType).test(parameterType)) {
return true;
}
if (predicateProvider.apply(extractElementType(valueType)).test(extractElementType(parameterType))) {
return true;
}
return (predicateProvider.apply(valueType).test(extractElementType(parameterType)));
};
}
/**
* Return a {@link Predicate} for a parameter type that checks if its target value
* is a {@link Class} and the value type is a {@link String}. This is a regular use
* cases where a {@link Class} is defined in the bean definition as an FQN.
* @param valueType the type of the value
* @return a predicate to indicate a fallback match for a String to Class parameter
*/
private Predicate<ResolvableType> isStringForClassFallback(ResolvableType valueType) {
return parameterType -> (valueType.isAssignableFrom(String.class)
&& parameterType.isAssignableFrom(Class.class));
}
private Predicate<ResolvableType> isSimpleConvertibleType(ResolvableType valueType) {
return parameterType -> isSimpleConvertibleType(parameterType.toClass())
&& isSimpleConvertibleType(valueType.toClass());
}
@Nullable
private Class<?> getFactoryBeanClass(BeanDefinition beanDefinition) {
if (beanDefinition instanceof RootBeanDefinition rbd) {
if (rbd.hasBeanClass()) {
Class<?> beanClass = rbd.getBeanClass();
return FactoryBean.class.isAssignableFrom(beanClass) ? beanClass : null;
}
}
return null;
}
@Nullable
private Class<?> getBeanClass(BeanDefinition beanDefinition) {
if (beanDefinition instanceof AbstractBeanDefinition abd) {
return abd.hasBeanClass() ? abd.getBeanClass() : loadClass(abd.getBeanClassName());
}
return (beanDefinition.getBeanClassName() != null) ? loadClass(beanDefinition.getBeanClassName()) : null;
}
private ResolvableType getBeanType(BeanDefinition beanDefinition) {
ResolvableType resolvableType = beanDefinition.getResolvableType();
if (resolvableType != ResolvableType.NONE) {
return resolvableType;
}
if (beanDefinition instanceof RootBeanDefinition rbd) {
if (rbd.hasBeanClass()) {
return ResolvableType.forClass(rbd.getBeanClass());
}
}
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null) {
return ResolvableType.forClass(loadClass(beanClassName));
}
throw new IllegalStateException("Failed to determine bean class of " + beanDefinition);
}
private Class<?> loadClass(String beanClassName) {
try {
return ClassUtils.forName(beanClassName, this.classLoader);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException("Failed to load class " + beanClassName);
}
}
@Nullable
private <T> T getField(BeanDefinition beanDefinition, String fieldName, Class<T> targetType) {
Field field = ReflectionUtils.findField(RootBeanDefinition.class, fieldName);
ReflectionUtils.makeAccessible(field);
return targetType.cast(ReflectionUtils.getField(field, beanDefinition));
}
public static boolean isSimpleConvertibleType(Class<?> type) {
return (type.isPrimitive() && type != void.class) ||
type == Double.class || type == Float.class || type == Long.class ||
type == Integer.class || type == Short.class || type == Character.class ||
type == Byte.class || type == Boolean.class || type == String.class;
}
enum FallbackMode {
NONE,
ASSIGNABLE_ELEMENT,
TYPE_CONVERSION
}
}
}

View File

@@ -1,255 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.aot.generator.ProtectedAccess;
import org.springframework.aot.generator.ProtectedAccess.Options;
import org.springframework.beans.factory.generator.config.BeanDefinitionRegistrar.BeanInstanceContext;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.CodeBlock.Builder;
import org.springframework.util.ClassUtils;
/**
* Generate the necessary code to {@link #generateInstantiation(Executable)
* create a bean instance} or {@link #generateInjection(Member, boolean)
* inject dependencies}.
* <p/>
* The generator assumes a number of variables to be accessible:
* <ul>
* <li>{@code beanFactory}: the general {@code DefaultListableBeanFactory}</li>
* <li>{@code instanceContext}: the {@link BeanInstanceContext} callback</li>
* <li>{@code bean}: the variable that refers to the bean instance</li>
* </ul>
*
* @author Stephane Nicoll
* @author Brian Clozel
*/
public class InjectionGenerator {
private static final Options METHOD_INJECTION_OPTIONS = Options.defaults()
.useReflection(member -> false).build();
private final BeanParameterGenerator parameterGenerator = new BeanParameterGenerator();
private final BeanFieldGenerator fieldGenerator = new BeanFieldGenerator();
/**
* Generate the necessary code to instantiate an object using the specified
* {@link Executable}. The code is suitable to be assigned to a variable
* or used as a {@literal return} statement.
* @param creator the executable to invoke to create an instance of the
* requested object
* @return the code to instantiate an object using the specified executable
*/
public CodeBlock generateInstantiation(Executable creator) {
if (creator instanceof Constructor<?> constructor) {
return generateConstructorInstantiation(constructor);
}
if (creator instanceof Method method) {
return generateMethodInstantiation(method);
}
throw new IllegalArgumentException("Could not handle creator " + creator);
}
/**
* Generate the code to inject a value resolved by {@link BeanInstanceContext}
* in the specified {@link Member}.
* @param member the field or method to inject
* @param required whether the value is required
* @return a statement that injects a value to the specified member
* @see #getProtectedAccessInjectionOptions(Member)
*/
public CodeBlock generateInjection(Member member, boolean required) {
if (member instanceof Method method) {
return generateMethodInjection(method, required);
}
if (member instanceof Field field) {
return generateFieldInjection(field, required);
}
throw new IllegalArgumentException("Could not handle member " + member);
}
/**
* Return the {@link Options} to use if protected access analysis is
* required for the specified {@link Member}.
* @param member the field or method to handle
* @return the options to use to analyse protected access
* @see ProtectedAccess
*/
public Options getProtectedAccessInjectionOptions(Member member) {
if (member instanceof Method) {
return METHOD_INJECTION_OPTIONS;
}
if (member instanceof Field) {
return BeanFieldGenerator.FIELD_OPTIONS;
}
throw new IllegalArgumentException("Could not handle member " + member);
}
private CodeBlock generateConstructorInstantiation(Constructor<?> creator) {
Builder code = CodeBlock.builder();
Class<?> declaringType = ClassUtils.getUserClass(creator.getDeclaringClass());
boolean innerClass = isInnerClass(declaringType);
Class<?>[] parameterTypes = Arrays.stream(creator.getParameters()).map(Parameter::getType)
.toArray(Class<?>[]::new);
// Shortcut for common case
if (innerClass && parameterTypes.length == 1) {
code.add("beanFactory.getBean($T.class).new $L()", declaringType.getEnclosingClass(),
declaringType.getSimpleName());
return code.build();
}
if (parameterTypes.length == 0) {
code.add("new $T()", declaringType);
return code.build();
}
boolean isAmbiguous = Arrays.stream(creator.getDeclaringClass().getDeclaredConstructors())
.filter(constructor -> constructor.getParameterCount() == parameterTypes.length).count() > 1;
code.add("instanceContext.create(beanFactory, (attributes) ->");
List<CodeBlock> parameters = resolveParameters(creator.getParameters(), isAmbiguous);
if (innerClass) { // Remove the implicit argument
parameters.remove(0);
}
code.add(" ");
if (innerClass) {
code.add("beanFactory.getBean($T.class).new $L(", declaringType.getEnclosingClass(),
declaringType.getSimpleName());
}
else {
code.add("new $T(", declaringType);
}
for (int i = 0; i < parameters.size(); i++) {
code.add(parameters.get(i));
if (i < parameters.size() - 1) {
code.add(", ");
}
}
code.add(")");
code.add(")");
return code.build();
}
private static boolean isInnerClass(Class<?> type) {
return type.isMemberClass() && !Modifier.isStatic(type.getModifiers());
}
private CodeBlock generateMethodInstantiation(Method injectionPoint) {
if (injectionPoint.getParameterCount() == 0) {
Builder code = CodeBlock.builder();
Class<?> declaringType = injectionPoint.getDeclaringClass();
if (Modifier.isStatic(injectionPoint.getModifiers())) {
code.add("$T", declaringType);
}
else {
code.add("beanFactory.getBean($T.class)", declaringType);
}
code.add(".$L()", injectionPoint.getName());
return code.build();
}
return generateMethodInvocation(injectionPoint, code -> code.add(".create(beanFactory, (attributes) ->"), true);
}
private CodeBlock generateMethodInjection(Method injectionPoint, boolean required) {
Consumer<Builder> attributesResolver = code -> {
if (required) {
code.add(".invoke(beanFactory, (attributes) ->");
}
else {
code.add(".resolve(beanFactory, false).ifResolved((attributes) ->");
}
};
return generateMethodInvocation(injectionPoint, attributesResolver, false);
}
private CodeBlock generateMethodInvocation(Method injectionPoint, Consumer<Builder> attributesResolver, boolean instantiation) {
Builder code = CodeBlock.builder();
code.add("instanceContext");
if (!instantiation) {
code.add(".method($S, ", injectionPoint.getName());
code.add(this.parameterGenerator.generateExecutableParameterTypes(injectionPoint));
code.add(")\n").indent().indent();
}
attributesResolver.accept(code);
Parameter[] methodParameters = injectionPoint.getParameters();
boolean isAmbiguous = Arrays.stream(injectionPoint.getDeclaringClass().getDeclaredMethods())
.filter(method -> method.getName().equals(injectionPoint.getName()) && method.getParameterCount() == methodParameters.length).count() > 1;
List<CodeBlock> parameters = resolveParameters(methodParameters, isAmbiguous);
code.add(" ");
if (instantiation) {
if (Modifier.isStatic(injectionPoint.getModifiers())) {
code.add("$T", injectionPoint.getDeclaringClass());
}
else {
code.add("beanFactory.getBean($T.class)", injectionPoint.getDeclaringClass());
}
}
else {
code.add("bean");
}
code.add(".$L(", injectionPoint.getName());
code.add(CodeBlock.join(parameters, ", "));
code.add(")");
code.add(")");
if (!instantiation) {
code.unindent().unindent();
}
return code.build();
}
CodeBlock generateFieldInjection(Field injectionPoint, boolean required) {
Builder code = CodeBlock.builder();
code.add("instanceContext.field($S", injectionPoint.getName());
code.add(")\n").indent().indent();
if (required) {
code.add(".invoke(beanFactory, ");
}
else {
code.add(".resolve(beanFactory, false).ifResolved(");
}
code.add(this.fieldGenerator.generateSetValue("bean", injectionPoint,
CodeBlock.of("attributes.get(0)")).toLambda("(attributes) ->"));
code.add(")").unindent().unindent();
return code.build();
}
private List<CodeBlock> resolveParameters(Parameter[] parameters, boolean shouldCast) {
List<CodeBlock> parameterValues = new ArrayList<>();
for (int i = 0; i < parameters.length; i++) {
if (shouldCast) {
parameterValues.add(CodeBlock.of("attributes.get($L, $T.class)", i, parameters[i].getType()));
}
else {
parameterValues.add(CodeBlock.of("attributes.get($L)", i));
}
}
return parameterValues;
}
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator;
import org.springframework.beans.factory.generator.config.BeanDefinitionRegistrar;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.javapoet.CodeBlock;
/**
* A specialization of {@link BeanRegistrationContributionProvider} that handles
* inner bean definitions.
*
* @author Stephane Nicoll
*/
class InnerBeanRegistrationBeanFactoryContribution extends BeanRegistrationBeanFactoryContribution {
InnerBeanRegistrationBeanFactoryContribution(String beanName, RootBeanDefinition beanDefinition,
BeanInstantiationGenerator beanInstantiationGenerator,
DefaultBeanRegistrationContributionProvider innerBeanRegistrationContributionProvider) {
super(beanName, beanDefinition, beanInstantiationGenerator, innerBeanRegistrationContributionProvider);
}
@Override
protected CodeBlock initializeBeanDefinitionRegistrar() {
return CodeBlock.of("$T.inner(", BeanDefinitionRegistrar.class);
}
}

View File

@@ -1,401 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator.config;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.ResolvableType;
import org.springframework.core.log.LogMessage;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
/**
* {@link BeanDefinition} registration mechanism offering transparent
* dependency resolution, as well as exception management.
*
* <p>Used by code generators and for internal use within the framework
* only.
*
* @author Stephane Nicoll
* @since 6.0
*/
public final class BeanDefinitionRegistrar {
private static final Log logger = LogFactory.getLog(BeanDefinitionRegistrar.class);
@Nullable
private final String beanName;
private final Class<?> beanClass;
@Nullable
private final ResolvableType beanType;
private final List<Consumer<RootBeanDefinition>> customizers;
@Nullable
private Executable instanceCreator;
@Nullable
private RootBeanDefinition beanDefinition;
private BeanDefinitionRegistrar(@Nullable String beanName, Class<?> beanClass, @Nullable ResolvableType beanType) {
this.beanName = beanName;
this.beanClass = beanClass;
this.beanType = beanType;
this.customizers = new ArrayList<>();
}
/**
* Initialize the registration of a bean with the specified name and type.
* @param beanName the name of the bean
* @param beanType the type of the bean
* @return a registrar for the specified bean
*/
public static BeanDefinitionRegistrar of(String beanName, ResolvableType beanType) {
return new BeanDefinitionRegistrar(beanName, beanType.toClass(), beanType);
}
/**
* Initialize the registration of a bean with the specified name and type.
* @param beanName the name of the bean
* @param beanType the type of the bean
* @return a registrar for the specified bean
*/
public static BeanDefinitionRegistrar of(String beanName, Class<?> beanType) {
return new BeanDefinitionRegistrar(beanName, beanType, null);
}
/**
* Initialize the registration of an inner bean with the specified type.
* @param beanType the type of the inner bean
* @return a registrar for the specified inner bean
*/
public static BeanDefinitionRegistrar inner(ResolvableType beanType) {
return new BeanDefinitionRegistrar(null, beanType.toClass(), beanType);
}
/**
* Initialize the registration of an inner bean with the specified type.
* @param beanType the type of the inner bean
* @return a registrar for the specified inner bean
*/
public static BeanDefinitionRegistrar inner(Class<?> beanType) {
return new BeanDefinitionRegistrar(null, beanType, null);
}
/**
* Specify the factory method to use to instantiate the bean.
* @param declaredType the {@link Method#getDeclaringClass() declared type}
* of the factory method.
* @param name the name of the method
* @param parameterTypes the parameter types of the method
* @return {@code this}, to facilitate method chaining
* @see RootBeanDefinition#getResolvedFactoryMethod()
*/
public BeanDefinitionRegistrar withFactoryMethod(Class<?> declaredType, String name, Class<?>... parameterTypes) {
this.instanceCreator = getMethod(declaredType, name, parameterTypes);
return this;
}
/**
* Specify the constructor to use to instantiate the bean.
* @param parameterTypes the parameter types of the constructor
* @return {@code this}, to facilitate method chaining
*/
public BeanDefinitionRegistrar withConstructor(Class<?>... parameterTypes) {
this.instanceCreator = getConstructor(this.beanClass, parameterTypes);
return this;
}
/**
* Specify how the bean instance should be created and initialized, using
* the {@link BeanInstanceContext} to resolve dependencies if necessary.
* @param instanceContext the {@link BeanInstanceContext} to use
* @return {@code this}, to facilitate method chaining
*/
public BeanDefinitionRegistrar instanceSupplier(ThrowableFunction<BeanInstanceContext, ?> instanceContext) {
return customize(beanDefinition -> beanDefinition.setInstanceSupplier(() ->
instanceContext.apply(createBeanInstanceContext())));
}
/**
* Specify how the bean instance should be created and initialized.
* @return {@code this}, to facilitate method chaining
*/
public BeanDefinitionRegistrar instanceSupplier(ThrowableSupplier<?> instanceSupplier) {
return customize(beanDefinition -> beanDefinition.setInstanceSupplier(instanceSupplier));
}
/**
* Customize the {@link RootBeanDefinition} using the specified consumer.
* @param bd a consumer for the bean definition
* @return {@code this}, to facilitate method chaining
*/
public BeanDefinitionRegistrar customize(ThrowableConsumer<RootBeanDefinition> bd) {
this.customizers.add(bd);
return this;
}
/**
* Register the {@link RootBeanDefinition} defined by this instance to
* the specified bean factory.
* @param beanFactory the bean factory to use
*/
public void register(DefaultListableBeanFactory beanFactory) {
BeanDefinition beanDefinition = toBeanDefinition();
Assert.state(this.beanName != null, () -> "Bean name not set. Could not register " + beanDefinition);
logger.debug(LogMessage.format("Register bean definition with name '%s'", this.beanName));
beanFactory.registerBeanDefinition(this.beanName, beanDefinition);
}
/**
* Return the {@link RootBeanDefinition} defined by this instance.
* @return the bean definition
*/
public RootBeanDefinition toBeanDefinition() {
try {
this.beanDefinition = createBeanDefinition();
return this.beanDefinition;
}
catch (Exception ex) {
throw new FatalBeanException("Failed to create bean definition for bean with name '" + this.beanName + "'", ex);
}
}
private RootBeanDefinition createBeanDefinition() {
RootBeanDefinition bd = (RootBeanDefinition) BeanDefinitionBuilder
.rootBeanDefinition(this.beanClass).getBeanDefinition();
if (this.beanType != null) {
bd.setTargetType(this.beanType);
}
if (this.instanceCreator instanceof Method) {
bd.setResolvedFactoryMethod((Method) this.instanceCreator);
}
this.customizers.forEach(customizer -> customizer.accept(bd));
return bd;
}
private BeanInstanceContext createBeanInstanceContext() {
String resolvedBeanName = this.beanName != null ? this.beanName : createInnerBeanName();
return new BeanInstanceContext(resolvedBeanName, this.beanClass);
}
private String createInnerBeanName() {
return "(inner bean)" + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR +
(this.beanDefinition != null ? ObjectUtils.getIdentityHexString(this.beanDefinition) : 0);
}
@Nullable
private BeanDefinition resolveBeanDefinition(DefaultListableBeanFactory beanFactory) {
return this.beanDefinition;
}
private static Constructor<?> getConstructor(Class<?> beanType, Class<?>... parameterTypes) {
try {
return beanType.getDeclaredConstructor(parameterTypes);
}
catch (NoSuchMethodException ex) {
String message = String.format("No constructor with type(s) [%s] found on %s",
toCommaSeparatedNames(parameterTypes), beanType.getName());
throw new IllegalArgumentException(message, ex);
}
}
private static Method getMethod(Class<?> declaredType, String methodName, Class<?>... parameterTypes) {
Method method = ReflectionUtils.findMethod(declaredType, methodName, parameterTypes);
if (method == null) {
String message = String.format("No method '%s' with type(s) [%s] found on %s", methodName,
toCommaSeparatedNames(parameterTypes), declaredType.getName());
throw new IllegalArgumentException(message);
}
return MethodIntrospector.selectInvocableMethod(method, declaredType);
}
private static String toCommaSeparatedNames(Class<?>... parameterTypes) {
return Arrays.stream(parameterTypes).map(Class::getName).collect(Collectors.joining(", "));
}
/**
* Callback interface used by instance suppliers that need to resolve
* dependencies for the {@link Executable} used to create the instance
* as well as any {@link Member} that should be handled by the context.
*/
public final class BeanInstanceContext {
private final String beanName;
private final Class<?> beanType;
private BeanInstanceContext(String beanName, Class<?> beanType) {
this.beanName = beanName;
this.beanType = beanType;
}
/**
* Return the bean instance using the {@code factory}.
* @param beanFactory the bean factory to use
* @param factory a function that returns the bean instance based on
* the resolved attributes required by its instance creator
* @param <T> the type of the bean
* @return the bean instance
*/
public <T> T create(DefaultListableBeanFactory beanFactory, ThrowableFunction<InjectedElementAttributes, T> factory) {
return resolveInstanceCreator(BeanDefinitionRegistrar.this.instanceCreator).create(beanFactory, factory);
}
private InjectedElementResolver resolveInstanceCreator(@Nullable Executable instanceCreator) {
if (instanceCreator instanceof Method) {
return new InjectedConstructionResolver(instanceCreator, instanceCreator.getDeclaringClass(), this.beanName,
BeanDefinitionRegistrar.this::resolveBeanDefinition);
}
if (instanceCreator instanceof Constructor) {
return new InjectedConstructionResolver(instanceCreator, this.beanType, this.beanName,
BeanDefinitionRegistrar.this::resolveBeanDefinition);
}
throw new IllegalStateException("No factory method or constructor is set");
}
/**
* Create an {@link InjectedElementResolver} for the specified field.
* @param name the name of the field
* @return a resolved for the specified field
*/
public InjectedElementResolver field(String name) {
return new InjectedFieldResolver(getField(name), this.beanName);
}
/**
* Create an {@link InjectedElementResolver} for the specified bean method.
* @param name the name of the method on the target bean
* @param parameterTypes the method parameter types
* @return a resolved for the specified bean method
*/
public InjectedElementResolver method(String name, Class<?>... parameterTypes) {
return new InjectedMethodResolver(getMethod(this.beanType, name, parameterTypes), this.beanType, this.beanName);
}
private Field getField(String fieldName) {
Field field = ReflectionUtils.findField(this.beanType, fieldName);
Assert.notNull(field, () -> "No field '" + fieldName + "' found on " + this.beanType.getName());
return field;
}
}
/**
* A {@link Consumer} that allows to invoke code that throws a checked exception.
*
* @author Stephane Nicoll
* @param <T> the type of the input to the operation
*/
@FunctionalInterface
public interface ThrowableConsumer<T> extends Consumer<T> {
void acceptWithException(T t) throws Exception;
@Override
default void accept(T t) {
try {
acceptWithException(t);
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw new RuntimeException(ex.getMessage(), ex);
}
}
}
/**
* A {@link Function} that allows to invoke code that throws a checked exception.
*
* @author Stephane Nicoll
* @param <T> the type of the input to the function
* @param <R> the type of the result of the function
*/
@FunctionalInterface
public interface ThrowableFunction<T, R> extends Function<T, R> {
R applyWithException(T t) throws Exception;
@Override
default R apply(T t) {
try {
return applyWithException(t);
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw new RuntimeException(ex.getMessage(), ex);
}
}
}
/**
* A {@link Supplier} that allows to invoke code that throws a checked exception.
*
* @author Stephane Nicoll
* @param <T> the type of results supplied by this supplier
*/
public interface ThrowableSupplier<T> extends Supplier<T> {
T getWithException() throws Exception;
@Override
default T get() {
try {
return getWithException();
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
throw new RuntimeException(ex.getMessage(), ex);
}
}
}
}

View File

@@ -1,175 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator.config;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.Function;
import java.util.function.Supplier;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.BeanDefinitionValueResolver;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.CollectionFactory;
import org.springframework.core.MethodParameter;
/**
* An {@link InjectedElementResolver} for an {@link Executable} that creates
* a bean instance.
*
* @author Stephane Nicoll
*/
class InjectedConstructionResolver implements InjectedElementResolver {
private final Executable executable;
private final Class<?> targetType;
private final String beanName;
private final Function<DefaultListableBeanFactory, BeanDefinition> beanDefinitionResolver;
InjectedConstructionResolver(Executable executable, Class<?> targetType, String beanName,
Function<DefaultListableBeanFactory, BeanDefinition> beanDefinitionResolver) {
this.executable = executable;
this.targetType = targetType;
this.beanName = beanName;
this.beanDefinitionResolver = beanDefinitionResolver;
}
Executable getExecutable() {
return this.executable;
}
@Override
public InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory, boolean required) {
int argumentCount = this.executable.getParameterCount();
List<Object> arguments = new ArrayList<>();
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
TypeConverter typeConverter = beanFactory.getTypeConverter();
ConstructorArgumentValues argumentValues = resolveArgumentValues(beanFactory);
for (int i = 0; i < argumentCount; i++) {
MethodParameter methodParam = createMethodParameter(i);
ValueHolder valueHolder = argumentValues.getIndexedArgumentValue(i, null);
if (valueHolder != null) {
if (valueHolder.isConverted()) {
arguments.add(valueHolder.getConvertedValue());
}
else {
Object userValue = beanFactory.getTypeConverter()
.convertIfNecessary(valueHolder.getValue(), methodParam.getParameterType());
arguments.add(userValue);
}
}
else {
DependencyDescriptor depDescriptor = new DependencyDescriptor(methodParam, true);
depDescriptor.setContainingClass(this.targetType);
try {
Object arg = resolveDependency(() -> beanFactory.resolveDependency(
depDescriptor, this.beanName, autowiredBeans, typeConverter), methodParam.getParameterType());
arguments.add(arg);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, this.beanName, new InjectionPoint(methodParam), ex);
}
}
}
return new InjectedElementAttributes(arguments);
}
private Object resolveDependency(Supplier<Object> resolvedDependency, Class<?> dependencyType) {
try {
return resolvedDependency.get();
}
catch (NoSuchBeanDefinitionException ex) {
// Single constructor or factory method -> let's return an empty array/collection
// for e.g. a vararg or a non-null List/Set/Map parameter.
if (dependencyType.isArray()) {
return Array.newInstance(dependencyType.getComponentType(), 0);
}
else if (CollectionFactory.isApproximableCollectionType(dependencyType)) {
return CollectionFactory.createCollection(dependencyType, 0);
}
else if (CollectionFactory.isApproximableMapType(dependencyType)) {
return CollectionFactory.createMap(dependencyType, 0);
}
throw ex;
}
}
private ConstructorArgumentValues resolveArgumentValues(DefaultListableBeanFactory beanFactory) {
ConstructorArgumentValues resolvedValues = new ConstructorArgumentValues();
BeanDefinition beanDefinition = this.beanDefinitionResolver.apply(beanFactory);
if (beanDefinition == null || !beanDefinition.hasConstructorArgumentValues()) {
return resolvedValues;
}
ConstructorArgumentValues argumentValues = beanDefinition.getConstructorArgumentValues();
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(beanFactory,
this.beanName, beanDefinition);
for (Map.Entry<Integer, ValueHolder> entry : argumentValues.getIndexedArgumentValues().entrySet()) {
int index = entry.getKey();
ValueHolder valueHolder = entry.getValue();
if (valueHolder.isConverted()) {
resolvedValues.addIndexedArgumentValue(index, valueHolder);
}
else {
Object resolvedValue =
valueResolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue());
ValueHolder resolvedValueHolder =
new ValueHolder(resolvedValue, valueHolder.getType(), valueHolder.getName());
resolvedValueHolder.setSource(valueHolder);
resolvedValues.addIndexedArgumentValue(index, resolvedValueHolder);
}
}
return resolvedValues;
}
private MethodParameter createMethodParameter(int index) {
if (this.executable instanceof Constructor) {
return new MethodParameter((Constructor<?>) this.executable, index);
}
else {
return new MethodParameter((Method) this.executable, index);
}
}
@Override
public String toString() {
return new StringJoiner(", ", InjectedConstructionResolver.class.getSimpleName() + "[", "]")
.add("executable=" + this.executable)
.toString();
}
}

View File

@@ -1,93 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator.config;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Resolved attributes of an injected element.
*
* @author Stephane Nicoll
* @since 6.0
*/
public class InjectedElementAttributes {
@Nullable
private final List<Object> attributes;
InjectedElementAttributes(@Nullable List<Object> attributes) {
this.attributes = attributes;
}
/**
* Specify if the attributes have been resolved.
* @return the resolution of the injection
*/
public boolean isResolved() {
return (this.attributes != null);
}
/**
* Run the specified {@linkplain Runnable task} only if this instance is
* {@link #isResolved() resolved}.
* @param task the task to invoke if attributes are available
*/
public void ifResolved(Runnable task) {
if (isResolved()) {
task.run();
}
}
/**
* Invoke the specified {@link Consumer} with the resolved attributes.
* @param attributes the consumer to invoke if this instance is resolved
*/
public void ifResolved(BeanDefinitionRegistrar.ThrowableConsumer<InjectedElementAttributes> attributes) {
ifResolved(() -> attributes.accept(this));
}
/**
* Return the resolved attribute at the specified index.
* @param index the attribute index
* @param <T> the type of the attribute
* @return the attribute
*/
@SuppressWarnings("unchecked")
public <T> T get(int index) {
Assert.notNull(this.attributes, "Attributes must not be null");
return (T) this.attributes.get(index);
}
/**
* Return the resolved attribute at the specified index.
* @param index the attribute index
* @param type the attribute type
* @param <T> the type of the attribute
* @return the attribute
*/
@SuppressWarnings("unchecked")
public <T> T get(int index, Class<T> type) {
Assert.notNull(this.attributes, "Attributes must not be null");
return (T) this.attributes.get(index);
}
}

View File

@@ -1,75 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator.config;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
/**
* Resolve the attributes of an injected element such as a {@code Constructor}
* or a factory {@code Method}.
*
* @author Stephane Nicoll
* @since 6.0
*/
public interface InjectedElementResolver {
/**
* Resolve the attributes using the specified bean factory.
* @param beanFactory the bean factory to use
* @return the resolved attributes
*/
default InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory) {
return resolve(beanFactory, true);
}
/**
* Resolve the attributes using the specified bean factory.
* @param beanFactory the bean factory to use
* @param required whether the injection point is mandatory
* @return the resolved attributes
*/
InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory, boolean required);
/**
* Invoke the specified consumer with the resolved
* {@link InjectedElementAttributes attributes}.
* @param beanFactory the bean factory to use to resolve the attributes
* @param attributes a consumer of the resolved attributes
*/
default void invoke(DefaultListableBeanFactory beanFactory,
BeanDefinitionRegistrar.ThrowableConsumer<InjectedElementAttributes> attributes) {
InjectedElementAttributes elements = resolve(beanFactory);
attributes.accept(elements);
}
/**
* Create an instance based on the resolved
* {@link InjectedElementAttributes attributes}.
* @param beanFactory the bean factory to use to resolve the attributes
* @param factory a factory to create the instance based on the resolved attributes
* @param <T> the type of the instance
* @return a new instance
*/
default <T> T create(DefaultListableBeanFactory beanFactory,
BeanDefinitionRegistrar.ThrowableFunction<InjectedElementAttributes, T> factory) {
InjectedElementAttributes attributes = resolve(beanFactory);
return factory.apply(attributes);
}
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator.config;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
/**
* An {@link InjectedElementResolver} for a {@link Field}.
*
* @author Stephane Nicoll
*/
class InjectedFieldResolver implements InjectedElementResolver {
private final Field field;
private final String beanName;
/**
* Create a new instance.
* @param field the field to handle
* @param beanName the name of the bean, or {@code null}
*/
InjectedFieldResolver(Field field, String beanName) {
this.field = field;
this.beanName = beanName;
}
@Override
public InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory, boolean required) {
DependencyDescriptor desc = new DependencyDescriptor(this.field, required);
desc.setContainingClass(this.field.getType());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
TypeConverter typeConverter = beanFactory.getTypeConverter();
try {
Object value = beanFactory.resolveDependency(desc, this.beanName, autowiredBeanNames, typeConverter);
if (value == null && !required) {
return new InjectedElementAttributes(null);
}
return new InjectedElementAttributes(Collections.singletonList(value));
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, this.beanName, new InjectionPoint(this.field), ex);
}
}
}

View File

@@ -1,86 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.generator.config;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.MethodParameter;
/**
* An {@link InjectedElementResolver} for a {@link Method}.
*
* @author Stephane Nicoll
*/
class InjectedMethodResolver implements InjectedElementResolver {
private final Method method;
private final Class<?> target;
private final String beanName;
/**
* Create a new instance.
* @param method the method to handle
* @param target the type on which the method is declared
* @param beanName the name of the bean, or {@code null}
*/
InjectedMethodResolver(Method method, Class<?> target, String beanName) {
this.method = method;
this.target = target;
this.beanName = beanName;
}
@Override
public InjectedElementAttributes resolve(DefaultListableBeanFactory beanFactory, boolean required) {
int argumentCount = this.method.getParameterCount();
List<Object> arguments = new ArrayList<>();
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
TypeConverter typeConverter = beanFactory.getTypeConverter();
for (int i = 0; i < argumentCount; i++) {
MethodParameter methodParam = new MethodParameter(this.method, i);
DependencyDescriptor depDescriptor = new DependencyDescriptor(methodParam, required);
depDescriptor.setContainingClass(this.target);
try {
Object arg = beanFactory.resolveDependency(depDescriptor, this.beanName, autowiredBeans, typeConverter);
if (arg == null && !required) {
arguments = null;
break;
}
arguments.add(arg);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, this.beanName, new InjectionPoint(methodParam), ex);
}
}
return new InjectedElementAttributes(arguments);
}
}

View File

@@ -1,9 +0,0 @@
/**
* Classes used in generated code to ease bean registration.
*/
@NonNullApi
@NonNullFields
package org.springframework.beans.factory.generator.config;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;

View File

@@ -1,9 +0,0 @@
/**
* Support for generating code that represents the state of a bean factory.
*/
@NonNullApi
@NonNullFields
package org.springframework.beans.factory.generator;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;