I have an MVC controller bean that's conditional on the registration of a bean implementing a handler interface. I'm using a Spring Integration service gateway to implement the interface and importing an XML integration plan in my own autoconfiguration class, and the debug log shows

Loading XML bean definitions from class path resource [META-INF/spring/integration/image-processing/image-processing-gateways.xml]

very early in the loading process, but the condition does not see the service gateway as a matching bean. I have explicitly listed my application's configuration as @AutoConfigureBefore the class that needs the bean, but it still doesn't match. Using @DependsOn and explicitly supplying a bean ID for the gateway also does not work.

Comment From: philwebb

@ConditionalOnBean annotations can be very picky when it comes to the order that beans are defined and I'm not sure that we have tested them with XML imports in the mix. Can you share a project to demonstrate the issue?

Comment From: chrylis

I'll try to figure out a minimal reproduction.

Comment From: chrylis

@ConditionalOnBean annotations can be very picky when it comes to the order that beans are defined

I've very much noticed this, and it's especially glaring when the autoconfiguration report shows negative matches with matching beans. I know the dependency algorithms are hairy, but is there any likelihood of a refactor that will reduce the ordering gotchas, or is that performance-prohibitive? Example turning on @ComponentScan for a completely unrelated package results in this:

   SpringBoot2811AutoConfiguration
      - @ConditionalOnBean (types: com.chrylis.sprboot2811.NumberPrinter; SearchStrategy: all) found no beans (OnBeanCondition)
      - @ConditionalOnBean (types: com.chrylis.sprboot2811.NumberPrinter; SearchStrategy: all) found the following [anonymousPrinter] (OnBeanCondition)

Comment From: chrylis

Reproduction here: https://github.com/chrylis/spring-boot-2811

I have an autoconfig class providing a bean through Spring Integration that explicitly indicates configuration before the class with the condition on that bean. Condition doesn't match, the second autoconfiguration doesn't activate, and its bean definitions don't get registered. Flipping on a profile that registers a second matching bean causes the condition to match and then throws on duplicate bean. (Commenting out the condition makes everything resolve and work correctly.)

Comment From: wilkinsona

Thanks for the reproduction.

At the time that the @ConditionalOnBean(NumberPrinter.class) is evaluated, the definition for the gateway printer is available, but its type is determined to be java.lang.Object which prevents the condition from matching. I don't know why yet.

A workaround is to change the condition to match the bean by name: @ConditionalOnBean(name="gatewayPrinter")

Comment From: wilkinsona

The gatewayPrinter bean is a Spring Integration GatewayProxyFactoryBean. It's typed as a FactoryBean<Object>. In the absence of factoryBeanObjectType attribute on the bean definition, Boot's best guess is that the bean produced by the factory will be a java.lang.Object.

Comment From: wilkinsona

I'm not sure why we aren't calling BeanFactory.getType() if we fail to determine the type via generics or the factoryBeanObjectType attribute. Doing so fixes this problem but it may be prohibitive from a performance perspective. /cc @philwebb

Comment From: chrylis

In this specific instance, should Spring Integration be setting the factoryBeanObjectType in the namespace handler?

Comment From: wilkinsona

I discussed that with @garyrussell and @artembilan earlier today and they're happy to make that change if necessary. However, it'd only be a fix for this specific scenario. Calling BeanFactory.getType() would be a more general purpose solution if it doesn't cause any unwanted side effects.

Comment From: chrylis

I'm not familiar enough with the internals of bean-providing classes to know, but what I'm asking is whether setting that attribute is just incidentally helpful or the Right Thing that ought to happen anyway.

Comment From: wilkinsona

It entirely depends on whether or not we update Boot to call BeanFactory.getType()

Comment From: philwebb

Unfortunately we can't call BeanFactory.getType() because it causes early initialization of the factory beans. I think we did try this approach initially but it cause all sorts of problems.

is there any likelihood of a refactor that will reduce the ordering gotchas

I can't really see what we can do to improve the situation unfortunately. The general rule that we use internally is @ConditionOnBean (or @ConditionOnMissingBean) can only be used in a auto-configuration class, and if it tests for a bean created by another auto-configuration the @AutoConfigureAfter or @AutoConfigureBefore annotations must be used to guarantee order.

Comment From: chrylis

I can't really see what we can do to improve the situation unfortunately. The general rule that we use internally is @ConditionOnBean (or @ConditionOnMissingBean) can only be used in a auto-configuration class, and if it tests for a bean created by another auto-configuration the @AutoConfigureAfter or @AutoConfigureBefore annotations must be used to guarantee order. - Does "created by another auto-configuration" include beans created in imported XML files? - @AutoConfigureBefore is being used. I understand that the ordering instructions may be necessary, and I tried adding them before opening this issue. - The specific behavior also manifested when importing the XML from a non-auto class.

Comment From: snicoll

If the file has been imported in an auto-configuration, yes.

Comment From: chrylis

What this all seems like to me is that the namespace handler for int:service-gateway is buggy--possibly unfixably buggy for now, but buggy nonetheless. The whole purpose of the tag is to provide an implementation of a specific interface, and the magic ought to be able to communicate to the context that type. It doesn't have to do any analysis or resolution; it's right there in the tag.

Comment From: wilkinsona

BeanTypeRegistry currently only supports factoryBeanObjectType attributes that are Class instances. This isn't good for namespace handlers as they should, where possible, avoid class loading. BeanTypeRegistry should be updated to also support String attributes.

Comment From: garyrussell

Note that the solution of populating the hint in the bean definition attribute will only work as long as the service-interface attribute does not contain a property placeholder. While this may be unlikely, it is possible.

Perhaps code could be added to BeanTypeRegistry.determineTypeFromDefinitionAttribute() to resolve placeholders/SpEL expressions before invoking ClassUtils.forName() ?

Comment From: wilkinsona

Spring Integration 4.1.4 has been released so we can complete the fix for this by upgrading

Comment From: akibandali

After defining both beans like below it works. But do we have some other good solution. @MockBean(name = "messageRepository") private MessageRepository messageRepository;

@MockBean(name = "messageScopeRepository")
private MessageRepository messageScopeRepository;

Comment From: wilkinsona

@akibandali I can't see the connection between @MockBean, which was introduced in Spring Boot 1.4.0, and this issue which was fixed in Spring Boot 1.2.x. If you believe you have found a bug, please open a new issue and provide a minimal sample that reproduces the problem. If you're looking for some guidance, please ask a question on Stack Overflow.

Comment From: akibandali

@wilkinsona - Yes I will open a case. And I asked the question on stack over flow-> https://stackoverflow.com/questions/75182337/define-couchbase-collection-name-based-on-property