Introduce first-class support for programmatic bean registration
This commit introduces a new BeanRegistrar interface that can be implemented to register beans programmatically in a concise and flexible way. Those bean registrar implementations are typically imported with an `@Import` annotation on `@Configuration` classes. See BeanRegistrarConfigurationTests for a concrete example. See gh-18353
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.beans.factory;
|
||||
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* Contract for registering beans programmatically.
|
||||
*
|
||||
* <p>Typically imported with an {@link org.springframework.context.annotation.Import @Import}
|
||||
* annotation on {@link org.springframework.context.annotation.Configuration @Configuration}
|
||||
* classes.
|
||||
* <pre class="code">
|
||||
* @Configuration
|
||||
* @Import(MyBeanRegistrar.class)
|
||||
* class MyConfiguration {
|
||||
* }</pre>
|
||||
*
|
||||
* <p>The bean registrar implementation uses {@link BeanRegistry} and {@link Environment}
|
||||
* APIs to register beans programmatically in a concise and flexible way.
|
||||
* <pre class="code">
|
||||
* class MyBeanRegistrar implements BeanRegistrar {
|
||||
*
|
||||
* @Override
|
||||
* public void register(BeanRegistry registry, Environment env) {
|
||||
* registry.registerBean("foo", Foo.class);
|
||||
* registry.registerBean("bar", Bar.class, spec -> spec
|
||||
* .prototype()
|
||||
* .lazyInit()
|
||||
* .description("Custom description")
|
||||
* .supplier(context -> new Bar(context.bean(Foo.class))));
|
||||
* if (env.matchesProfiles("baz")) {
|
||||
* registry.registerBean(Baz.class, spec -> spec
|
||||
* .supplier(context -> new Baz("Hello World!")));
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* <p>In Kotlin, it is recommended to use {@code BeanRegistrarDsl} instead of
|
||||
* implementing {@code BeanRegistrar}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 7.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BeanRegistrar {
|
||||
|
||||
/**
|
||||
* Register beans in a programmatic way.
|
||||
* @param registry the bean registry
|
||||
* @param env the environment that can be used to get the active profile or some properties
|
||||
*/
|
||||
void register(BeanRegistry registry, Environment env);
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.beans.factory;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* Used in {@link BeanRegistrar#register(BeanRegistry, Environment)} to expose
|
||||
* programmatic bean registration capabilities.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 7.0
|
||||
*/
|
||||
public interface BeanRegistry {
|
||||
|
||||
/**
|
||||
* Register a bean from the given bean class, which will be instantiated
|
||||
* using the related {@link BeanUtils#getResolvableConstructor resolvable constructor}
|
||||
* if any.
|
||||
* @param beanClass the class of the bean
|
||||
* @return the generated bean name
|
||||
*/
|
||||
<T> String registerBean(Class<T> beanClass);
|
||||
|
||||
/**
|
||||
* Register a bean from the given bean class, customizing it with the customizer
|
||||
* callback. The bean will be instantiated using the supplier that can be
|
||||
* configured in the customizer callback, or will be tentatively instantiated
|
||||
* with its {@link BeanUtils#getResolvableConstructor resolvable constructor}
|
||||
* otherwise.
|
||||
* @param beanClass the class of the bean
|
||||
* @param customizer callback to customize other bean properties than the name
|
||||
* @return the generated bean name
|
||||
*/
|
||||
<T> String registerBean(Class<T> beanClass, Consumer<Spec<T>> customizer);
|
||||
|
||||
/**
|
||||
* Register a bean from the given bean class, which will be instantiated
|
||||
* using the related {@link BeanUtils#getResolvableConstructor resolvable constructor}
|
||||
* if any.
|
||||
* @param name the name of the bean
|
||||
* @param beanClass the class of the bean
|
||||
*/
|
||||
<T> void registerBean(String name, Class<T> beanClass);
|
||||
|
||||
/**
|
||||
* Register a bean from the given bean class, customizing it with the customizer
|
||||
* callback. The bean will be instantiated using the supplier that can be
|
||||
* configured in the customizer callback, or will be tentatively instantiated with its
|
||||
* {@link BeanUtils#getResolvableConstructor resolvable constructor} otherwise.
|
||||
* @param name the name of the bean
|
||||
* @param beanClass the class of the bean
|
||||
* @param customizer callback to customize other bean properties than the name
|
||||
*/
|
||||
<T> void registerBean(String name, Class<T> beanClass, Consumer<Spec<T>> customizer);
|
||||
|
||||
/**
|
||||
* Specification for customizing a bean.
|
||||
* @param <T> the bean type
|
||||
*/
|
||||
interface Spec<T> {
|
||||
|
||||
/**
|
||||
* Allow for instantiating this bean on a background thread.
|
||||
* @see AbstractBeanDefinition#setBackgroundInit(boolean)
|
||||
*/
|
||||
Spec<T> backgroundInit();
|
||||
|
||||
/**
|
||||
* Set a human-readable description of this bean.
|
||||
* @see BeanDefinition#setDescription(String)
|
||||
*/
|
||||
Spec<T> description(String description);
|
||||
|
||||
/**
|
||||
* Configure this bean as a fallback autowire candidate.
|
||||
* @see BeanDefinition#setFallback(boolean)
|
||||
* @see #primary
|
||||
*/
|
||||
Spec<T> fallback();
|
||||
|
||||
/**
|
||||
* Hint that this bean has an infrastructure role, meaning it has no
|
||||
* relevance to the end-user.
|
||||
* @see BeanDefinition#setRole(int)
|
||||
* @see BeanDefinition#ROLE_INFRASTRUCTURE
|
||||
*/
|
||||
Spec<T> infrastructure();
|
||||
|
||||
/**
|
||||
* Configure this bean as lazily initialized.
|
||||
* @see BeanDefinition#setLazyInit(boolean)
|
||||
*/
|
||||
Spec<T> lazyInit();
|
||||
|
||||
/**
|
||||
* Configure this bean as not a candidate for getting autowired into some
|
||||
* other bean.
|
||||
* @see BeanDefinition#setAutowireCandidate(boolean)
|
||||
*/
|
||||
Spec<T> notAutowirable();
|
||||
|
||||
/**
|
||||
* The sort order of this bean. This is analogous to the
|
||||
* {@code @Order} annotation.
|
||||
* @see AbstractBeanDefinition#ORDER_ATTRIBUTE
|
||||
*/
|
||||
Spec<T> order(int order);
|
||||
|
||||
/**
|
||||
* Configure this bean as a primary autowire candidate.
|
||||
* @see BeanDefinition#setPrimary(boolean)
|
||||
* @see #fallback
|
||||
*/
|
||||
Spec<T> primary();
|
||||
|
||||
/**
|
||||
* Configure this bean with a prototype scope.
|
||||
* @see BeanDefinition#setScope(String)
|
||||
* @see BeanDefinition#SCOPE_PROTOTYPE
|
||||
*/
|
||||
Spec<T> prototype();
|
||||
|
||||
/**
|
||||
* Set the supplier to construct a bean instance.
|
||||
* @see AbstractBeanDefinition#setInstanceSupplier(Supplier)
|
||||
*/
|
||||
Spec<T> supplier(Function<SupplierContext, T> supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Context available from the bean instance supplier designed to give access
|
||||
* to bean dependencies.
|
||||
*/
|
||||
interface SupplierContext {
|
||||
|
||||
/**
|
||||
* Return the bean instance that uniquely matches the given object type,
|
||||
* if any.
|
||||
* @param requiredType type the bean must match; can be an interface or
|
||||
* superclass
|
||||
* @return an instance of the single bean matching the required type
|
||||
* @see BeanFactory#getBean(String)
|
||||
*/
|
||||
<T> T bean(Class<T> requiredType) throws BeansException;
|
||||
|
||||
/**
|
||||
* Return an instance, which may be shared or independent, of the
|
||||
* specified bean.
|
||||
* @param name the name of the bean to retrieve
|
||||
* @param requiredType type the bean must match; can be an interface or superclass
|
||||
* @return an instance of the bean.
|
||||
* @see BeanFactory#getBean(String, Class)
|
||||
*/
|
||||
<T> T bean(String name, Class<T> requiredType) throws BeansException;
|
||||
|
||||
/**
|
||||
* Return a provider for the specified bean, allowing for lazy on-demand retrieval
|
||||
* of instances, including availability and uniqueness options.
|
||||
* <p>For matching a generic type, consider {@link #beanProvider(ResolvableType)}.
|
||||
* @param requiredType type the bean must match; can be an interface or superclass
|
||||
* @return a corresponding provider handle
|
||||
* @see BeanFactory#getBeanProvider(Class)
|
||||
*/
|
||||
<T> ObjectProvider<T> beanProvider(Class<T> requiredType);
|
||||
|
||||
/**
|
||||
* Return a provider for the specified bean, allowing for lazy on-demand retrieval
|
||||
* of instances, including availability and uniqueness options. This variant allows
|
||||
* for specifying a generic type to match, similar to reflective injection points
|
||||
* with generic type declarations in method/constructor parameters.
|
||||
* <p>Note that collections of beans are not supported here, in contrast to reflective
|
||||
* injection points. For programmatically retrieving a list of beans matching a
|
||||
* specific type, specify the actual bean type as an argument here and subsequently
|
||||
* use {@link ObjectProvider#orderedStream()} or its lazy streaming/iteration options.
|
||||
* <p>Also, generics matching is strict here, as per the Java assignment rules.
|
||||
* For lenient fallback matching with unchecked semantics (similar to the 'unchecked'
|
||||
* Java compiler warning), consider calling {@link #beanProvider(Class)} with the
|
||||
* raw type as a second step if no full generic match is
|
||||
* {@link ObjectProvider#getIfAvailable() available} with this variant.
|
||||
* @param requiredType type the bean must match; can be a generic type declaration
|
||||
* @return a corresponding provider handle
|
||||
* @see BeanFactory#getBeanProvider(ResolvableType)
|
||||
*/
|
||||
<T> ObjectProvider<T> beanProvider(ResolvableType requiredType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.beans.factory.support;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanRegistrar;
|
||||
import org.springframework.beans.factory.BeanRegistry;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionCustomizer;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* {@link BeanRegistry} implementation that delegates to
|
||||
* {@link BeanDefinitionRegistry} and {@link ListableBeanFactory}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 7.0
|
||||
*/
|
||||
public class BeanRegistryAdapter implements BeanRegistry {
|
||||
|
||||
private final BeanDefinitionRegistry beanRegistry;
|
||||
|
||||
private final ListableBeanFactory beanFactory;
|
||||
|
||||
private final Class<? extends BeanRegistrar> beanRegistrarClass;
|
||||
|
||||
private final @Nullable MultiValueMap<String, BeanDefinitionCustomizer> customizers;
|
||||
|
||||
|
||||
public BeanRegistryAdapter(BeanDefinitionRegistry beanRegistry, ListableBeanFactory beanFactory,
|
||||
Class<? extends BeanRegistrar> beanRegistrarClass) {
|
||||
this(beanRegistry, beanFactory, beanRegistrarClass, null);
|
||||
}
|
||||
|
||||
public BeanRegistryAdapter(BeanDefinitionRegistry beanRegistry, ListableBeanFactory beanFactory,
|
||||
Class<? extends BeanRegistrar> beanRegistrarClass, @Nullable MultiValueMap<String, BeanDefinitionCustomizer> customizers) {
|
||||
|
||||
this.beanRegistry = beanRegistry;
|
||||
this.beanFactory = beanFactory;
|
||||
this.beanRegistrarClass = beanRegistrarClass;
|
||||
this.customizers = customizers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> String registerBean(Class<T> beanClass) {
|
||||
String beanName = BeanDefinitionReaderUtils.uniqueBeanName(beanClass.getName(), this.beanRegistry);
|
||||
registerBean(beanName, beanClass);
|
||||
return beanName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> String registerBean(Class<T> beanClass, Consumer<Spec<T>> customizer) {
|
||||
String beanName = BeanDefinitionReaderUtils.uniqueBeanName(beanClass.getName(), this.beanRegistry);
|
||||
registerBean(beanName, beanClass, customizer);
|
||||
return beanName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void registerBean(String name, Class<T> beanClass) {
|
||||
BeanRegistrarBeanDefinition beanDefinition = new BeanRegistrarBeanDefinition(beanClass, this.beanRegistrarClass);
|
||||
if (this.customizers != null && this.customizers.containsKey(name)) {
|
||||
for (BeanDefinitionCustomizer customizer : this.customizers.get(name)) {
|
||||
customizer.customize(beanDefinition);
|
||||
}
|
||||
}
|
||||
this.beanRegistry.registerBeanDefinition(name, beanDefinition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void registerBean(String name, Class<T> beanClass, Consumer<Spec<T>> spec) {
|
||||
BeanRegistrarBeanDefinition beanDefinition = new BeanRegistrarBeanDefinition(beanClass, this.beanRegistrarClass);
|
||||
spec.accept(new BeanSpecAdapter<>(beanDefinition, this.beanFactory));
|
||||
if (this.customizers != null && this.customizers.containsKey(name)) {
|
||||
for (BeanDefinitionCustomizer customizer : this.customizers.get(name)) {
|
||||
customizer.customize(beanDefinition);
|
||||
}
|
||||
}
|
||||
this.beanRegistry.registerBeanDefinition(name, beanDefinition);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link RootBeanDefinition} subclass for {@code #registerBean} based
|
||||
* registrations with constructors resolution match{@link BeanUtils#getResolvableConstructor}
|
||||
* behavior. It also sets the bean registrar class as the source.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private static class BeanRegistrarBeanDefinition extends RootBeanDefinition {
|
||||
|
||||
public BeanRegistrarBeanDefinition(Class<?> beanClass, Class<? extends BeanRegistrar> beanRegistrarClass) {
|
||||
super(beanClass);
|
||||
this.setSource(beanRegistrarClass);
|
||||
this.setAttribute("aotProcessingIgnoreRegistration", true);
|
||||
}
|
||||
|
||||
public BeanRegistrarBeanDefinition(BeanRegistrarBeanDefinition original) {
|
||||
super(original);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Constructor<?> @Nullable [] getPreferredConstructors() {
|
||||
if (this.getInstanceSupplier() != null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new Constructor<?>[] { BeanUtils.getResolvableConstructor(getBeanClass()) };
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootBeanDefinition cloneBeanDefinition() {
|
||||
return new BeanRegistrarBeanDefinition(this);
|
||||
}
|
||||
}
|
||||
|
||||
static class BeanSpecAdapter<T> implements Spec<T> {
|
||||
|
||||
private final RootBeanDefinition beanDefinition;
|
||||
|
||||
private final ListableBeanFactory beanFactory;
|
||||
|
||||
public BeanSpecAdapter(RootBeanDefinition beanDefinition, ListableBeanFactory beanFactory) {
|
||||
this.beanDefinition = beanDefinition;
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spec<T> backgroundInit() {
|
||||
this.beanDefinition.setBackgroundInit(true);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spec<T> fallback() {
|
||||
this.beanDefinition.setFallback(true);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spec<T> primary() {
|
||||
this.beanDefinition.setPrimary(true);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spec<T> description(String description) {
|
||||
this.beanDefinition.setDescription(description);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spec<T> infrastructure() {
|
||||
this.beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spec<T> lazyInit() {
|
||||
this.beanDefinition.setLazyInit(true);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spec<T> notAutowirable() {
|
||||
this.beanDefinition.setAutowireCandidate(false);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spec<T> order(int order) {
|
||||
this.beanDefinition.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, order);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spec<T> prototype() {
|
||||
this.beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spec<T> supplier(Function<SupplierContext, T> supplier) {
|
||||
this.beanDefinition.setInstanceSupplier(() ->
|
||||
supplier.apply(new SupplierContextAdapter(this.beanFactory)));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
static class SupplierContextAdapter implements SupplierContext {
|
||||
|
||||
private final ListableBeanFactory beanFactory;
|
||||
|
||||
public SupplierContextAdapter(ListableBeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T bean(Class<T> requiredType) throws BeansException {
|
||||
return this.beanFactory.getBean(requiredType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T bean(String name, Class<T> requiredType) throws BeansException {
|
||||
return this.beanFactory.getBean(name, requiredType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ObjectProvider<T> beanProvider(Class<T> requiredType) {
|
||||
return this.beanFactory.getBeanProvider(requiredType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ObjectProvider<T> beanProvider(ResolvableType requiredType) {
|
||||
return this.beanFactory.getBeanProvider(requiredType);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.beans.factory.support;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanRegistrar;
|
||||
import org.springframework.beans.factory.BeanRegistry;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link BeanRegistryAdapter}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class BeanRegistryAdapterTests {
|
||||
|
||||
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
|
||||
private final Environment env = new StandardEnvironment();
|
||||
|
||||
@Test
|
||||
void defaultBackgroundInit() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
|
||||
new DefaultBeanRegistrar().register(adapter, env);
|
||||
AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) this.beanFactory.getBeanDefinition("foo");
|
||||
assertThat(beanDefinition.isBackgroundInit()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void enableBackgroundInit() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, BackgroundInitBeanRegistrar.class);
|
||||
new BackgroundInitBeanRegistrar().register(adapter, env);
|
||||
AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) this.beanFactory.getBeanDefinition("foo");
|
||||
assertThat(beanDefinition.isBackgroundInit()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultDescription() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
|
||||
new DefaultBeanRegistrar().register(adapter, env);
|
||||
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
|
||||
assertThat(beanDefinition.getDescription()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void customDescription() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, CustomDescriptionBeanRegistrar.class);
|
||||
new CustomDescriptionBeanRegistrar().register(adapter, env);
|
||||
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
|
||||
assertThat(beanDefinition.getDescription()).isEqualTo("custom");
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultFallback() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
|
||||
new DefaultBeanRegistrar().register(adapter, env);
|
||||
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
|
||||
assertThat(beanDefinition.isFallback()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void enableFallback() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, FallbackBeanRegistrar.class);
|
||||
new FallbackBeanRegistrar().register(adapter, env);
|
||||
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
|
||||
assertThat(beanDefinition.isFallback()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultRole() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
|
||||
new DefaultBeanRegistrar().register(adapter, env);
|
||||
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
|
||||
assertThat(beanDefinition.getRole()).isEqualTo(AbstractBeanDefinition.ROLE_APPLICATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void infrastructureRole() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, InfrastructureBeanRegistrar.class);
|
||||
new InfrastructureBeanRegistrar().register(adapter, env);
|
||||
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
|
||||
assertThat(beanDefinition.getRole()).isEqualTo(AbstractBeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultLazyInit() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
|
||||
new DefaultBeanRegistrar().register(adapter, env);
|
||||
AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) this.beanFactory.getBeanDefinition("foo");
|
||||
assertThat(beanDefinition.isLazyInit()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void enableLazyInit() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, LazyInitBeanRegistrar.class);
|
||||
new LazyInitBeanRegistrar().register(adapter, env);
|
||||
AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) this.beanFactory.getBeanDefinition("foo");
|
||||
assertThat(beanDefinition.isLazyInit()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultAutowirable() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
|
||||
new DefaultBeanRegistrar().register(adapter, env);
|
||||
AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) this.beanFactory.getBeanDefinition("foo");
|
||||
assertThat(beanDefinition.isAutowireCandidate()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void notAutowirable() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, NotAutowirableBeanRegistrar.class);
|
||||
new NotAutowirableBeanRegistrar().register(adapter, env);
|
||||
AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) this.beanFactory.getBeanDefinition("foo");
|
||||
assertThat(beanDefinition.isAutowireCandidate()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultOrder() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
|
||||
new DefaultBeanRegistrar().register(adapter, env);
|
||||
AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) this.beanFactory.getBeanDefinition("foo");
|
||||
Integer order = (Integer)beanDefinition.getAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE);
|
||||
assertThat(order).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void customOrder() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, CustomOrderBeanRegistrar.class);
|
||||
new CustomOrderBeanRegistrar().register(adapter, env);
|
||||
AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) this.beanFactory.getBeanDefinition("foo");
|
||||
Integer order = (Integer)beanDefinition.getAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE);
|
||||
assertThat(order).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultPrimary() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
|
||||
new DefaultBeanRegistrar().register(adapter, env);
|
||||
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
|
||||
assertThat(beanDefinition.isPrimary()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void enablePrimary() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, PrimaryBeanRegistrar.class);
|
||||
new PrimaryBeanRegistrar().register(adapter, env);
|
||||
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
|
||||
assertThat(beanDefinition.isPrimary()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultScope() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
|
||||
new DefaultBeanRegistrar().register(adapter, env);
|
||||
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
|
||||
assertThat(beanDefinition.getScope()).isEqualTo(AbstractBeanDefinition.SCOPE_DEFAULT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void prototypeScope() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, PrototypeBeanRegistrar.class);
|
||||
new PrototypeBeanRegistrar().register(adapter, env);
|
||||
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
|
||||
assertThat(beanDefinition.getScope()).isEqualTo(AbstractBeanDefinition.SCOPE_PROTOTYPE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultSupplier() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, DefaultBeanRegistrar.class);
|
||||
new DefaultBeanRegistrar().register(adapter, env);
|
||||
AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition)this.beanFactory.getBeanDefinition("foo");
|
||||
assertThat(beanDefinition.getInstanceSupplier()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSupplier() {
|
||||
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, SupplierBeanRegistrar.class);
|
||||
new SupplierBeanRegistrar().register(adapter, env);
|
||||
AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition)this.beanFactory.getBeanDefinition("foo");
|
||||
Supplier<?> supplier = beanDefinition.getInstanceSupplier();
|
||||
assertThat(supplier).isNotNull();
|
||||
assertThat(supplier.get()).isNotNull().isInstanceOf(Foo.class);
|
||||
}
|
||||
|
||||
|
||||
private static class DefaultBeanRegistrar implements BeanRegistrar {
|
||||
|
||||
@Override
|
||||
public void register(BeanRegistry registry, Environment env) {
|
||||
registry.registerBean("foo", Foo.class);
|
||||
}
|
||||
}
|
||||
|
||||
private static class BackgroundInitBeanRegistrar implements BeanRegistrar {
|
||||
|
||||
@Override
|
||||
public void register(BeanRegistry registry, Environment env) {
|
||||
registry.registerBean("foo", Foo.class, BeanRegistry.Spec::backgroundInit);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CustomDescriptionBeanRegistrar implements BeanRegistrar {
|
||||
|
||||
@Override
|
||||
public void register(BeanRegistry registry, Environment env) {
|
||||
registry.registerBean("foo", Foo.class, spec -> spec.description("custom"));
|
||||
}
|
||||
}
|
||||
|
||||
private static class FallbackBeanRegistrar implements BeanRegistrar {
|
||||
|
||||
@Override
|
||||
public void register(BeanRegistry registry, Environment env) {
|
||||
registry.registerBean("foo", Foo.class, BeanRegistry.Spec::fallback);
|
||||
}
|
||||
}
|
||||
|
||||
private static class InfrastructureBeanRegistrar implements BeanRegistrar {
|
||||
|
||||
@Override
|
||||
public void register(BeanRegistry registry, Environment env) {
|
||||
registry.registerBean("foo", Foo.class, BeanRegistry.Spec::infrastructure);
|
||||
}
|
||||
}
|
||||
|
||||
private static class LazyInitBeanRegistrar implements BeanRegistrar {
|
||||
|
||||
@Override
|
||||
public void register(BeanRegistry registry, Environment env) {
|
||||
registry.registerBean("foo", Foo.class, BeanRegistry.Spec::lazyInit);
|
||||
}
|
||||
}
|
||||
|
||||
private static class NotAutowirableBeanRegistrar implements BeanRegistrar {
|
||||
|
||||
@Override
|
||||
public void register(BeanRegistry registry, Environment env) {
|
||||
registry.registerBean("foo", Foo.class, BeanRegistry.Spec::notAutowirable);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CustomOrderBeanRegistrar implements BeanRegistrar {
|
||||
|
||||
@Override
|
||||
public void register(BeanRegistry registry, Environment env) {
|
||||
registry.registerBean("foo", Foo.class, spec -> spec.order(1));
|
||||
}
|
||||
}
|
||||
|
||||
private static class PrimaryBeanRegistrar implements BeanRegistrar {
|
||||
|
||||
@Override
|
||||
public void register(BeanRegistry registry, Environment env) {
|
||||
registry.registerBean("foo", Foo.class, BeanRegistry.Spec::primary);
|
||||
}
|
||||
}
|
||||
|
||||
private static class PrototypeBeanRegistrar implements BeanRegistrar {
|
||||
|
||||
@Override
|
||||
public void register(BeanRegistry registry, Environment env) {
|
||||
registry.registerBean("foo", Foo.class, BeanRegistry.Spec::prototype);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SupplierBeanRegistrar implements BeanRegistrar {
|
||||
|
||||
@Override
|
||||
public void register(BeanRegistry registry, Environment env) {
|
||||
registry.registerBean("foo", Foo.class, spec -> spec.supplier(context -> new Foo()));
|
||||
}
|
||||
}
|
||||
|
||||
private static class Foo {}
|
||||
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import java.util.Set;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.factory.BeanRegistrar;
|
||||
import org.springframework.beans.factory.parsing.Location;
|
||||
import org.springframework.beans.factory.parsing.Problem;
|
||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||
@@ -65,6 +66,8 @@ final class ConfigurationClass {
|
||||
private final Map<String, Class<? extends BeanDefinitionReader>> importedResources =
|
||||
new LinkedHashMap<>();
|
||||
|
||||
private final Set<BeanRegistrar> beanRegistrars = new LinkedHashSet<>();
|
||||
|
||||
private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
|
||||
new LinkedHashMap<>();
|
||||
|
||||
@@ -219,6 +222,14 @@ final class ConfigurationClass {
|
||||
return this.importedResources;
|
||||
}
|
||||
|
||||
void addBeanRegistrar(BeanRegistrar beanRegistrar) {
|
||||
this.beanRegistrars.add(beanRegistrar);
|
||||
}
|
||||
|
||||
public Set<BeanRegistrar> getBeanRegistrars() {
|
||||
return this.beanRegistrars;
|
||||
}
|
||||
|
||||
void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
|
||||
this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -29,6 +29,8 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.BeanRegistrar;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
@@ -41,6 +43,7 @@ import org.springframework.beans.factory.support.BeanDefinitionOverrideException
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReader;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.beans.factory.support.BeanRegistryAdapter;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
|
||||
import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase;
|
||||
@@ -146,7 +149,8 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
}
|
||||
|
||||
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
|
||||
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
|
||||
loadBeanDefinitionsFromImportBeanDefinitionRegistrars(configClass.getImportBeanDefinitionRegistrars());
|
||||
loadBeanDefinitionsFromBeanRegistrars(configClass.getBeanRegistrars());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -395,11 +399,19 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
});
|
||||
}
|
||||
|
||||
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
|
||||
private void loadBeanDefinitionsFromImportBeanDefinitionRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
|
||||
registrars.forEach((registrar, metadata) ->
|
||||
registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
|
||||
}
|
||||
|
||||
private void loadBeanDefinitionsFromBeanRegistrars(Set<BeanRegistrar> registrars) {
|
||||
Assert.isInstanceOf(ListableBeanFactory.class, this.registry,
|
||||
"Cannot support bean registrars since " + this.registry.getClass().getName() +
|
||||
" does not implement BeanDefinitionRegistry");
|
||||
registrars.forEach(registrar -> registrar.register(new BeanRegistryAdapter(this.registry,
|
||||
(ListableBeanFactory) this.registry, registrar.getClass()), this.environment));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link RootBeanDefinition} marker subclass used to signify that a bean definition
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.BeanRegistrar;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
@@ -597,6 +598,13 @@ class ConfigurationClassParser {
|
||||
processImports(configClass, currentSourceClass, importSourceClasses, filter, false);
|
||||
}
|
||||
}
|
||||
else if (candidate.isAssignable(BeanRegistrar.class)) {
|
||||
Class<?> candidateClass = candidate.loadClass();
|
||||
BeanRegistrar registrar =
|
||||
ParserStrategyUtils.instantiateClass(candidateClass, BeanRegistrar.class,
|
||||
this.environment, this.resourceLoader, this.registry);
|
||||
configClass.addBeanRegistrar(registrar);
|
||||
}
|
||||
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
|
||||
// Candidate class is an ImportBeanDefinitionRegistrar ->
|
||||
// delegate to it to register additional bean definitions
|
||||
|
||||
@@ -20,7 +20,9 @@ import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -40,8 +42,12 @@ import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
|
||||
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.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;
|
||||
@@ -49,7 +55,10 @@ import org.springframework.beans.PropertyValues;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanRegistrar;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.aot.AotServices;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
|
||||
@@ -60,6 +69,7 @@ import org.springframework.beans.factory.aot.BeanRegistrationCodeFragments;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationCodeFragmentsDecorator;
|
||||
import org.springframework.beans.factory.aot.InstanceSupplierCodeGenerator;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionCustomizer;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
@@ -73,6 +83,7 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.beans.factory.support.BeanRegistryAdapter;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RegisteredBean.InstantiationDescriptor;
|
||||
@@ -99,6 +110,7 @@ import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.CodeBlock.Builder;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
@@ -106,6 +118,10 @@ import org.springframework.javapoet.ParameterizedTypeName;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link BeanFactoryPostProcessor} used for bootstrapping processing of
|
||||
@@ -181,6 +197,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
@SuppressWarnings("NullAway.Init")
|
||||
private List<PropertySourceDescriptor> propertySourceDescriptors;
|
||||
|
||||
private Set<BeanRegistrar> beanRegistrars = new LinkedHashSet<>();
|
||||
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
@@ -323,7 +341,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
public @Nullable BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
|
||||
boolean hasPropertySourceDescriptors = !CollectionUtils.isEmpty(this.propertySourceDescriptors);
|
||||
boolean hasImportRegistry = beanFactory.containsBean(IMPORT_REGISTRY_BEAN_NAME);
|
||||
if (hasPropertySourceDescriptors || hasImportRegistry) {
|
||||
boolean hasBeanRegistrars = !this.beanRegistrars.isEmpty();
|
||||
if (hasPropertySourceDescriptors || hasImportRegistry || hasBeanRegistrars) {
|
||||
return (generationContext, code) -> {
|
||||
if (hasPropertySourceDescriptors) {
|
||||
new PropertySourcesAotContribution(this.propertySourceDescriptors, this::resolvePropertySourceLocation)
|
||||
@@ -332,6 +351,9 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
if (hasImportRegistry) {
|
||||
new ImportAwareAotContribution(beanFactory).applyTo(generationContext, code);
|
||||
}
|
||||
if (hasBeanRegistrars) {
|
||||
new BeanRegistrarAotContribution(this.beanRegistrars, beanFactory).applyTo(generationContext, code);
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
@@ -420,6 +442,9 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
this.importBeanNameGenerator, parser.getImportRegistry());
|
||||
}
|
||||
this.reader.loadBeanDefinitions(configClasses);
|
||||
for (ConfigurationClass configClass : configClasses) {
|
||||
this.beanRegistrars.addAll(configClass.getBeanRegistrars());
|
||||
}
|
||||
alreadyParsed.addAll(configClasses);
|
||||
processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();
|
||||
|
||||
@@ -815,4 +840,182 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
}
|
||||
}
|
||||
|
||||
private static class BeanRegistrarAotContribution implements BeanFactoryInitializationAotContribution {
|
||||
|
||||
private static final String CUSTOMIZER_MAP_VARIABLE = "customizers";
|
||||
|
||||
private static final String ENVIRONMENT_VARIABLE = "environment";
|
||||
|
||||
private final Set<BeanRegistrar> beanRegistrars;
|
||||
|
||||
private final ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
private final AotServices<BeanRegistrationAotProcessor> aotProcessors;
|
||||
|
||||
public BeanRegistrarAotContribution(Set<BeanRegistrar> beanRegistrars, ConfigurableListableBeanFactory beanFactory) {
|
||||
this.beanRegistrars = beanRegistrars;
|
||||
this.beanFactory = beanFactory;
|
||||
this.aotProcessors = AotServices.factoriesAndBeans(this.beanFactory).load(BeanRegistrationAotProcessor.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) {
|
||||
GeneratedMethod generatedMethod = beanFactoryInitializationCode.getMethods().add(
|
||||
"applyBeanRegistrars", builder -> this.generateApplyBeanRegistrarsMethod(builder, generationContext));
|
||||
beanFactoryInitializationCode.addInitializer(generatedMethod.toMethodReference());
|
||||
}
|
||||
|
||||
private void generateApplyBeanRegistrarsMethod(MethodSpec.Builder method, GenerationContext generationContext) {
|
||||
ReflectionHints reflectionHints = generationContext.getRuntimeHints().reflection();
|
||||
method.addJavadoc("Apply bean registrars.");
|
||||
method.addModifiers(Modifier.PRIVATE);
|
||||
method.addParameter(ListableBeanFactory.class, BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE);
|
||||
method.addParameter(Environment.class, ENVIRONMENT_VARIABLE);
|
||||
method.addCode(generateCustomizerMap());
|
||||
|
||||
for (String name : this.beanFactory.getBeanDefinitionNames()) {
|
||||
BeanDefinition beanDefinition = this.beanFactory.getMergedBeanDefinition(name);
|
||||
if (beanDefinition.getSource() instanceof Class<?> sourceClass
|
||||
&& BeanRegistrar.class.isAssignableFrom(sourceClass)) {
|
||||
|
||||
for (BeanRegistrationAotProcessor aotProcessor : this.aotProcessors) {
|
||||
BeanRegistrationAotContribution contribution =
|
||||
aotProcessor.processAheadOfTime(RegisteredBean.of(this.beanFactory, name));
|
||||
if (contribution != null) {
|
||||
contribution.applyTo(generationContext,
|
||||
new UnsupportedBeanRegistrationCode(name, aotProcessor.getClass()));
|
||||
}
|
||||
}
|
||||
if (beanDefinition instanceof RootBeanDefinition rootBeanDefinition) {
|
||||
if (rootBeanDefinition.getPreferredConstructors() != null) {
|
||||
for (Constructor<?> constructor : rootBeanDefinition.getPreferredConstructors()) {
|
||||
reflectionHints.registerConstructor(constructor, ExecutableMode.INVOKE);
|
||||
}
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(rootBeanDefinition.getInitMethodNames())) {
|
||||
method.addCode(generateInitDestroyMethods(name, rootBeanDefinition,
|
||||
rootBeanDefinition.getInitMethodNames(), "setInitMethodNames", reflectionHints));
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(rootBeanDefinition.getDestroyMethodNames())) {
|
||||
method.addCode(generateInitDestroyMethods(name, rootBeanDefinition,
|
||||
rootBeanDefinition.getDestroyMethodNames(), "setDestroyMethodNames", reflectionHints));
|
||||
}
|
||||
checkUnsupportedFeatures(rootBeanDefinition);
|
||||
}
|
||||
}
|
||||
}
|
||||
method.addCode(generateRegisterCode());
|
||||
}
|
||||
|
||||
private void checkUnsupportedFeatures(AbstractBeanDefinition beanDefinition) {
|
||||
if (!ObjectUtils.isEmpty(beanDefinition.getFactoryBeanName())) {
|
||||
throw new UnsupportedOperationException("AOT post processing of the factory bean name is not supported yet with BeanRegistrar");
|
||||
}
|
||||
if (beanDefinition.hasConstructorArgumentValues()) {
|
||||
throw new UnsupportedOperationException("AOT post processing of argument values is not supported yet with BeanRegistrar");
|
||||
}
|
||||
if (!beanDefinition.getQualifiers().isEmpty()) {
|
||||
throw new UnsupportedOperationException("AOT post processing of qualifiers is not supported yet with BeanRegistrar");
|
||||
}
|
||||
for (String attributeName : beanDefinition.attributeNames()) {
|
||||
if (!attributeName.equals(AbstractBeanDefinition.ORDER_ATTRIBUTE)
|
||||
&& !attributeName.equals("aotProcessingIgnoreRegistration")) {
|
||||
throw new UnsupportedOperationException("AOT post processing of attribute " + attributeName +
|
||||
" is not supported yet with BeanRegistrar");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CodeBlock generateCustomizerMap() {
|
||||
Builder code = CodeBlock.builder();
|
||||
code.addStatement("$T<$T, $T> $L = new $T<>()", MultiValueMap.class, String.class, BeanDefinitionCustomizer.class,
|
||||
CUSTOMIZER_MAP_VARIABLE, LinkedMultiValueMap.class);
|
||||
return code.build();
|
||||
}
|
||||
|
||||
private CodeBlock generateRegisterCode() {
|
||||
Builder code = CodeBlock.builder();
|
||||
for (BeanRegistrar beanRegistrar : this.beanRegistrars) {
|
||||
code.addStatement("new $T().register(new $T(($T)$L, $L, $T.class, $L), $L)", beanRegistrar.getClass(),
|
||||
BeanRegistryAdapter.class, BeanDefinitionRegistry.class, BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE,
|
||||
BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE, beanRegistrar.getClass(), CUSTOMIZER_MAP_VARIABLE,
|
||||
ENVIRONMENT_VARIABLE);
|
||||
}
|
||||
return code.build();
|
||||
}
|
||||
|
||||
private CodeBlock generateInitDestroyMethods(String beanName, AbstractBeanDefinition beanDefinition,
|
||||
String[] methodNames, String method, ReflectionHints reflectionHints) {
|
||||
|
||||
Builder code = CodeBlock.builder();
|
||||
// For Publisher-based destroy methods
|
||||
reflectionHints.registerType(TypeReference.of("org.reactivestreams.Publisher"));
|
||||
Class<?> beanType = ClassUtils.getUserClass(beanDefinition.getResolvableType().toClass());
|
||||
Arrays.stream(methodNames).forEach(methodName -> addInitDestroyHint(beanType, methodName, reflectionHints));
|
||||
CodeBlock arguments = Arrays.stream(methodNames)
|
||||
.map(name -> CodeBlock.of("$S", name))
|
||||
.collect(CodeBlock.joining(", "));
|
||||
|
||||
code.addStatement("$L.add($S, $L -> (($T)$L).$L($L))", CUSTOMIZER_MAP_VARIABLE, beanName, "bd",
|
||||
AbstractBeanDefinition.class, "bd", method, arguments);
|
||||
return code.build();
|
||||
}
|
||||
|
||||
// Inspired from BeanDefinitionPropertiesCodeGenerator#addInitDestroyHint
|
||||
private static void addInitDestroyHint(Class<?> beanUserClass, String methodName, ReflectionHints reflectionHints) {
|
||||
Class<?> methodDeclaringClass = beanUserClass;
|
||||
|
||||
// Parse fully-qualified method name if necessary.
|
||||
int indexOfDot = methodName.lastIndexOf('.');
|
||||
if (indexOfDot > 0) {
|
||||
String className = methodName.substring(0, indexOfDot);
|
||||
methodName = methodName.substring(indexOfDot + 1);
|
||||
if (!beanUserClass.getName().equals(className)) {
|
||||
try {
|
||||
methodDeclaringClass = ClassUtils.forName(className, beanUserClass.getClassLoader());
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new IllegalStateException("Failed to load Class [" + className +
|
||||
"] from ClassLoader [" + beanUserClass.getClassLoader() + "]", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Method method = ReflectionUtils.findMethod(methodDeclaringClass, methodName);
|
||||
if (method != null) {
|
||||
reflectionHints.registerMethod(method, ExecutableMode.INVOKE);
|
||||
Method publiclyAccessibleMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(method, beanUserClass);
|
||||
if (!publiclyAccessibleMethod.equals(method)) {
|
||||
reflectionHints.registerMethod(publiclyAccessibleMethod, ExecutableMode.INVOKE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class UnsupportedBeanRegistrationCode implements BeanRegistrationCode {
|
||||
|
||||
private final String message;
|
||||
|
||||
public UnsupportedBeanRegistrationCode(String beanName, Class<?> aotProcessorClass) {
|
||||
this.message = "Code generation attempted for bean " + beanName + " by the AOT Processor " +
|
||||
aotProcessorClass + " is not supported with BeanRegistrar yet";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassName getClassName() {
|
||||
throw new UnsupportedOperationException(this.message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeneratedMethods getMethods() {
|
||||
throw new UnsupportedOperationException(this.message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInstancePostProcessor(MethodReference methodReference) {
|
||||
throw new UnsupportedOperationException(this.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -22,6 +22,8 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.BeanRegistrar;
|
||||
|
||||
/**
|
||||
* Indicates one or more <em>component classes</em> to import — typically
|
||||
* {@link Configuration @Configuration} classes.
|
||||
@@ -57,7 +59,8 @@ public @interface Import {
|
||||
|
||||
/**
|
||||
* {@link Configuration @Configuration}, {@link ImportSelector},
|
||||
* {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
|
||||
* {@link ImportBeanDefinitionRegistrar}, {@link BeanRegistrar} or regular
|
||||
* component classes to import.
|
||||
*/
|
||||
Class<?>[] value();
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ 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.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
@@ -150,6 +151,7 @@ class ApplicationContextInitializationCodeGenerator implements BeanFactoryInitia
|
||||
private @Nullable CodeBlock apply(ClassName className) {
|
||||
String name = className.canonicalName();
|
||||
if (name.equals(DefaultListableBeanFactory.class.getName())
|
||||
|| name.equals(ListableBeanFactory.class.getName())
|
||||
|| name.equals(ConfigurableListableBeanFactory.class.getName())) {
|
||||
return CodeBlock.of(BEAN_FACTORY_VARIABLE);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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,7 @@
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
@@ -24,6 +25,7 @@ import java.util.function.Predicate;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
@@ -37,7 +39,10 @@ import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanRegistrar;
|
||||
import org.springframework.beans.factory.BeanRegistry;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
@@ -54,6 +59,7 @@ import org.springframework.context.testfixture.context.annotation.SimpleConfigur
|
||||
import org.springframework.context.testfixture.context.generator.SimpleComponent;
|
||||
import org.springframework.core.Ordered;
|
||||
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.Compiled;
|
||||
@@ -74,8 +80,9 @@ import static org.assertj.core.api.Assertions.entry;
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @author Sam Brannen
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class ConfigurationClassPostProcessorAotContributionTests {
|
||||
public class ConfigurationClassPostProcessorAotContributionTests {
|
||||
|
||||
private final TestGenerationContext generationContext = new TestGenerationContext();
|
||||
|
||||
@@ -439,6 +446,135 @@ class ConfigurationClassPostProcessorAotContributionTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
public class BeanRegistrarTests {
|
||||
|
||||
@Test
|
||||
void applyToWhenHasDefaultConstructor() throws NoSuchMethodException {
|
||||
BeanFactoryInitializationAotContribution contribution = getContribution(DefaultConstructorConfiguration.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
|
||||
void applyToWhenHasInstanceSupplier() {
|
||||
BeanFactoryInitializationAotContribution contribution = getContribution(InstanceSupplierConfiguration.class);
|
||||
assertThat(contribution).isNotNull();
|
||||
contribution.applyTo(generationContext, beanFactoryInitializationCode);
|
||||
compile((initializer, compiled) -> {
|
||||
GenericApplicationContext freshContext = new GenericApplicationContext();
|
||||
initializer.accept(freshContext);
|
||||
freshContext.refresh();
|
||||
assertThat(freshContext.getBean(Foo.class)).isNotNull();
|
||||
assertThat(generationContext.getRuntimeHints().reflection().getTypeHint(Foo.class)).isNull();
|
||||
freshContext.close();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyToWhenHasPostConstructAnnotationPostProcessed() {
|
||||
BeanFactoryInitializationAotContribution contribution = getContribution(CommonAnnotationBeanPostProcessor.class,
|
||||
PostConstructConfiguration.class);
|
||||
assertThat(contribution).isNotNull();
|
||||
contribution.applyTo(generationContext, beanFactoryInitializationCode);
|
||||
compile((initializer, compiled) -> {
|
||||
GenericApplicationContext freshContext = new GenericApplicationContext();
|
||||
initializer.accept(freshContext);
|
||||
freshContext.refresh();
|
||||
Init init = freshContext.getBean(Init.class);
|
||||
assertThat(init).isNotNull();
|
||||
assertThat(init.initialized).isTrue();
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(Init.class, "postConstruct"))
|
||||
.accepts(generationContext.getRuntimeHints());
|
||||
freshContext.close();
|
||||
});
|
||||
}
|
||||
|
||||
private void compile(BiConsumer<Consumer<GenericApplicationContext>, Compiled> result) {
|
||||
MethodReference methodReference = beanFactoryInitializationCode.getInitializers().get(0);
|
||||
beanFactoryInitializationCode.getTypeBuilder().set(type -> {
|
||||
ArgumentCodeGenerator argCodeGenerator = ArgumentCodeGenerator
|
||||
.of(ListableBeanFactory.class, "applicationContext.getBeanFactory()")
|
||||
.and(ArgumentCodeGenerator.of(Environment.class, "applicationContext.getEnvironment()"));
|
||||
CodeBlock methodInvocation = methodReference.toInvokeCodeBlock(argCodeGenerator,
|
||||
beanFactoryInitializationCode.getClassName());
|
||||
type.addModifiers(Modifier.PUBLIC);
|
||||
type.addSuperinterface(ParameterizedTypeName.get(Consumer.class, GenericApplicationContext.class));
|
||||
type.addMethod(MethodSpec.methodBuilder("accept").addModifiers(Modifier.PUBLIC)
|
||||
.addParameter(GenericApplicationContext.class, "applicationContext")
|
||||
.addStatement(methodInvocation)
|
||||
.build());
|
||||
});
|
||||
generationContext.writeGeneratedContent();
|
||||
TestCompiler.forSystem().with(generationContext).compile(compiled ->
|
||||
result.accept(compiled.getInstance(Consumer.class), compiled));
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@Import(DefaultConstructorBeanRegistrar.class)
|
||||
public static class DefaultConstructorConfiguration {
|
||||
}
|
||||
|
||||
public static class DefaultConstructorBeanRegistrar implements BeanRegistrar {
|
||||
|
||||
@Override
|
||||
public void register(BeanRegistry registry, Environment env) {
|
||||
registry.registerBean(Foo.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import(InstanceSupplierBeanRegistrar.class)
|
||||
public static class InstanceSupplierConfiguration {
|
||||
}
|
||||
|
||||
public static class InstanceSupplierBeanRegistrar implements BeanRegistrar {
|
||||
|
||||
@Override
|
||||
public void register(BeanRegistry registry, Environment env) {
|
||||
registry.registerBean(Foo.class, spec -> spec.supplier(context -> new Foo()));
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import(PostConstructBeanRegistrar.class)
|
||||
public static class PostConstructConfiguration {
|
||||
}
|
||||
|
||||
public static class PostConstructBeanRegistrar implements BeanRegistrar {
|
||||
|
||||
@Override
|
||||
public void register(BeanRegistry registry, Environment env) {
|
||||
registry.registerBean(Init.class);
|
||||
}
|
||||
}
|
||||
|
||||
static class Foo {
|
||||
}
|
||||
|
||||
static class Init {
|
||||
|
||||
boolean initialized = false;
|
||||
|
||||
@PostConstruct
|
||||
void postConstruct() {
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private @Nullable BeanFactoryInitializationAotContribution getContribution(Class<?>... types) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.annotation.beanregistrar;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanRegistrar;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar.Bar;
|
||||
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar.Baz;
|
||||
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar.Foo;
|
||||
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar.Init;
|
||||
import org.springframework.context.testfixture.context.annotation.registrar.BeanRegistrarConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link BeanRegistrar} imported by @{@link org.springframework.context.annotation.Configuration}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class BeanRegistrarConfigurationTests {
|
||||
|
||||
@Test
|
||||
void beanRegistrar() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanRegistrarConfiguration.class);
|
||||
assertThat(context.getBean(Bar.class).foo()).isEqualTo(context.getBean(Foo.class));
|
||||
assertThatThrownBy(() -> context.getBean(Baz.class)).isInstanceOf(NoSuchBeanDefinitionException.class);
|
||||
assertThat(context.getBean(Init.class).initialized).isTrue();
|
||||
BeanDefinition beanDefinition = context.getBeanDefinition("bar");
|
||||
assertThat(beanDefinition.getScope()).isEqualTo(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
assertThat(beanDefinition.isLazyInit()).isTrue();
|
||||
assertThat(beanDefinition.getDescription()).isEqualTo("Custom description");
|
||||
}
|
||||
|
||||
@Test
|
||||
void beanRegistrarWithProfile() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.register(BeanRegistrarConfiguration.class);
|
||||
context.getEnvironment().addActiveProfile("baz");
|
||||
context.refresh();
|
||||
assertThat(context.getBean(Baz.class).message()).isEqualTo("Hello World!");
|
||||
}
|
||||
|
||||
@Test
|
||||
void scannedFunctionalConfiguration() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.scan("org.springframework.context.testfixture.context.annotation.registrar");
|
||||
context.refresh();
|
||||
assertThat(context.getBean(Bar.class).foo()).isEqualTo(context.getBean(Foo.class));
|
||||
assertThatThrownBy(() -> context.getBean(Baz.class).message()).isInstanceOf(NoSuchBeanDefinitionException.class);
|
||||
assertThat(context.getBean(Init.class).initialized).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.testfixture.beans.factory;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
import org.springframework.beans.factory.BeanRegistrar;
|
||||
import org.springframework.beans.factory.BeanRegistry;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
public class SampleBeanRegistrar implements BeanRegistrar {
|
||||
|
||||
@Override
|
||||
public void register(BeanRegistry registry, Environment env) {
|
||||
registry.registerBean("foo", Foo.class);
|
||||
registry.registerBean("bar", Bar.class, spec -> spec
|
||||
.prototype()
|
||||
.lazyInit()
|
||||
.description("Custom description")
|
||||
.supplier(context -> new Bar(context.bean(Foo.class))));
|
||||
if (env.matchesProfiles("baz")) {
|
||||
registry.registerBean(Baz.class, spec -> spec
|
||||
.supplier(context -> new Baz("Hello World!")));
|
||||
}
|
||||
registry.registerBean(Init.class);
|
||||
}
|
||||
|
||||
public record Foo() {}
|
||||
public record Bar(Foo foo) {}
|
||||
public record Baz(String message) {}
|
||||
|
||||
public static class Init {
|
||||
|
||||
public boolean initialized = false;
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.testfixture.context.annotation.registrar;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.testfixture.beans.factory.SampleBeanRegistrar;
|
||||
|
||||
@Configuration
|
||||
@Import(SampleBeanRegistrar.class)
|
||||
public class BeanRegistrarConfiguration {
|
||||
}
|
||||
Reference in New Issue
Block a user