I'm trying to create N number of beans dynamically using BeanDefinitionRegistryPostProcessor
. Based off of this question, I opted to use BeanDefinitionRegistryPostProcessor
for my use case.
I have the following defined in my application.yml
:
app:
downstream-services:
rest:
jsonPlaceHolder:
url: https://jsonplaceholder.typicode.com/todos
api-type: io.mateo.dynamicbeans.JsonPlaceHolderApi
Which gets wired up to a ConfigiruationProperties
class here: https://github.com/ciscoo/dynamicbeans/blob/master/src/main/java/io/mateo/dynamicbeans/FeignConfigurationProperties.java
I then want to inject that ConfigiruationProperties
class along with a factory bean that I defined here: https://github.com/ciscoo/dynamicbeans/blob/master/src/main/java/io/mateo/dynamicbeans/FeignClientAutoConfiguration.java
So now I have the following:
https://github.com/ciscoo/dynamicbeans/blob/master/src/main/java/io/mateo/dynamicbeans/FeignClientFactoryPostProcessor.java
@Component
public class FeignClientFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {
private final FeignConfigurationProperties properties;
private final FeignClientFactory feignClientFactory;
public FeignClientFactoryPostProcessor(FeignConfigurationProperties properties, FeignClientFactory feignClientFactory) {
this.properties = properties;
this.feignClientFactory = feignClientFactory;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
properties.getDownstreamServices().getRest().forEach((beanName, props) -> makeClient(beanName, props, registry));
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// no-op
}
private void makeClient(String beanName, FeignClientProperties props, BeanDefinitionRegistry registry) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(props.getApiType());
beanDefinition.setInstanceSupplier(() -> feignClientFactory.create(props));
registry.registerBeanDefinition(beanName, beanDefinition);
}
}
The single bean it should create is to injected in a service class here: https://github.com/ciscoo/dynamicbeans/blob/master/src/main/java/io/mateo/dynamicbeans/JsonPlaceHolderService.java
The problem I'm running into is:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [io.mateo.dynamicbeans.FeignClientFactoryPostProcessor]: No default constructor found; nested exception is java.lang.NoSuchMethodException: io.mateo.dynamicbeans.FeignClientFactoryPostProcessor.<init>()
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:83) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1262) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
... 17 common frames omitted
Caused by: java.lang.NoSuchMethodException: io.mateo.dynamicbeans.FeignClientFactoryPostProcessor.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3350) ~[na:na]
at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2554) ~[na:na]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:78) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
... 18 common frames omitted
But when I remove the final
keyword from the two properties and the defined constructor, I get a NullPointerException
.
So how do I dynamically create N number of beans so that they will be available in time for any of my @Service
classes to use?
I'm aware of https://spring.io/projects/spring-cloud-openfeign. I recreated my issue here to illustrate the same problem I'm having in a different project with dynamically creating SOAP clients.
Update: Doing the following changes: https://github.com/ciscoo/dynamicbeans/commit/4f16de9d03271025cd65d95932a3e854c0619c29, now I am able to accomplish my use case.
The default autowiring is by type, not by name, so when there is more than one bean of the same type, you have to use the @Qualifier annotation.
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor. Extension to the standard BeanFactoryPostProcessor SPI, allowing for the registration of further bean definitions before regular BeanFactoryPostProcessor detection kicks in.
As the answer to the question that you have linked to suggests, you can’t inject dependencies into a bean factory post-processor. Rather than injecting your configuration properties class, you’ll need to bind it programmatically. In Spring Boot 2.x, that is achieved using the Binder
API:
The new Binder API can also be used outside of
@ConfigurationProperties
directly in your own code. For example, the following will bind to aList
ofPersonName
objects:List<PersonName> people = Binder.get(environment) .bind("my.property", Bindable.listOf(PersonName.class)) .orElseThrow(IllegalStateException::new);
The configuration source could be represented in YAML like this:
my: property: - first-name: Jane last-name: Doe - first-name: John last-name: Doe
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With