Add support for BeanRegistrar registration on GenericApplicationContext

Closes gh-34574
This commit is contained in:
Juergen Hoeller
2025-03-11 21:18:20 +01:00
parent beb3a91847
commit 86b21d9b5c
5 changed files with 80 additions and 7 deletions

View File

@@ -60,9 +60,10 @@ import org.springframework.core.env.Environment;
public interface BeanRegistrar {
/**
* Register beans in a programmatic way.
* @param registry the bean registry
* Register beans on the given {@link BeanRegistry} in a programmatic way.
* @param registry the bean registry to operate on
* @param env the environment that can be used to get the active profile or some properties
*/
void register(BeanRegistry registry, Environment env);
}

View File

@@ -56,6 +56,12 @@ public class BeanRegistryAdapter implements BeanRegistry {
private final @Nullable MultiValueMap<String, BeanDefinitionCustomizer> customizers;
public BeanRegistryAdapter(DefaultListableBeanFactory beanFactory, Environment environment,
Class<? extends BeanRegistrar> beanRegistrarClass) {
this(beanFactory, beanFactory, environment, beanRegistrarClass, null);
}
public BeanRegistryAdapter(BeanDefinitionRegistry beanRegistry, ListableBeanFactory beanFactory,
Environment environment, Class<? extends BeanRegistrar> beanRegistrarClass) {
@@ -73,6 +79,7 @@ public class BeanRegistryAdapter implements BeanRegistry {
this.customizers = customizers;
}
@Override
public <T> String registerBean(Class<T> beanClass) {
String beanName = BeanDefinitionReaderUtils.uniqueBeanName(beanClass.getName(), this.beanRegistry);
@@ -154,7 +161,8 @@ public class BeanRegistryAdapter implements BeanRegistry {
}
}
static class BeanSpecAdapter<T> implements Spec<T> {
private static class BeanSpecAdapter<T> implements Spec<T> {
private final RootBeanDefinition beanDefinition;
@@ -239,7 +247,8 @@ public class BeanRegistryAdapter implements BeanRegistry {
}
}
static class SupplierContextAdapter implements SupplierContext {
private static class SupplierContextAdapter implements SupplierContext {
private final ListableBeanFactory beanFactory;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2025 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.
@@ -16,6 +16,8 @@
package org.springframework.context.annotation;
import org.springframework.beans.factory.BeanRegistrar;
/**
* Common interface for annotation config application contexts,
* defining {@link #register} and {@link #scan} methods.
@@ -26,17 +28,33 @@ package org.springframework.context.annotation;
public interface AnnotationConfigRegistry {
/**
* Register one or more component classes to be processed.
* Invoke the given registrars for registering their beans with this
* application context.
* <p>This can be used to register custom beans without inferring
* annotation-based characteristics for primary/fallback/lazy-init,
* rather specifying those programmatically if needed.
* @param registrars one or more {@link BeanRegistrar} instances
* @since 7.0
* @see #register(Class[])
*/
void register(BeanRegistrar... registrars);
/**
* Register one or more component classes to be processed, inferring
* annotation-based characteristics for primary/fallback/lazy-init
* just like for scanned component classes.
* <p>Calls to {@code register} are idempotent; adding the same
* component class more than once has no additional effect.
* @param componentClasses one or more component classes,
* for example, {@link Configuration @Configuration} classes
* @see #scan(String...)
*/
void register(Class<?>... componentClasses);
/**
* Perform a scan within the specified base packages.
* @param basePackages the packages to scan for component classes
* @see #register(Class[])
*/
void scan(String... basePackages);

View File

@@ -30,6 +30,7 @@ import org.springframework.aot.hint.support.ClassHintUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanRegistrar;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -38,6 +39,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.BeanRegistryAdapter;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -594,6 +596,21 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
registerBeanDefinition(nameToUse, beanDefinition);
}
/**
* Invoke the given registrars for registering their beans with this
* application context.
* <p>This can be used to apply encapsulated pieces of programmatic
* bean registration to this application context without relying on
* individual calls to its context-level {@code registerBean} methods.
* @param registrars one or more {@link BeanRegistrar} instances
* @since 7.0
*/
public void register(BeanRegistrar... registrars) {
for (BeanRegistrar registrar : registrars) {
new BeanRegistryAdapter(this.beanFactory, getEnvironment(), registrar.getClass()).register(registrar);
}
}
/**
* {@link RootBeanDefinition} subclass for {@code #registerBean} based

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 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.
@@ -22,7 +22,9 @@ import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.BeanRegistrar;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.BeanRegistryAdapter;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigRegistry;
@@ -104,6 +106,8 @@ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWe
private @Nullable ScopeMetadataResolver scopeMetadataResolver;
private final Set<BeanRegistrar> beanRegistrars = new LinkedHashSet<>();
private final Set<Class<?>> componentClasses = new LinkedHashSet<>();
private final Set<String> basePackages = new LinkedHashSet<>();
@@ -148,6 +152,20 @@ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWe
}
/**
* Invoke the given registrars for registering their beans with this
* application context.
* <p>Note that {@link #refresh()} must be called in order for the context
* to fully process the new classes.
* @param registrars one or more {@link BeanRegistrar} instances
* @since 7.0
*/
@Override
public void register(BeanRegistrar... registrars) {
Assert.notEmpty(registrars, "At least one BeanRegistrar must be specified");
Collections.addAll(this.beanRegistrars, registrars);
}
/**
* Register one or more component classes to be processed.
* <p>Note that {@link #refresh()} must be called in order for the context
@@ -222,6 +240,16 @@ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWe
scanner.setScopeMetadataResolver(scopeMetadataResolver);
}
if (!this.beanRegistrars.isEmpty()) {
if (logger.isDebugEnabled()) {
logger.debug("Applying bean registrars: [" +
StringUtils.collectionToCommaDelimitedString(this.beanRegistrars) + "]");
}
for (BeanRegistrar registrar : this.beanRegistrars) {
new BeanRegistryAdapter(beanFactory, getEnvironment(), registrar.getClass()).register(registrar);
}
}
if (!this.componentClasses.isEmpty()) {
if (logger.isDebugEnabled()) {
logger.debug("Registering component classes: [" +