I have a multi-subproject Spring Boot 3.0 application built by gradle. In modules A
and B
I have beans with the annotations
@Bean
@ConditionalOnBean(jakarta.jms.ConnectionFactory.class)
declared.
The actual app
subproject containing the runnable boot application depends on both of these modules. Also module B
depends on A
. And app
also has a runtimeOnly
dependency on ActiveMQ Artemis to let Spring Boot's ArtemisAutoConfiguration
do the configuration of the JMS connection factory via ArtemisAutoConfiguration
.
However, when I look at the condition evaluation report after the application started I see that the condition did not match for the bean defined in subproject A
but it did match for the bean defined in subproject B
. Since ConditionalOnBean
is evaluated during bean registration phase I suppose the bean definition for artemis is registered only after the bean in subproject A
is registered but before the one in subproject B
.
Can anyone explain what determines this order?
I already had a look at BOOT-INF/classpath.idx
in the spring boot jar. But this shows A
and B
before any artemis and spring-boot-autoconfiguration entries. So I suppose it's not that.
NOTE: yes, using @AutoConfigureAfter(ArtemisAutoConfiguration.class)
on the auto configuration of subproject A
fixes this. I'm just curious to know why this was actually necessary to apply.
Essentially the order of spring boot auto configurations is determined by org.springframework.boot.autoconfigure.AutoConfigurationSorter
. The ordering of auto configuration classes implemented there honors @AutoConfigureOrder
, @AutoConfigureBefore
and @AutoConfigureAfter
as expected. However these ordering hints for auto configuration classes are only applied after the classes have initially been sorted alphabetically.
This means a @ConditionalOnBean
might evaluate to true
if the bean is defined in an auto configuration class that is alphabetically after the auto configuration class that deines a bean that matches the @ConditionalOnBean
.
For example suppose that there is a very basic spring boot app:
@SpringBootApplication
@EnableJms
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
that has 2 auto configurations in its classpath:
package a;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class EarlyConfiguration {
@Bean
@ConditionalOnBean(jakarta.jms.ConnectionFactory.class)
public Object early() {
return new Object();
}
}
package z;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class LateConfiguration {
@Bean
@ConditionalOnBean(jakarta.jms.ConnectionFactory.class)
public Object late() {
return new Object();
}
}
With the appropriate application.yml
that configures a jakarta.jms.ConnectionFactory
the condition evaluation report is:
2024-06-27T14:05:52.859+02:00 DEBUG 21060 --- [demo] [ main] .s.b.a.l.ConditionEvaluationReportLogger :
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:
-----------------
[...]
ArtemisConnectionFactoryConfiguration.SimpleConnectionFactoryConfiguration matched:
- @ConditionalOnProperty (spring.artemis.pool.enabled=false) matched (OnPropertyCondition)
[...]
LateConfiguration#late matched:
- @ConditionalOnBean (types: jakarta.jms.ConnectionFactory; SearchStrategy: all) found bean 'jmsConnectionFactory' (OnBeanCondition)
[...]
Negative matches:
-----------------
[...]
EarlyConfiguration#early:
Did not match:
- @ConditionalOnBean (types: jakarta.jms.ConnectionFactory; SearchStrategy: all) did not find any beans of type jakarta.jms.ConnectionFactory (OnBeanCondition)
[...]
Exclusions:
-----------
None
Unconditional classes:
----------------------
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration
z.LateConfiguration
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
a.EarlyConfiguration
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration
I also noticed that component scaning on application classes may also have an influence on the order of bean registration. If for example
@SpringBootApplication
@EnableJms
@ComponentScan(basePackages = "z")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Then z.LateConfiguration
would also not match because the configuration class is not discovered by the auto configuration but by the component scanning of the DemoApplication
class which takes place earlier.