I'm trying to use the @ConfigurationCondition
annotation to register a bean based on a dynamic condition. This dynamic condition is present in another bean.
Putting the code below to better describe this :
Below the class ImplementationA.java
which I want to instantiate only if a certain condition is true. I've used the @Conditional
annotation here on this class to achieve this.
@Component
@Conditional(MyConfigurationCondition.class)
public class ImplementationA{
@Override
public void printIt() {
System.out.println("Inside ImplementationA");
}
}
Below is the MyConfigurationCondition.java
which has been passed to the @Conditional
annotation in the above ImplementationA.java
.
public class MyConfigurationCondition implements ConfigurationCondition {
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
try {
MyConfig myConfig = (MyConfig)context.getBeanFactory().getBean(MyConfig.class);
if(myConfig.getCustomFlag()){
return true;
}
return false;
}
}
Notice in the above code, we are returning true using a function(getCustomFlag()
) of the bean MyConfig.class
. And we are trying to get this bean from the applicationContext
(this is the reason I've returned ConfigurationPhase.REGISTER_BEAN
from the overridden getConfigurationPhase
method)
Below the code for the MyConfig.class
@Component
public class MyConfig {
private final Component1 component1;
private final Component2 component2;
@Autowired
public MyConfig(Component1 component1, Component2 component2) {
System.out.println("Inside MyConfig(Component1,Component2)");
this.component1 = component1;
this.component2 = component2;
}
public getCustomFlag(){
return component1.flag() && component2.flag();
}
}
But when I run the Spring Boot application, I get this exception:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myConfig' defined in file [com.config.MyConfig.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.config.MyConfig]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.config.MyConfig.<init>()
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1334)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:233)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1282)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1243)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:494)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
at com.service.condition.MyConfigurationCondition.matches(MyConfigurationCondition.java:37)
at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:108)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$TrackedConditionEvaluator.shouldSkip(ConfigurationClassBeanDefinitionReader.java:489)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:140)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:129)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:343)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:247)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:311)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:112)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:746)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:564)
at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:66)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:308)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295)
at com.sagnik.ReactiveWebApplication.ReactiveWebApplication.main(ReactiveWebApplication.java:16)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.config.MyConfig]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.config.MyConfig.<init>()
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:83)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1326)
... 30 more
Caused by: java.lang.NoSuchMethodException: com.config.MyConfig.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3585)
at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2754)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:78)
... 31 more
I can understand from the exception that inside the matches
method of MyConfigurationCondition.java
, when spring encounters the line MyConfig myConfig = (MyConfig)context.getBeanFactory().getBean(MyConfig.class);
, it tries to instantiate MyConfig.java
with the default constructor which is ofcourse not available in the class definition. My question is why is Spring trying to instantiate it manually using the default constructor when this bean should be available by default(instantiate using Autowired constructor) once the component scanning is completed.
Any help would be hugely appreciated.
CodePudding user response:
To me it looks like an issue in the design actually
Spring's @Conditional
-s are meant to be evaluated during the spring startup.
When spring builds an application context - it decides which beans to load. So it uses various conditions to see whether the bean should actually be loaded into the application context. But in general, beans do not exist when this process happens.
However, you're trying to create a conditional that assumes that spring beans are already there. So even if you'll make it work somehow I think you should slightly redesign the application logic to make it work in a spring-ish way.
To provide the actual refactoring guidelines I need to understand the logic more:
- These
Component1
andComponent2
seem to evaluate some kind of feature flag but they're also regular spring beans. So I understand that when you callcomponent1.flag() && component2.flag()
in general you can get different results in different times. Otherwise it's just a static configuration or something. In other words, the resolution of the feature flag value is a part of your business logic.
But if so, maybe your Implementation
class should not have a Conditional
on it and be loaded in any case to the application context:
@Component
@Conditional(MyConfigurationCondition.class)
public class ImplementationA{
MyConfig config;
@Override
public void printIt() {
if(config.getCustomFlag()) {
System.out.println("Inside ImplementationA");
}
}
}
Alternatively if its a configuration, you can access it from @Conditional
-s but in this case they should not be spring beans.
CodePudding user response:
Because AutowiredAnnotationBeanPostProcessor
has not been registered, the constructor of MyConfig cannot be determined. In the end, simply use the no-argument constructor.