spring-cloud / spring-cloud/spring-cloud-stream
Protocol resolver from parent context is not used in binder context
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 1.1k
- Forks
- 646
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 8
Description
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 https://github.com/spring-projects/spring-boot/issues/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.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with DefaultBinderFactory.initializeBinderContextSimple and the CustomProtocolResolverBindingTest reproduction. Run the test and trace how the child binding context is created; done means the context resolves the backwards:// resource through the parent protocol resolver and the assertions pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, spring, spring-boot
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100