New abstraction for StreamListener method setup invoker.

StreamListenerMethodSetupOrchestrator is an API hook that allows
downstram binder implementations or applications to inject custom
strategies to alter the default StreamListener adapter method invocations.

This PR primarily focuses on allowing customizations on the oubound side.
For the inbound, StreamListenerMethodSetupOrchestrator interface provides
a default implementation.

Corresponding refactoring in StreamListenerAnnotationBeanPostProcessor.
By default StreamListenerAnnotationBeanPostProcessor will use the current
strategies used for invoking the StreamListener adapters. If beans are provided
for orchestration, they take precedence however.

Test changes.

Further polishing in StreamListenerAnnotationBeanPostProcessor.

Resolves #1177
Resolves #1179
This commit is contained in:
Soby Chacko
2018-01-10 13:51:59 -05:00
committed by Oleg Zhurakousky
parent 9ef1181f7a
commit c1749ca866
3 changed files with 445 additions and 173 deletions

View File

@@ -0,0 +1,147 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.config;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.binding.StreamListenerAnnotationBeanPostProcessor;
import org.springframework.cloud.stream.binding.StreamListenerSetupMethodOrchestrator;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* @author Soby Chacko
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class StreamListenerMethodSetupOrchestratorTests {
@SpyBean
CustomOrchestrator customOrchestrator;
@SpyBean
MultipleStreamListenerProcessor multipleStreamListenerProcessor;
@SpyBean
StreamListenerAnnotationBeanPostProcessor streamListenerAnnotationBeanPostProcessor;
@Test
@SuppressWarnings("unchecked")
public void testCustomStreamListenerOrchestratorAndDefaultTogetherInSameContext() throws Exception {
//Two StreamListener methods, so 2 invocations
verify(customOrchestrator, times(2)).supports(any());
Method method = multipleStreamListenerProcessor.getClass().getMethod("handleMessage");
StreamListener streamListener = AnnotatedElementUtils.findMergedAnnotation(method, StreamListener.class);
//verify that the invocation happened on the custom Orchestrator
verify(customOrchestrator).orchestrateStreamListenerSetupMethod(streamListener, method, multipleStreamListenerProcessor);
Method method1 = multipleStreamListenerProcessor.getClass().getMethod("produceString");
StreamListener streamListener1 = AnnotatedElementUtils.findMergedAnnotation(method, StreamListener.class);
//Verify that the invocation did not happen on the custom orchestrator
verify(customOrchestrator, never()).orchestrateStreamListenerSetupMethod(streamListener1, method1, multipleStreamListenerProcessor);
Field field = ReflectionUtils.findField(streamListenerAnnotationBeanPostProcessor.getClass(), "streamListenerSetupMethodOrchestrators");
ReflectionUtils.makeAccessible(field);
Set<StreamListenerSetupMethodOrchestrator> field1 =
(LinkedHashSet<StreamListenerSetupMethodOrchestrator>)ReflectionUtils.getField(field, streamListenerAnnotationBeanPostProcessor);
List<StreamListenerSetupMethodOrchestrator> list = new ArrayList<>(field1);
//Ensure that the custom orchestrator did not support this request
assertThat(list.get(0).supports(method1)).isEqualTo(false);
//Ensure that we are using the default Orchestrator in StreamListenerAnnoatationBeanPostProcessor
assertThat(list.get(1).supports(method1)).isEqualTo(true);
}
public interface SomeProcessor {
@Input(Sink.INPUT)
SubscribableChannel channel1();
@Input("foobar")
SubscribableChannel channel2();
@Output(Source.OUTPUT)
MessageChannel channel3();
}
@EnableBinding(SomeProcessor.class)
@EnableAutoConfiguration
public static class MultipleStreamListenerProcessor {
@StreamListener(Sink.INPUT)
public void handleMessage() {
}
@StreamListener("foobar")
@SendTo("output")
public String produceString(){
return "foobar";
}
@Bean
public CustomOrchestrator myOrchestrator() {
return new CustomOrchestrator();
}
}
static class CustomOrchestrator implements StreamListenerSetupMethodOrchestrator {
@Override
public boolean supports(Method method) {
return method.getReturnType() != String.class;
}
@Override
public void orchestrateStreamListenerSetupMethod(StreamListener streamListener, Method method, Object bean) {
//stub method
}
}
}