Add support for package-private BeanRegistrar

Closes gh-35803
This commit is contained in:
Sébastien Deleuze
2025-12-05 17:04:51 +01:00
parent 69207c6c16
commit f19f1a667c
2 changed files with 129 additions and 14 deletions
@@ -42,16 +42,20 @@ import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
import org.springframework.aot.generate.AccessControl;
import org.springframework.aot.generate.GeneratedClass;
import org.springframework.aot.generate.GeneratedMethod;
import org.springframework.aot.generate.GeneratedMethods;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.MethodReference;
import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.ResourceHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanDefinitionStoreException;
@@ -872,11 +876,14 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
@Override
public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) {
GeneratedMethod generatedMethod = beanFactoryInitializationCode.getMethods().add(
"applyBeanRegistrars", builder -> this.generateApplyBeanRegistrarsMethod(builder, generationContext));
"applyBeanRegistrars", builder -> this.generateApplyBeanRegistrarsMethod(builder,
generationContext, beanFactoryInitializationCode.getClassName()));
beanFactoryInitializationCode.addInitializer(generatedMethod.toMethodReference());
}
private void generateApplyBeanRegistrarsMethod(MethodSpec.Builder method, GenerationContext generationContext) {
private void generateApplyBeanRegistrarsMethod(MethodSpec.Builder method, GenerationContext generationContext,
ClassName className) {
ReflectionHints reflectionHints = generationContext.getRuntimeHints().reflection();
method.addJavadoc("Apply bean registrars.");
method.addModifiers(Modifier.PRIVATE);
@@ -915,7 +922,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
}
}
method.addCode(generateRegisterCode());
method.addCode(generateRegisterCode(className, generationContext));
}
private void checkUnsupportedFeatures(AbstractBeanDefinition beanDefinition) {
@@ -937,37 +944,79 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
return code.build();
}
private CodeBlock generateRegisterCode() {
private CodeBlock generateRegisterCode(ClassName className, GenerationContext generationContext) {
Builder code = CodeBlock.builder();
Builder metadataReaderFactoryCode = null;
NameAllocator nameAllocator = new NameAllocator();
for (Map.Entry<String, List<BeanRegistrar>> beanRegistrarEntry : this.beanRegistrars.entrySet()) {
for (BeanRegistrar beanRegistrar : beanRegistrarEntry.getValue()) {
String beanRegistrarName = nameAllocator.newName(StringUtils.uncapitalize(beanRegistrar.getClass().getSimpleName()));
code.addStatement("$T $L = new $T()", beanRegistrar.getClass(), beanRegistrarName, beanRegistrar.getClass());
Constructor<?> constructor = BeanUtils.getResolvableConstructor(beanRegistrar.getClass());
boolean visible = isVisible(constructor, className);
if (visible) {
code.addStatement("$T $L = new $T()", beanRegistrar.getClass(), beanRegistrarName, beanRegistrar.getClass());
}
else {
try {
Class<?> configClass = ClassUtils.forName(beanRegistrarEntry.getKey(), beanRegistrar.getClass().getClassLoader());
GeneratedClass generatedClass = generationContext.getGeneratedClasses()
.getOrAddForFeatureComponent("BeanRegistrars", configClass, type ->
type.addJavadoc("Bean registrars for {@link $T}.", configClass)
.addModifiers(Modifier.PUBLIC));
GeneratedMethod generatedMethod = generatedClass.getMethods().add(
"get" + beanRegistrar.getClass().getSimpleName(),
method -> method
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(BeanRegistrar.class)
.addStatement("return new $T()", beanRegistrar.getClass()));
code.addStatement("$T $L = $L", BeanRegistrar.class, beanRegistrarName,
generatedMethod.toMethodReference().toInvokeCodeBlock(ArgumentCodeGenerator.none()));
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
}
if (beanRegistrar instanceof ImportAware) {
if (metadataReaderFactoryCode == null) {
metadataReaderFactoryCode = CodeBlock.builder();
metadataReaderFactoryCode.addStatement("$T metadataReaderFactory = new $T()",
MetadataReaderFactory.class, CachingMetadataReaderFactory.class);
}
CodeBlock setImportMetadataCode;
if (visible) {
setImportMetadataCode = CodeBlock.builder()
.addStatement("$L.setImportMetadata(metadataReaderFactory.getMetadataReader($S).getAnnotationMetadata())",
beanRegistrarName, beanRegistrarEntry.getKey()).build();
}
else {
setImportMetadataCode = CodeBlock.builder()
.addStatement("(($T)$L).setImportMetadata(metadataReaderFactory.getMetadataReader($S).getAnnotationMetadata())",
ImportAware.class, beanRegistrarName, beanRegistrarEntry.getKey()).build();
}
code.beginControlFlow("try")
.addStatement("$L.setImportMetadata(metadataReaderFactory.getMetadataReader($S).getAnnotationMetadata())",
beanRegistrarName, beanRegistrarEntry.getKey())
.add(setImportMetadataCode)
.nextControlFlow("catch ($T ex)", IOException.class)
.addStatement("throw new $T(\"Failed to read metadata for '$L'\", ex)",
IllegalStateException.class, beanRegistrarEntry.getKey())
.endControlFlow();
}
code.addStatement("$L.register(new $T(($T)$L, $L, $L, $T.class, $L), $L)", beanRegistrarName,
code.addStatement("$L.register(new $T(($T)$L, $L, $L, $L.getClass(), $L), $L)", beanRegistrarName,
BeanRegistryAdapter.class, BeanDefinitionRegistry.class, BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE,
BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE, ENVIRONMENT_VARIABLE, beanRegistrar.getClass(),
BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE, ENVIRONMENT_VARIABLE, beanRegistrarName,
CUSTOMIZER_MAP_VARIABLE, ENVIRONMENT_VARIABLE);
}
}
return (metadataReaderFactoryCode == null ? code.build() : metadataReaderFactoryCode.add(code.build()).build());
}
private boolean isVisible(Constructor<?> ctor, ClassName className) {
AccessControl classAccessControl = AccessControl.forClass(ctor.getDeclaringClass());
AccessControl memberAccessControl = AccessControl.forMember(ctor);
AccessControl.Visibility visibility = AccessControl.lowest(classAccessControl, memberAccessControl).getVisibility();
return (visibility == AccessControl.Visibility.PUBLIC || (visibility != AccessControl.Visibility.PRIVATE &&
ctor.getDeclaringClass().getPackageName().equals(className.packageName())));
}
private CodeBlock generateInitDestroyMethods(String beanName, AbstractBeanDefinition beanDefinition,
String[] methodNames, String method, ReflectionHints reflectionHints) {
@@ -62,6 +62,7 @@ import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
import org.springframework.core.test.tools.Compiled;
import org.springframework.core.test.tools.TestCompiler;
import org.springframework.core.type.AnnotationMetadata;
@@ -503,7 +504,7 @@ public class ConfigurationClassPostProcessorAotContributionTests {
@Test
void applyToWhenIsImportAware() {
BeanFactoryInitializationAotContribution contribution = getContribution(CommonAnnotationBeanPostProcessor.class,
ImportAwareBeanRegistrarConfiguration.class);
ImportAwareConfiguration.class);
assertThat(contribution).isNotNull();
contribution.applyTo(generationContext, beanFactoryInitializationCode);
compile((initializer, compiled) -> {
@@ -511,7 +512,42 @@ public class ConfigurationClassPostProcessorAotContributionTests {
initializer.accept(freshContext);
freshContext.refresh();
assertThat(freshContext.getBean(ClassNameHolder.class).className())
.isEqualTo(ImportAwareBeanRegistrarConfiguration.class.getName());
.isEqualTo(ImportAwareConfiguration.class.getName());
freshContext.close();
});
}
@Test
@CompileWithForkedClassLoader
void applyToWhenIsPackagePrivate() throws NoSuchMethodException {
BeanFactoryInitializationAotContribution contribution = getContribution(PackagePrivateConfiguration.class);
assertThat(contribution).isNotNull();
contribution.applyTo(generationContext, beanFactoryInitializationCode);
Constructor<Foo> fooConstructor = Foo.class.getDeclaredConstructor();
compile((initializer, compiled) -> {
GenericApplicationContext freshContext = new GenericApplicationContext();
initializer.accept(freshContext);
freshContext.refresh();
assertThat(freshContext.getBean(Foo.class)).isNotNull();
assertThat(RuntimeHintsPredicates.reflection().onConstructorInvocation(fooConstructor))
.accepts(generationContext.getRuntimeHints());
freshContext.close();
});
}
@Test
@CompileWithForkedClassLoader
void applyToWhenIsPackagePrivateAndImportAware() {
BeanFactoryInitializationAotContribution contribution = getContribution(CommonAnnotationBeanPostProcessor.class,
PackagePrivateAndImportAwareConfiguration.class);
assertThat(contribution).isNotNull();
contribution.applyTo(generationContext, beanFactoryInitializationCode);
compile((initializer, compiled) -> {
GenericApplicationContext freshContext = new GenericApplicationContext();
initializer.accept(freshContext);
freshContext.refresh();
assertThat(freshContext.getBean(ClassNameHolder.class).className())
.isEqualTo(PackagePrivateAndImportAwareConfiguration.class.getName());
freshContext.close();
});
}
@@ -578,7 +614,7 @@ public class ConfigurationClassPostProcessorAotContributionTests {
}
@Import(ImportAwareBeanRegistrar.class)
public static class ImportAwareBeanRegistrarConfiguration {
public static class ImportAwareConfiguration {
}
public static class ImportAwareBeanRegistrar implements BeanRegistrar, ImportAware {
@@ -596,9 +632,39 @@ public class ConfigurationClassPostProcessorAotContributionTests {
public void setImportMetadata(AnnotationMetadata importMetadata) {
this.importMetadata = importMetadata;
}
}
public @Nullable AnnotationMetadata getImportMetadata() {
return this.importMetadata;
@Configuration
@Import(PackagePrivateBeanRegistrar.class)
static class PackagePrivateConfiguration {
}
static class PackagePrivateBeanRegistrar implements BeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment env) {
registry.registerBean(Foo.class);
}
}
@Import(PackagePrivateAndImportAwareBeanRegistrar.class)
static class PackagePrivateAndImportAwareConfiguration {
}
static class PackagePrivateAndImportAwareBeanRegistrar implements BeanRegistrar, ImportAware {
@Nullable
private AnnotationMetadata importMetadata;
@Override
public void register(BeanRegistry registry, Environment env) {
registry.registerBean(ClassNameHolder.class, spec -> spec.supplier(context ->
new ClassNameHolder(this.importMetadata == null ? null : this.importMetadata.getClassName())));
}
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
this.importMetadata = importMetadata;
}
}