Here are some more examples on how to generate beans programmatically.
I used to create beans like this
private void createBean(final DefaultListableBeanFactory factory, final String name, final Object bean) {
//apply BeanPostProcessors
final var instance = factory.initializeBean(bean, name);
//creates bean definition, autowire dependent beans if such exists, setPropertyValues
factory.autowireBeanProperties(instance, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true);
//add bean to DefaultSingletonBeanRegistry registeredSingletons and singletonObjects
factory.registerSingleton(name, instance);
}
And it was quite fine. All dependencies were resolved and properties were set. Besides, in some cases it's quite enough to call only SingletonBeanRegistry#registerSingleton(String, Object).
However, when creating beans with generics I faced NoUniqueBeanDefinitionException exception ("expected single matching bean but found ") when they are autowired.
The solution was to register bean definition and set RootBeanDefinition.targetType explicitly.
Finally,
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ResolvableType;
import java.util.function.Supplier;
@Configuration
class CreateBeanConfig {
CreateBeanConfig(final DefaultListableBeanFactory factory) {
createBean(factory, "testIntBean", (Supplier<Integer>)() -> 1, Supplier.class, Integer.class);
createBean(factory, "testStringBean", (Supplier<String>)() -> "testString", Supplier.class, String.class);
}
private void createBean(
final DefaultListableBeanFactory factory,
final String name,
final Object bean,
final Class<?> type,
final Class<?>... generics
) {
final var bd = new RootBeanDefinition();
bd.setTargetType(ResolvableType.forClassWithGenerics(type, generics));
bd.setAutowireCandidate(true);
bd.setScope(BeanDefinition.SCOPE_SINGLETON);
bd.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
factory.registerBeanDefinition(name, bd);
//apply BeanPostProcessors, autowire dependent beans if such exists, setPropertyValues
final var instance = factory.configureBean(bean, name);
//add bean to DefaultSingletonBeanRegistry registeredSingletons and singletonObjects
factory.registerSingleton(name, instance);
}
}
Besides, AbstractAutowireCapableBeanFactory#configureBean(Object,String) requires bean definition created otherwise it throws exception. So, if you want to use this method you need bean definition anyway.
If NoUniqueBeanDefinitionException is not reproduced in your Spring version for the first approach, feel free to use it.
BeanDefinitionRegistryPostProcessor
or do some context hierarchy magic (separate contexts for your modules where the dependencies can register themselves in the parent context) or just do a service lookup instead of letting spring to inject your dependencies (i.e.ApplicationContext#getBeansOfType
). – Artichoke