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