Please see POC repository on Github
I am having a hard time defining a @ConditionalOnBean(jakarta.jms.ConnectionFactory.class) bean for some Spring Integration Flows that depend on the availability of a JMS connection. I have read the documentation (linked below) that @ConditionalOnBean must/should be used only on AutoConfiguration classes, but I insist on using it on regular beans like linked sources do.
I only need to define an IntegrationFlow as soon as the ConnectionFactory exists. If the application is started without a JMS broker (e.g. is not configured) then it should reduce its functionality and not initialize the relative IntegrationFlows. I have it over-simplified in my POC, as the real application contains around 120 integration flows and interacts with a handful of JMS channels.
Expectation: the provided JUnit test should invoke the flow using JMS and get the reply back. Commenting out the @ConditionalOnBean annotation fixes the test
I am opening an issue here instead of an SO question because ConditionalOnBean annotation is working perfectly in any other case, and I can't tell Spring to load my configuration class after JMS
In the POC repository, we define an example IntegrationFlow that:
- Reads from
entryJMS channel - Makes the word uppercase
- Outputs the result to
exitJMS channel
The IntegrationFlow depends on the jakarta.jms.ConnectionFactory to have been defined as a bean. In my business case, the Spring Boot application may start without JMS at all, so the IntegrationFlow must not be defined.
For that purpose, we use the @ConditionalOnBean annotation.
Per Spring documentation, that annotation should be used only on @AutoConfiguration classes, which makes things a little more complicated.
Half of the world uses the same annotation for a variety of purpose, and we are actively using it in our real application to instantiate a bean only if a collaborator is defined as a bean. None of these require that one uses @ConditionalOnBean on @AutoConfigurations only
To provide an example, we use a number of _RestService_s that depend on RestTemplate (bearing the root url and authentication info), as well as _SoapService_s depending on WebServiceTemplate, and they depend as such:
WebServiceTemplatebean depends on business propertycom.example.ws.{url|username|password}- SoapService component is annotated
@ConditionalOnBean(value = WebServiceTemplate.class, name = "mySpecificWebServiceTemplateBecauseWeHavePlentiesOf")
All beautiful, except that it doesn't work with JMS ConnectionFactory. In this case, the connection factory exists only if spring.artemis.embedded=true (or another MQ broker is defined according to profile configuration).
In this POC, I can see that Spring refuses to instantiate the IntegrationFlow despite the ConnectionFactory exists.
Other considerations:
- In the POC, I have simplified declaring the bean in the application class, but in my application we have dedicated
@Configurationclasses - Declaring that Configuration as an
@AutoConfiguration(after = JmsAutoConfiguration.class)did not work @DependsOn("jmsConnectionFactory")fails when there is no JMS ConnectionFactory- I can clearly see in the logs that
MyJmsConfigurationdoes not match the Condition - Debugging into
ArtemisAutoConfiguration, the ConnectionFactory eventually is created - Debugging the test with the
BeanFactory, the ConnectionFactory bean is eventually available @SpringIntegrationTestis currently not present in the POC but makes no difference (the real application shows it)
Comment From: bclozel
Thanks for getting in touch, but this issue tracker is dedicated to bugs an enhancement requests.
As you already know, ConditionalOnMissingBean and similar conditions should only be used on auto-configurations. While it might work in some cases, ordering is key: the condition is processed with your application and at that point the auto-configured bean might not be detected. You'll find many similar reports in our issue tracker over the years.
Half of the world uses the same annotation for a variety of purpose, and we are actively using it in our real application to instantiate a bean only if a collaborator is defined as a bean. None of these require that one uses @ConditionalOnBean on @AutoConfigurations only
All of those use cases are invalid. I've reported an issue to one of them, feel free to do so for other sources.
Declaring that Configuration as an @AutoConfiguration(after = JmsAutoConfiguration.class) did not work
As for this case, it is probably because you've left your configuration to be scanned in your main application package. There is a note about this on our reference documentation:
Auto-configurations must be loaded only by being named in the imports file. Make sure that they are defined in a specific package space and that they are never the target of component scanning. Furthermore, auto-configuration classes should not enable component scanning to find additional components. Specific @Import annotations should be used instead.
Moving your configuration class to a different package:
package org.zighinetto.autoconfigure;
import jakarta.jms.ConnectionFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.jms.dsl.Jms;
@AutoConfiguration(after = JmsAutoConfiguration.class)
public class IntegrationFlowAutoConfiguration {
@ConditionalOnBean(ConnectionFactory.class)
@Bean
public IntegrationFlow exampleIntegrationFlow(ConnectionFactory connectionFactory) {
return IntegrationFlow.from(Jms.inboundAdapter(connectionFactory)
.destination("entry")
)
.transform(String.class, String::toUpperCase)
.handle(Jms.outboundAdapter(connectionFactory)
.destination("exit")
)
.get();
}
}
Declaring it in src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports with:
org.zighinetto.autoconfigure.IntegrationFlowAutoConfiguration
Yields the following in the auto-configuration report (when starting your application with the DEBUG property):
IntegrationFlowAutoConfiguration#exampleIntegrationFlow matched:
- @ConditionalOnBean (types: jakarta.jms.ConnectionFactory; SearchStrategy: all) found bean 'jmsConnectionFactory' (OnBeanCondition)