Support registration of non-public BeanDefinitionReader via @⁠ImportResource
Backport Bot / build (push) Waiting to run Details
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:ubuntu-latest name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:ubuntu-latest name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:ubuntu-latest name:Linux]) (push) Waiting to run Details
Deploy Docs / Dispatch docs deployment (push) Waiting to run Details

Prior to this commit, a BeanDefinitionReader registered via
@⁠ImportResource was required to be public and have a public
constructor that accepts a single BeanDefinitionRegistry. However, the
public visibility requirements are not necessary, and the requirements
for the constructor's formal parameter list is not documented.

To address those issues, this commit removes the public visibility
restrictions and documents that a BeanDefinitionReader registered via
@⁠ImportResource must declare a constructor that accepts a single
BeanDefinitionRegistry.

In addition, this commit includes the cause of the instantiation
failure in case the registered BeanDefinitionReader cannot be
instantiated.

Closes gh-34928
This commit is contained in:
Sam Brannen 2025-05-21 15:31:04 +02:00
parent 98cef503fb
commit d890a38f3c
3 changed files with 35 additions and 3 deletions

View File

@ -16,6 +16,7 @@
package org.springframework.context.annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
@ -27,6 +28,7 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
@ -371,9 +373,11 @@ class ConfigurationClassBeanDefinitionReader {
BeanDefinitionReader reader = readerInstanceCache.get(readerClass);
if (reader == null) {
try {
Constructor<? extends BeanDefinitionReader> constructor =
readerClass.getDeclaredConstructor(BeanDefinitionRegistry.class);
// Instantiate the specified BeanDefinitionReader
reader = readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry);
// Delegate the current ResourceLoader to it if possible
reader = BeanUtils.instantiateClass(constructor, this.registry);
// Delegate the current ResourceLoader and Environment to it if possible
if (reader instanceof AbstractBeanDefinitionReader abdr) {
abdr.setResourceLoader(this.resourceLoader);
abdr.setEnvironment(this.environment);
@ -382,7 +386,7 @@ class ConfigurationClassBeanDefinitionReader {
}
catch (Throwable ex) {
throw new IllegalStateException(
"Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]");
"Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]", ex);
}
}
reader.loadBeanDefinitions(resource);

View File

@ -80,6 +80,10 @@ public @interface ImportResource {
* {@link BeanDefinitionReader} implementation to use when processing
* resources specified via the {@link #locations() locations} or
* {@link #value() value} attribute.
* <p>The configured {@code BeanDefinitionReader} type must declare a
* constructor that accepts a single
* {@link org.springframework.beans.factory.support.BeanDefinitionRegistry
* BeanDefinitionRegistry} argument.
* <p>By default, the reader will be adapted to the resource path specified:
* {@code ".groovy"} files will be processed with a
* {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader

View File

@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@ -109,6 +110,13 @@ class ImportResourceTests {
}
}
@Test
void importResourceWithPrivateReader() {
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportWithPrivateReaderConfig.class)) {
assertThat(ctx.containsBean("propertiesDeclaredBean")).isTrue();
}
}
@Configuration
@ImportResource("classpath:org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml")
@ -173,4 +181,20 @@ class ImportResourceTests {
static class ImportNonXmlResourceConfig {
}
@SuppressWarnings("deprecation")
@Configuration
@ImportResource(locations = "org/springframework/context/annotation/configuration/ImportNonXmlResourceConfig.properties",
reader = PrivatePropertiesBeanDefinitionReader.class)
static class ImportWithPrivateReaderConfig {
}
@SuppressWarnings("deprecation")
private static class PrivatePropertiesBeanDefinitionReader
extends org.springframework.beans.factory.support.PropertiesBeanDefinitionReader {
PrivatePropertiesBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
}
}
}