spring-boot icon indicating copy to clipboard operation
spring-boot copied to clipboard

Setting spring.reactor.context-propagation has no effect when lazy initialization is enabled

Open nosan opened this issue 6 months ago • 5 comments

See https://github.com/spring-projects/spring-boot/issues/45846

nosan avatar Jun 08 '25 09:06 nosan

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);
		}

	}

}

nosan avatar Jun 08 '25 14:06 nosan

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.

wilkinsona avatar Jun 09 '25 07:06 wilkinsona

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();
		}

	}

nosan avatar Jun 09 '25 09:06 nosan

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.

wilkinsona avatar Jun 09 '25 11:06 wilkinsona

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();
		}

	}

}
	

nosan avatar Jun 09 '25 15:06 nosan

Oh, sorry @nosan, i missed your latest update and have implemented the same solution in https://github.com/spring-projects/spring-boot/commit/e2571a41bf2215cddcef9cfe0cccc42a70cec885.

mhalbritter avatar Jun 23 '25 13:06 mhalbritter

No worries, @mhalbritter 😃

nosan avatar Jun 23 '25 13:06 nosan