Conditions evaluation report wrongly reports @ConditionalOnMissingBean
conditions as if it was @ConditionalOnBean
condition for negative matches.
Example:
============================
CONDITIONS EVALUATION REPORT
============================
... skipped text ...
Negative matches:
-----------------
... skipped text ...
UserDetailsServiceAutoConfiguration:
Did not match:
- @ConditionalOnBean (types: org.springframework.security.authentication.AuthenticationManager,org.springframework.security.authentication.AuthenticationProvider,org.springframework.security.core.userdetails.UserDetailsService,org.springframework.security.oauth2.jwt.JwtDecoder,org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; SearchStrategy: all) found beans of type 'org.springframework.security.authentication.AuthenticationManager' authenticationManager and found beans of type 'org.springframework.security.core.userdetails.UserDetailsService' myUserDetailsService and found beans of type 'org.springframework.security.authentication.AuthenticationProvider' anonymousUserAuthenticationProvider, myAuthenticationProvider (OnBeanCondition)
Matched:
- @ConditionalOnClass found required class 'org.springframework.security.authentication.AuthenticationManager' (OnClassCondition)
Here, we see in the report Did not match: @ConditionalOnBean...
where it is actually a report for @ConditionalOnMissingBean
condition from UserDetailsServiceAutoConfiguration
class.
So, it works right, but mistakenly prints @ConditionalOnBean
instead of @ConditionalOnMissingBean
.
Spring Boot 2.2.1.RELEASE
Comment From: wilkinsona
Thanks for the report, @xak2000.
On first look, I suspect this is specific to the case where we have metadata for the auto-configuration class that allows it to be filtered out early:
https://github.com/spring-projects/spring-boot/blob/c3786e727fc6bb80deb0f830a6fc2537de73e3a1/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java#L82-L99
Comment From: jcordoba95
Hello! If no one else is working on this, I would like to work on this.
Comment From: wilkinsona
That would be great, thank you @jcordoba95. Please let us know if you have any questions while working on it.
Comment From: OLPMO
I made some debug and found that this bug is caused by a wrong construction of log information.
In getMatchOutcome
of OnBeanCondition
, the spec.message(ConditionalOnMissingBean.class).because(reason)
would be called for constructing the log message.However,the constructor would only generate the log message of
org.springframework.boot.autoconfigure.condition.ConditionalOnBean
ConditionMessage.Builder message() {
return ConditionMessage.forCondition(ConditionalOnBean.class, this);
}
So, my solution is that pass the info of class to the ConditionMessage.Builder message()
in order to construct the correct log . Like this
ConditionMessage.Builder message(Class<? extends Annotation> clazz) {
return ConditionMessage.forCondition(clazz, this);
}
Is my idea correct? Besides, I want to ask where is the test function of org.springframework.boot.autoconfigure.condition.OnBeanCondition.Spec#message(java.lang.Class<? extends java.lang.annotation.Annotation>)
? @wilkinsona
Comment From: wilkinsona
Thanks for taking a look, @OLPMO, but @jcordoba95 is already working on this issue.
Comment From: OLPMO
OK, I hope to contribute code to the project next time.Thanks for your reply.@wilkinsona
Comment From: jcordoba95
Realized this was set for milestone 2.1.X, so I closed my pull request since the solution I wrote was according to what OLPMO found (and realized the file has changes and the solution won’t work for this milestone).
@wilkinsona I do have a question, how can I replicate this issue to make some debugging? I could't find any replication steps.
Comment From: wilkinsona
@jcordoba95 I don't believe we have any. @xak2000 Could you provide a small sample the reproduces that behaviour you've described?
Comment From: xak2000
The simplest thing you can do is to just declare a UserDetailsService
bean.
I created mcve: gh-19169.zip.
Comment From: jcordoba95
@wilkinsona I've made some research and this is what I got:
I've made some changes to the getOutcomes function at OnBeanCondition.java
and this is how it currently looks,
@Override
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
for (int i = 0; i < outcomes.length; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");
outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);
if (outcomes[i] == null) {
Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,
"ConditionalOnSingleCandidate");
outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class);
}
if (outcomes[i] == null) {
Set<String> onMissingBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,
"ConditionalOnMissingBean");
outcomes[i] = getOutcome(onMissingBeanTypes, ConditionalOnMissingBean.class);
}
}
}
return outcomes;
}
So what I did was add an extra if statement to check for any ConditionalOnMissingBean
, but when the function getProperty(String key)
is called in method get(String className, String key, String defaultValue)
in file AutoConfigurationMetadataLoader.java
returns null.
I don't know if what I'm doing is correct or if the getProperty
method should return a non-null value for this specific case.
Comment From: wilkinsona
Thanks, @jcordoba95, and sorry it's taken me so long to get back to you. I don't think we need any extra logic in getOutcomes
. Looking at the code, I think the problem lies in org.springframework.boot.autoconfigure.condition.OnBeanCondition.Spec<A>
, specifically its message()
and message(ConditionMessage message)
methods where it hardcodes ConditionalOnBean.class
rather than using its annotationType
field.
Comment From: snicoll
Closing in favour of PR #19948