-
Notifications
You must be signed in to change notification settings - Fork 2
Make request/reply auto-configuration excludable (#8); document context propagation (#50) #56
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
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 99 additions & 0 deletions
99
...ava/community/solace/spring/cloud/requestreply/service/RequestReplyFunctionRegistrar.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| package community.solace.spring.cloud.requestreply.service; | ||
|
|
||
| import community.solace.spring.cloud.requestreply.config.RequestReplyProperties; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import org.springframework.beans.BeansException; | ||
| import org.springframework.beans.factory.BeanFactory; | ||
| import org.springframework.beans.factory.BeanFactoryAware; | ||
| import org.springframework.beans.factory.support.BeanDefinitionRegistry; | ||
| import org.springframework.beans.factory.support.RootBeanDefinition; | ||
| import org.springframework.boot.context.properties.bind.BindResult; | ||
| import org.springframework.boot.context.properties.bind.Binder; | ||
| import org.springframework.cloud.function.context.FunctionRegistration; | ||
| import org.springframework.context.EnvironmentAware; | ||
| import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; | ||
| import org.springframework.core.ResolvableType; | ||
| import org.springframework.core.env.Environment; | ||
| import org.springframework.core.type.AnnotationMetadata; | ||
| import org.springframework.messaging.Message; | ||
|
|
||
| import java.util.function.Consumer; | ||
| import java.util.function.Supplier; | ||
|
|
||
| /** | ||
| * Registers one {@link FunctionRegistration} per configured | ||
| * {@code spring.cloud.stream.requestreply.bindingMapping} so that spring-cloud-stream routes incoming | ||
| * replies to {@link RequestReplyServiceImpl#onReplyReceived(Message)}. | ||
| * | ||
| * <p>This logic used to live in an {@link org.springframework.context.ApplicationContextInitializer}. | ||
| * Spring Boot applies such initializers to <em>every</em> application context it creates - including | ||
| * sliced test contexts such as {@code @JsonTest} - and they cannot be turned off through | ||
| * {@code @ImportAutoConfiguration(exclude = ...)} or {@code spring.autoconfigure.exclude}. Because the | ||
| * registered reply consumers depend on the {@link RequestReplyServiceImpl} bean (which is absent in a | ||
| * slice), the context failed to start | ||
| * (<a href="https://github.com/solacecommunity/spring-cloud-stream-request-reply/issues/8">issue #8</a>).</p> | ||
| * | ||
| * <p>By contributing the bean definitions from a registrar that is | ||
| * {@link org.springframework.context.annotation.Import imported} by {@link RequestReplyAutoConfiguration}, | ||
| * the registration now runs only when the auto-configuration itself is active. Excluding the | ||
| * auto-configuration therefore also disables the reply-consumer registration, which is the behaviour a | ||
| * test slice expects.</p> | ||
| */ | ||
| class RequestReplyFunctionRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware, BeanFactoryAware { | ||
|
|
||
| private static final Logger LOG = LoggerFactory.getLogger(RequestReplyFunctionRegistrar.class); | ||
|
|
||
| /** | ||
| * The generic type of the reply consumer, i.e. {@code Consumer<Message<Object>>}. spring-cloud-function | ||
| * uses it to wire the registration to the {@code <bindingName>-in-0} input binding. | ||
| */ | ||
| private static final ResolvableType REPLY_CONSUMER_TYPE = ResolvableType.forClassWithGenerics( | ||
| Consumer.class, | ||
| ResolvableType.forClassWithGenerics(Message.class, Object.class)); | ||
|
|
||
| private Environment environment; | ||
| private BeanFactory beanFactory; | ||
|
|
||
| @Override | ||
| public void setEnvironment(final Environment environment) { | ||
| this.environment = environment; | ||
| } | ||
|
|
||
| @Override | ||
| public void setBeanFactory(final BeanFactory beanFactory) throws BeansException { | ||
| this.beanFactory = beanFactory; | ||
| } | ||
|
|
||
| @Override | ||
| public void registerBeanDefinitions(final AnnotationMetadata importingClassMetadata, final BeanDefinitionRegistry registry) { | ||
| final BindResult<RequestReplyProperties> bindResult = Binder.get(environment) | ||
| .bind("spring.cloud.stream.requestreply", RequestReplyProperties.class); | ||
|
|
||
| if (!bindResult.isBound()) { | ||
| return; | ||
| } | ||
|
|
||
| for (final String bindingName : bindResult.get().getBindingMappingNames()) { | ||
| if (registry.containsBeanDefinition(bindingName)) { | ||
| // Never override a function the application defined itself under the same name. | ||
| LOG.debug("Skip binding: {} for receiving replies, a bean with that name already exists", bindingName); | ||
| continue; | ||
| } | ||
|
|
||
| final RootBeanDefinition definition = new RootBeanDefinition(FunctionRegistration.class); | ||
| definition.setInstanceSupplier(replyConsumerSupplier()); | ||
| registry.registerBeanDefinition(bindingName, definition); | ||
|
|
||
| LOG.info("Register binding: {} for receiving replies", bindingName); | ||
| } | ||
| } | ||
|
|
||
| private Supplier<FunctionRegistration<Consumer<Message<?>>>> replyConsumerSupplier() { | ||
| return () -> { | ||
| final RequestReplyServiceImpl service = beanFactory.getBean(RequestReplyServiceImpl.class); | ||
| return new FunctionRegistration<Consumer<Message<?>>>(service::onReplyReceived) | ||
| .type(REPLY_CONSUMER_TYPE.getType()); | ||
| }; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1 @@ | ||
| org.springframework.boot.env.EnvironmentPostProcessor=community.solace.spring.cloud.requestreply.env.ReplyTopicWithWildcardPropertySourceEnvironmentPostProcessor | ||
| org.springframework.context.ApplicationContextInitializer=community.solace.spring.cloud.requestreply.service.RequestReplyAutoConfiguration |
73 changes: 73 additions & 0 deletions
73
...solace/spring/cloud/requestreply/service/RequestReplyAutoConfigurationExclusionTests.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| package community.solace.spring.cloud.requestreply.service; | ||
|
|
||
| import org.junit.jupiter.api.Test; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.boot.SpringBootConfiguration; | ||
| import org.springframework.boot.test.autoconfigure.json.JsonTest; | ||
| import org.springframework.cloud.function.context.FunctionRegistration; | ||
| import org.springframework.context.ApplicationContext; | ||
| import org.springframework.test.context.ContextConfiguration; | ||
| import org.springframework.test.context.TestPropertySource; | ||
| import tools.jackson.databind.ObjectMapper; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertFalse; | ||
| import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
|
|
||
| /** | ||
| * Regression test for | ||
| * <a href="https://github.com/solacecommunity/spring-cloud-stream-request-reply/issues/8">issue #8</a>. | ||
| * | ||
| * <p>A {@code @JsonTest} (or any other sliced test) only loads a small, fixed set of | ||
| * auto-configurations and therefore does <em>not</em> contain the request/reply beans. Before the | ||
| * fix, the request/reply function registration lived in an | ||
| * {@link org.springframework.context.ApplicationContextInitializer} that Spring Boot applies to | ||
| * <em>every</em> application context regardless of slicing or {@code @ImportAutoConfiguration(exclude = ...)}. | ||
| * That initializer registered one reply-consumer bean per configured | ||
| * {@code spring.cloud.stream.requestreply.bindingMapping}, each of which depends on the | ||
| * {@code RequestReplyServiceImpl} bean. Since that service bean is absent in a slice, the context | ||
| * failed to start.</p> | ||
| * | ||
| * <p>The reply-consumer registration is now contributed by {@link RequestReplyFunctionRegistrar}, | ||
| * which is imported by {@link RequestReplyAutoConfiguration}. As a result it only runs when the | ||
| * auto-configuration itself is active, so a slice that does not load the auto-configuration starts | ||
| * cleanly and none of the reply-consumer beans are registered.</p> | ||
| */ | ||
| @JsonTest | ||
| @ContextConfiguration(classes = RequestReplyAutoConfigurationExclusionTests.JsonOnlyApplication.class) | ||
| @TestPropertySource(properties = { | ||
| // A binding mapping is present in the environment, exactly as it would be in a real | ||
| // application. Before the fix this alone was enough to make the slice fail to start. | ||
| "spring.cloud.stream.requestreply.bindingMapping[0].binding=replyConsumerThatMustNotExist", | ||
| "spring.cloud.stream.requestreply.bindingMapping[0].replyTopic=some/reply/topic" | ||
| }) | ||
| class RequestReplyAutoConfigurationExclusionTests { | ||
|
|
||
| @SpringBootConfiguration | ||
| static class JsonOnlyApplication { | ||
| } | ||
|
|
||
| @Autowired | ||
| private ApplicationContext context; | ||
|
|
||
| @Autowired | ||
| private ObjectMapper objectMapper; | ||
|
|
||
| @Test | ||
| void jsonSliceStartsWithoutRegisteringReplyConsumers() { | ||
| // The slice itself is functional: this is what the reporter actually wanted to test. | ||
| assertNotNull(objectMapper, "the @JsonTest slice should still provide an ObjectMapper"); | ||
|
|
||
| // The reply consumer derived from the binding mapping must not have been registered, because | ||
| // the request/reply auto-configuration is not part of this slice. | ||
| assertFalse(context.containsBean("replyConsumerThatMustNotExist"), | ||
| "no reply-consumer bean must be registered when the request/reply auto-configuration is not loaded"); | ||
| assertEquals(0, context.getBeanNamesForType(FunctionRegistration.class).length, | ||
| "no request/reply FunctionRegistration beans must be registered in a slice"); | ||
|
|
||
| // Without the fix the RequestReplyServiceImpl is missing yet referenced, which is what broke | ||
| // the context. Assert it is genuinely absent so the test stays meaningful. | ||
| assertEquals(0, context.getBeanNamesForType(RequestReplyServiceImpl.class).length, | ||
| "the request/reply service must not be present in a slice that excludes it"); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.