Reputation: 23042
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:
@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.
Upvotes: 9
Views: 3443
Reputation: 116171
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
Upvotes: 13