Setting spring.reactor.context-propagation has no effect when lazy initialization is enabled
See https://github.com/spring-projects/spring-boot/issues/45846
I was initially thinking of the following approach:
@AutoConfiguration
@ConditionalOnClass(Hooks.class)
@ConditionalOnProperty(name = "spring.reactor.context-propagation", havingValue = "auto")
@EnableConfigurationProperties(ReactorProperties.class)
public class ReactorAutoConfiguration {
ReactorAutoConfiguration() {
Hooks.enableAutomaticContextPropagation();
}
@Bean
static LazyInitializationExcludeFilter reactorLazyInitializationExcludeFilter() {
return LazyInitializationExcludeFilter.forBeanTypes(ReactorAutoConfiguration.class);
}
}
However, ReactorProperties will stop being registered, which makes this approach somewhat risky to merge.
As a good alternative, maybe something like this:
@AutoConfiguration
@ConditionalOnClass(Hooks.class)
@EnableConfigurationProperties(ReactorProperties.class)
public class ReactorAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.reactor.context-propagation", havingValue = "auto")
static class ReactorContextPropagationConfiguration {
ReactorContextPropagationConfiguration() {
Hooks.enableAutomaticContextPropagation();
}
@Bean
static LazyInitializationExcludeFilter reactorContextPropagationLazyInitExcludeFilter() {
return LazyInitializationExcludeFilter.forBeanTypes(ReactorContextPropagationConfiguration.class);
}
}
}
Arguably, we shouldn't be relying on a side-effect of the constructor being called to configure the hooks and I'd prefer to use a specific callback mechanism instead. For example, implementing SmartInitializingSingleton and configuring the hooks in afterSingletonsInstantiated fixes the problem without the need for an exclude filter.
Flagging for team attention as I'm not 100% sure that SmartInitializingSingleton is the ideal callback here. @snicoll in particular may have an alternative suggestion.
Thanks, @wilkinsona
SmartInitializingSingleton is a decent option. However, it has one downside: enableAutomaticContextPropagation will not be visible to other SmartInitializingSingleton beans, whereas the constructor-based approach does not have such an issue. The following test demonstrates the issue:
@Test
void propagationShouldBeAppliedToSmartInitializingSingletonBeans() {
this.contextRunner.withPropertyValues("spring.reactor.context-propagation=AUTO")
.withInitializer(
(context) -> context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()))
.withBean(VerySmartSingleton.class)
.run((context) -> assertThat(context.getBean(VerySmartSingleton.class).getValue()).isEqualTo("updated"));
}
static class VerySmartSingleton implements SmartInitializingSingleton {
private final AtomicReference<String> threadLocalValue = new AtomicReference<>();
String getValue() {
return this.threadLocalValue.get();
}
@Override
public void afterSingletonsInstantiated() {
Mono.just("test")
.doOnNext((element) -> this.threadLocalValue.set(THREADLOCAL_VALUE.get()))
.contextWrite(Context.of(THREADLOCAL_KEY, "updated"))
.block();
}
}
Indeed. That's why it may not be ideal. There are still ordering concerns with the constructor-based approach as it leaves a window between a bean using Reactor during its standard creation/initialization and ReactorAutoConfiguration being created. However, using SmartInitializingSingleton widens the window.
There are still ordering concerns with the constructor-based approach as it leaves a window between a bean using Reactor during its standard creation/initialization and ReactorAutoConfiguration being created.
How about that one?
@AutoConfiguration
@ConditionalOnClass(Hooks.class)
@EnableConfigurationProperties(ReactorProperties.class)
public class ReactorAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.reactor.context-propagation", havingValue = "auto")
static class ReactorContextPropagationConfiguration {
@Bean
static BeanFactoryInitializer<ListableBeanFactory> reactorContextPropagationBeanFactoryInitializer() {
return (beanFactory) -> Hooks.enableAutomaticContextPropagation();
}
}
}
Oh, sorry @nosan, i missed your latest update and have implemented the same solution in https://github.com/spring-projects/spring-boot/commit/e2571a41bf2215cddcef9cfe0cccc42a70cec885.
No worries, @mhalbritter 😃