You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
One of the application team reported an issue and I found this slightly unintuitive behavior between condition evaluation of @ConditionalOn*Bean and spring's autowiring resolution for method argument.
Positive matches:
-----------------
AnotherApplication.Config#init matched:
- @ConditionalOnSingleCandidate (types: com.example.AnotherApplication$Child; SearchStrategy: all) found a primary bean from beans 'bar' (OnBeanCondition)
Failure:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method init in com.example.AnotherApplication$Config required a single bean, but 2 were found:
- foo: defined by method 'foo' in com.example.AnotherApplication$Config
- bar: defined by method 'bar' in com.example.AnotherApplication$Config
Analysis
While performing OnBeanCondition(@ConditionalOnSingleCandidate), it compares the target type(Child) against factory-method-return-type of each @Bean - Parent for bean foo and Child for bar.
Therefore, Child matches to bar bean and satisfies the condition.
The details of this process is, in OnBeanCondition, it calls beanFactory.getBeanNamesForType to collect bean names for the specified type(Child). In that, it calls AbstractBeanFactory#getSingleton. At this time, spring did not create raw beans yet, thus, the beanInstance becomes null. Then, it goes to check the bean definition's factoryMethodReturnType against specified condition type Child.
On the other hand, when spring performs autowiring for method arguments, it also calls beanFactory.getBeanNamesForType. At this time, raw beans are available, and it finds the actual bean instances. Then, it proceeds to perform instanceof check against the method argument type Child.
This instanceof matches to both foo(Parent) and bar(Child) beans. Therefore, it detects two beans and fails to resolve which one to apply for the method argument.
There is another not intuitive behavior.
In following situation, it will not match the condition because the return type of foo is Parent.
Negative matches:
-----------------
AnotherApplication.Config#init:
Did not match:
- @ConditionalOnBean (types: com.example.AnotherApplication$Child; SearchStrategy: all) did not find any beans of type com.example.AnotherApplication$Child (OnBeanCondition)
However, once I add Child bar() bean, suddenly the condition matches but autoconfiguration for Child fails with finding two beans - foo and bar.
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method init in com.example.AnotherApplication$Config required a single bean, but 2 were found:
- foo: defined by method 'foo' in com.example.AnotherApplication$Config
- bar: defined by method 'bar' in com.example.AnotherApplication$Config
This suddenly matching multiple beans issue has reported in our application and I found this behavior.
I understand this is possibly working as designed.
The difference is due to the behavior of beanfactory.getBeanNamesForType which changes slightly different based on the stage this method is called. And also due to that bean instances cannot be checked while processing conditions.
It might be a corner case, but may be good to document such behavior on @ConditonalOn*Bean annotations.
The text was updated successfully, but these errors were encountered:
As you have surmised, this is working as designed. Condition evaluation can only use the type information that's available in the bean factory. It cannot use any information that's only available once bean creation has begun as doing so would trigger unwanted bean initialization. It's also worth noting that things can behave in a similar way during autowiring if the type information to identify a match isn't available until a bean has been created. In Spring in general it's a good idea for your bean definitions to provide as much type information as possible, but certainly more so when bean-based conditions come into play. We had an issue in the past to provide more information in Boot's own configuration. Some updates to the documentation sound like a good idea to me. Thanks for the suggestion.
wilkinsona
changed the title
Different bean resolution in @ConditionalOn*Bean vs autowiring
Recommend that bean definitions provide as much type information as possible
Aug 13, 2020
One of the application team reported an issue and I found this slightly unintuitive behavior between condition evaluation of
@ConditionalOn*Bean
and spring's autowiring resolution for method argument.Sample Code:
Condition evaluation report:
Failure:
Analysis
While performing
OnBeanCondition
(@ConditionalOnSingleCandidate
), it compares the target type(Child
) against factory-method-return-type of each@Bean
-Parent
for beanfoo
andChild
forbar
.Therefore,
Child
matches tobar
bean and satisfies the condition.The details of this process is, in
OnBeanCondition
, it callsbeanFactory.getBeanNamesForType
to collect bean names for the specified type(Child
). In that, it callsAbstractBeanFactory#getSingleton
. At this time, spring did not create raw beans yet, thus, thebeanInstance
becomesnull
. Then, it goes to check the bean definition'sfactoryMethodReturnType
against specified condition typeChild
.On the other hand, when spring performs autowiring for method arguments, it also calls
beanFactory.getBeanNamesForType
. At this time, raw beans are available, and it finds the actual bean instances. Then, it proceeds to performinstanceof
check against the method argument typeChild
.This
instanceof
matches to bothfoo
(Parent
) andbar
(Child
) beans. Therefore, it detects two beans and fails to resolve which one to apply for the method argument.There is another not intuitive behavior.
In following situation, it will not match the condition because the return type of
foo
isParent
.However, once I add
Child bar()
bean, suddenly the condition matches but autoconfiguration forChild
fails with finding two beans -foo
andbar
.This suddenly matching multiple beans issue has reported in our application and I found this behavior.
I understand this is possibly working as designed.
The difference is due to the behavior of
beanfactory.getBeanNamesForType
which changes slightly different based on the stage this method is called. And also due to that bean instances cannot be checked while processing conditions.It might be a corner case, but may be good to document such behavior on
@ConditonalOn*Bean
annotations.The text was updated successfully, but these errors were encountered: