Support registration of non-public BeanDefinitionReader via @⁠ImportResource

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

View File

@@ -80,6 +80,10 @@ public @interface ImportResource {
* {@link BeanDefinitionReader} implementation to use when processing * {@link BeanDefinitionReader} implementation to use when processing
* resources specified via the {@link #locations() locations} or * resources specified via the {@link #locations() locations} or
* {@link #value() value} attribute. * {@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: * <p>By default, the reader will be adapted to the resource path specified:
* {@code ".groovy"} files will be processed with a * {@code ".groovy"} files will be processed with a
* {@link org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader * {@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.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; 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 @Configuration
@ImportResource("classpath:org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml") @ImportResource("classpath:org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml")
@@ -173,4 +181,20 @@ class ImportResourceTests {
static class ImportNonXmlResourceConfig { 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);
}
}
} }