Skip to content

Commit

Permalink
Make constructorOrFactory method resolution optional
Browse files Browse the repository at this point in the history
This commit allows a custom code fragment to provide the code to
create a bean without relying on ConstructorResolver. This is especially
important for use cases that derive from the default behaviour and
provide an instance supplier with the regular runtime scenario.

This is a breaking change for code fragments providing a custom
implementation of the related methods. As it turns out, almost all of
them did not need the Executable argument. Configuration class parsing
is the exception, where it needs to provide a different constructor in
the case of the proxy. To make this use case possible,
InstanceSupplierCodeGenerator has been made public.

Closes gh-31117
  • Loading branch information
snicoll committed Sep 11, 2023
1 parent fceed9f commit 66a571f
Show file tree
Hide file tree
Showing 10 changed files with 260 additions and 163 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package org.springframework.aop.scope;

import java.lang.reflect.Executable;
import java.util.function.Predicate;

import javax.lang.model.element.Modifier;
Expand Down Expand Up @@ -109,7 +108,7 @@ private static class ScopedProxyBeanRegistrationCodeFragments extends BeanRegist
}

@Override
public ClassName getTarget(RegisteredBean registeredBean, Executable constructorOrFactoryMethod) {
public ClassName getTarget(RegisteredBean registeredBean) {
return ClassName.get(this.targetBeanDefinition.getResolvableType().toClass());
}

Expand Down Expand Up @@ -139,9 +138,7 @@ public CodeBlock generateSetBeanDefinitionPropertiesCode(

@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode,
Executable constructorOrFactoryMethod,
boolean allowDirectSupplierShortcut) {
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {

GeneratedMethod generatedMethod = beanRegistrationCode.getMethods()
.add("getScopedProxyInstance", method -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@

package org.springframework.beans.factory.aot;

import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

import javax.lang.model.element.Modifier;
Expand All @@ -29,14 +25,9 @@
import org.springframework.aot.generate.GeneratedMethods;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.AutowireCandidateResolver;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.MethodParameter;
import org.springframework.javapoet.ClassName;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
Expand All @@ -56,8 +47,6 @@ class BeanDefinitionMethodGenerator {

private final RegisteredBean registeredBean;

private final Executable constructorOrFactoryMethod;

@Nullable
private final String currentPropertyName;

Expand All @@ -83,7 +72,6 @@ class BeanDefinitionMethodGenerator {
}
this.methodGeneratorFactory = methodGeneratorFactory;
this.registeredBean = registeredBean;
this.constructorOrFactoryMethod = registeredBean.resolveConstructorOrFactoryMethod();
this.currentPropertyName = currentPropertyName;
this.aotContributions = aotContributions;
}
Expand All @@ -98,9 +86,8 @@ class BeanDefinitionMethodGenerator {
MethodReference generateBeanDefinitionMethod(GenerationContext generationContext,
BeanRegistrationsCode beanRegistrationsCode) {

registerRuntimeHintsIfNecessary(generationContext.getRuntimeHints());
BeanRegistrationCodeFragments codeFragments = getCodeFragments(generationContext, beanRegistrationsCode);
ClassName target = codeFragments.getTarget(this.registeredBean, this.constructorOrFactoryMethod);
ClassName target = codeFragments.getTarget(this.registeredBean);
if (isWritablePackageName(target)) {
GeneratedClass generatedClass = lookupGeneratedClass(generationContext, target);
GeneratedMethods generatedMethods = generatedClass.getMethods().withPrefix(getName());
Expand Down Expand Up @@ -178,8 +165,7 @@ private GeneratedMethod generateBeanDefinitionMethod(GenerationContext generatio
BeanRegistrationCodeFragments codeFragments, Modifier modifier) {

BeanRegistrationCodeGenerator codeGenerator = new BeanRegistrationCodeGenerator(
className, generatedMethods, this.registeredBean,
this.constructorOrFactoryMethod, codeFragments);
className, generatedMethods, this.registeredBean, codeFragments);

this.aotContributions.forEach(aotContribution -> aotContribution.applyTo(generationContext, codeGenerator));

Expand Down Expand Up @@ -218,52 +204,4 @@ private String getSimpleBeanName(String beanName) {
return StringUtils.uncapitalize(beanName);
}

private void registerRuntimeHintsIfNecessary(RuntimeHints runtimeHints) {
if (this.registeredBean.getBeanFactory() instanceof DefaultListableBeanFactory dlbf) {
ProxyRuntimeHintsRegistrar registrar = new ProxyRuntimeHintsRegistrar(dlbf.getAutowireCandidateResolver());
if (this.constructorOrFactoryMethod instanceof Method method) {
registrar.registerRuntimeHints(runtimeHints, method);
}
else if (this.constructorOrFactoryMethod instanceof Constructor<?> constructor) {
registrar.registerRuntimeHints(runtimeHints, constructor);
}
}
}


private static class ProxyRuntimeHintsRegistrar {

private final AutowireCandidateResolver candidateResolver;

public ProxyRuntimeHintsRegistrar(AutowireCandidateResolver candidateResolver) {
this.candidateResolver = candidateResolver;
}

public void registerRuntimeHints(RuntimeHints runtimeHints, Method method) {
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
MethodParameter methodParam = new MethodParameter(method, i);
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(methodParam, true);
registerProxyIfNecessary(runtimeHints, dependencyDescriptor);
}
}

public void registerRuntimeHints(RuntimeHints runtimeHints, Constructor<?> constructor) {
Class<?>[] parameterTypes = constructor.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
MethodParameter methodParam = new MethodParameter(constructor, i);
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(
methodParam, true);
registerProxyIfNecessary(runtimeHints, dependencyDescriptor);
}
}

private void registerProxyIfNecessary(RuntimeHints runtimeHints, DependencyDescriptor dependencyDescriptor) {
Class<?> proxyType = this.candidateResolver.getLazyResolutionProxyClass(dependencyDescriptor, null);
if (proxyType != null && Proxy.isProxyClass(proxyType)) {
runtimeHints.proxies().registerJdkProxy(proxyType.getInterfaces());
}
}
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
Expand All @@ -16,9 +16,9 @@

package org.springframework.beans.factory.aot;

import java.lang.reflect.Executable;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;

import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
Expand All @@ -31,9 +31,19 @@

/**
* Generate the various fragments of code needed to register a bean.
*
* <p>
* A default implementation is provided that suits most needs and custom code
* fragments are only expected to be used by library authors having built custom
* arrangement on top of the core container.
* <p>
* Users are not expected to implement this interface directly, but rather extends
* from {@link BeanRegistrationCodeFragmentsDecorator} and only override the
* necessary method(s).
* @author Phillip Webb
* @author Stephane Nicoll
* @since 6.0
* @see BeanRegistrationCodeFragmentsDecorator
* @see BeanRegistrationAotContribution#withCustomCodeFragments(UnaryOperator)
*/
public interface BeanRegistrationCodeFragments {

Expand All @@ -50,16 +60,19 @@ public interface BeanRegistrationCodeFragments {

/**
* Return the target for the registration. Used to determine where to write
* the code.
* the code. This should take into account visibility issue, such as
* package access of an element of the bean to register.
* @param registeredBean the registered bean
* @param constructorOrFactoryMethod the constructor or factory method
* @return the target {@link ClassName}
*/
ClassName getTarget(RegisteredBean registeredBean,
Executable constructorOrFactoryMethod);
ClassName getTarget(RegisteredBean registeredBean);

/**
* Generate the code that defines the new bean definition instance.
* <p>
* This should declare a variable named {@value BEAN_DEFINITION_VARIABLE}
* so that further fragments can refer to the variable to further tune
* the bean definition.
* @param generationContext the generation context
* @param beanType the bean type
* @param beanRegistrationCode the bean registration code
Expand All @@ -81,6 +94,11 @@ CodeBlock generateSetBeanDefinitionPropertiesCode(

/**
* Generate the code that sets the instance supplier on the bean definition.
* <p>
* The {@code postProcessors} represent methods to be exposed once the
* instance has been created to further configure it. Each method should
* accept two parameters, the {@link RegisteredBean} and the bean
* instance, and should return the modified bean instance.
* @param generationContext the generation context
* @param beanRegistrationCode the bean registration code
* @param instanceSupplierCode the instance supplier code supplier code
Expand All @@ -96,15 +114,13 @@ CodeBlock generateSetBeanInstanceSupplierCode(
* Generate the instance supplier code.
* @param generationContext the generation context
* @param beanRegistrationCode the bean registration code
* @param constructorOrFactoryMethod the constructor or factory method for
* the bean
* @param allowDirectSupplierShortcut if direct suppliers may be used rather
* than always needing an {@link InstanceSupplier}
* @return the generated code
*/
CodeBlock generateInstanceSupplierCode(
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
Executable constructorOrFactoryMethod, boolean allowDirectSupplierShortcut);
boolean allowDirectSupplierShortcut);

/**
* Generate the return statement.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
Expand All @@ -16,7 +16,6 @@

package org.springframework.beans.factory.aot;

import java.lang.reflect.Executable;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
Expand Down Expand Up @@ -51,8 +50,8 @@ protected BeanRegistrationCodeFragmentsDecorator(BeanRegistrationCodeFragments d
}

@Override
public ClassName getTarget(RegisteredBean registeredBean, Executable constructorOrFactoryMethod) {
return this.delegate.getTarget(registeredBean, constructorOrFactoryMethod);
public ClassName getTarget(RegisteredBean registeredBean) {
return this.delegate.getTarget(registeredBean);
}

@Override
Expand Down Expand Up @@ -83,11 +82,10 @@ public CodeBlock generateSetBeanInstanceSupplierCode(GenerationContext generatio

@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, Executable constructorOrFactoryMethod,
boolean allowDirectSupplierShortcut) {
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {

return this.delegate.generateInstanceSupplierCode(generationContext,
beanRegistrationCode, constructorOrFactoryMethod, allowDirectSupplierShortcut);
beanRegistrationCode, allowDirectSupplierShortcut);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
Expand All @@ -16,7 +16,6 @@

package org.springframework.beans.factory.aot;

import java.lang.reflect.Executable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
Expand Down Expand Up @@ -47,19 +46,15 @@ class BeanRegistrationCodeGenerator implements BeanRegistrationCode {

private final RegisteredBean registeredBean;

private final Executable constructorOrFactoryMethod;

private final BeanRegistrationCodeFragments codeFragments;


BeanRegistrationCodeGenerator(ClassName className, GeneratedMethods generatedMethods,
RegisteredBean registeredBean, Executable constructorOrFactoryMethod,
BeanRegistrationCodeFragments codeFragments) {
RegisteredBean registeredBean, BeanRegistrationCodeFragments codeFragments) {

this.className = className;
this.generatedMethods = generatedMethods;
this.registeredBean = registeredBean;
this.constructorOrFactoryMethod = constructorOrFactoryMethod;
this.codeFragments = codeFragments;
}

Expand Down Expand Up @@ -87,8 +82,7 @@ CodeBlock generateCode(GenerationContext generationContext) {
generationContext, this, this.registeredBean.getMergedBeanDefinition(),
REJECT_ALL_ATTRIBUTES_FILTER));
CodeBlock instanceSupplierCode = this.codeFragments.generateInstanceSupplierCode(
generationContext, this, this.constructorOrFactoryMethod,
this.instancePostProcessors.isEmpty());
generationContext, this, this.instancePostProcessors.isEmpty());
code.add(this.codeFragments.generateSetBeanInstanceSupplierCode(generationContext,
this, instanceSupplierCode, this.instancePostProcessors));
code.add(this.codeFragments.generateReturnCode(generationContext, this));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.Supplier;

import org.springframework.aot.generate.AccessControl;
import org.springframework.aot.generate.GenerationContext;
Expand All @@ -39,12 +40,14 @@
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.function.SingletonSupplier;

/**
* Internal {@link BeanRegistrationCodeFragments} implementation used by
* default.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragments {

Expand All @@ -54,6 +57,8 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme

private final BeanDefinitionMethodGeneratorFactory beanDefinitionMethodGeneratorFactory;

private final Supplier<Executable> constructorOrFactoryMethod;


DefaultBeanRegistrationCodeFragments(BeanRegistrationsCode beanRegistrationsCode,
RegisteredBean registeredBean,
Expand All @@ -62,14 +67,13 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
this.beanRegistrationsCode = beanRegistrationsCode;
this.registeredBean = registeredBean;
this.beanDefinitionMethodGeneratorFactory = beanDefinitionMethodGeneratorFactory;
this.constructorOrFactoryMethod = SingletonSupplier.of(registeredBean::resolveConstructorOrFactoryMethod);
}


@Override
public ClassName getTarget(RegisteredBean registeredBean,
Executable constructorOrFactoryMethod) {

Class<?> target = extractDeclaringClass(registeredBean.getBeanType(), constructorOrFactoryMethod);
public ClassName getTarget(RegisteredBean registeredBean) {
Class<?> target = extractDeclaringClass(registeredBean.getBeanType(), this.constructorOrFactoryMethod.get());
while (target.getName().startsWith("java.") && registeredBean.isInnerBean()) {
RegisteredBean parent = registeredBean.getParent();
Assert.state(parent != null, "No parent available for inner bean");
Expand Down Expand Up @@ -219,12 +223,11 @@ public CodeBlock generateSetBeanInstanceSupplierCode(

@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode,
Executable constructorOrFactoryMethod, boolean allowDirectSupplierShortcut) {
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {

return new InstanceSupplierCodeGenerator(generationContext,
beanRegistrationCode.getClassName(), beanRegistrationCode.getMethods(), allowDirectSupplierShortcut)
.generateCode(this.registeredBean, constructorOrFactoryMethod);
.generateCode(this.registeredBean,this.constructorOrFactoryMethod.get());
}

@Override
Expand Down
Loading

0 comments on commit 66a571f

Please sign in to comment.