Skip to content

Protocol resolver from parent context is not used in binder context #3112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
tommyk-gears opened this issue May 12, 2025 · 0 comments
Open

Comments

@tommyk-gears
Copy link

Describe the issue
When a binder context is created in DefaultBinderFactory, the protocol resolvers from parent context are not copied to the binder context. In our case it breaks our application because we have a protocol resolver that makes sure that the kafka truststore is a local file (by downloading it), and org.springframework.boot.autoconfigure.kafka.KafkaProperties.Ssl#resourceToPath fails if it is not on file system. I.e. it fails because the protocol resolver is not applied in the binding context, and the KafkaProperties bean is created in that context.

This is caused by the fix for spring-projects/spring-boot#41487 where configuration properties beans are re-created in child contexts rather than inherited from the parent context. I'll leave it for you to evaluate if a fix should go into spring-boot or spring-cloud-stream. E.g. should the protocol resolvers be explicitly copied when creating the child context here? Or should protocol resolvers be automatically inherited from parent context?

To Reproduce
I create this test to demonstrate the issue:

package org.springframework.cloud.stream.binder;

import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.stream.config.BinderFactoryAutoConfiguration;
import org.springframework.cloud.stream.config.BindingServiceConfiguration;
import org.springframework.cloud.stream.function.FunctionConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.FileUrlResource;
import org.springframework.core.io.ProtocolResolver;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import java.net.URI;
import java.util.Collections;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;

class CustomProtocolResolverBindingTest {

	@Test
	void bindingContextUsesParentProtocolResolvers() {

		new ApplicationContextRunner()
			.withInitializer(ctx -> ctx.addProtocolResolver(new CustomProtocolResolver()))
			.withPropertyValues("test.resource=backwards://foo-bar")
			.withConfiguration(AutoConfigurations.of(BinderFactoryAutoConfiguration.class,
				BindingServiceConfiguration.class, FunctionConfiguration.class))
			.withUserConfiguration(AppConfig.class)
			.run(ctx -> {

				DefaultBinderFactory defaultBinderFactory = ctx.getBean(DefaultBinderFactory.class);
				ConfigurableApplicationContext bindingContext = defaultBinderFactory.initializeBinderContextSimple("mock",
					Collections.emptyMap(),
					new BinderType("custom-resolvers", new Class[]{AppConfig.class}),
					new BinderConfiguration("custom-resolvers", Collections.emptyMap(), true, true),
					true);

				TestProperties props = bindingContext.getBean(TestProperties.class);
				assertNotNull(props);
				assertNotNull(props.getResource());
				assertInstanceOf(FileUrlResource.class, props.getResource());
				assertEquals("file://rab-oof", props.getResource().getURI().toString());
			});
	}

	@SpringBootApplication
	@EnableConfigurationProperties(TestProperties.class)
	static class AppConfig {
	}

	@ConfigurationProperties(prefix = "test")
	static class TestProperties {
		private Resource resource;

		public Resource getResource() {
			return resource;
		}

		public void setResource(Resource resource) {
			this.resource = resource;
		}
	}

	/**
	 * Custom protocol resolver that handles the "backwards" scheme by replacing it with the
	 * "file" scheme and reversing the host part.
	 */
	static class CustomProtocolResolver implements ProtocolResolver {

		@Override
		public Resource resolve(String location, ResourceLoader resourceLoader) {
			URI uri = URI.create(location);
			if (uri.getScheme().equals("backwards")) {
				String reversed = new StringBuilder(uri.getHost()).reverse().toString();
				return resourceLoader.getResource("file://" + reversed);
			}
			return null;
		}
	}
}

Version of the framework
4.2.1 (with spring-boot 3.4.3)

Expected behavior
Protocol resolvers are applied also in the child context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant