Convert Consumer<Foo> to Function<Flux<Foo>,Mono<Void>>

This results in a better experience for users because the consumer
that they write is only applied to a Flux that is subscribed to
by the framework once. It gives better control over the flow of
foos, e.g. if some component wants to subscribe on a thread.
This commit is contained in:
Dave Syer
2018-03-26 10:06:13 +01:00
parent a1b624b28a
commit 5aeba1ea96
13 changed files with 144 additions and 76 deletions

View File

@@ -405,23 +405,22 @@ public class ContextFunctionCatalogAutoConfiguration {
findType(target);
}
Class<?> type;
target = target(target, key);
registration.target(target);
if (target instanceof Supplier) {
type = Supplier.class;
registration.target(target((Supplier<?>) target, key));
for (String name : registration.getNames()) {
this.suppliers.put(name, registration.getTarget());
}
}
else if (target instanceof Consumer) {
type = Consumer.class;
registration.target(target((Consumer<?>) target, key));
for (String name : registration.getNames()) {
this.consumers.put(name, registration.getTarget());
}
}
else if (target instanceof Function) {
type = Function.class;
registration.target(target((Function<?, ?>) target, key));
for (String name : registration.getNames()) {
this.functions.put(name, registration.getTarget());
}
@@ -454,34 +453,34 @@ public class ContextFunctionCatalogAutoConfiguration {
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private <T> T target(T target, String key) {
private Object target(Object target, String key) {
boolean isolated = getClass().getClassLoader() != target.getClass()
.getClassLoader();
if (target instanceof Supplier<?>) {
boolean flux = isFluxSupplier(key, (Supplier<?>) target);
if (isolated) {
target = (T) new IsolatedSupplier((Supplier<?>) target);
target = new IsolatedSupplier((Supplier<?>) target);
}
if (!flux) {
target = (T) new FluxSupplier((Supplier<?>) target);
target = new FluxSupplier((Supplier<?>) target);
}
}
else if (target instanceof Function<?, ?>) {
boolean flux = isFluxFunction(key, (Function<?, ?>) target);
if (isolated) {
target = (T) new IsolatedFunction((Function<?, ?>) target);
target = new IsolatedFunction((Function<?, ?>) target);
}
if (!flux) {
target = (T) new FluxFunction((Function<?, ?>) target);
target = new FluxFunction((Function<?, ?>) target);
}
}
else if (target instanceof Consumer<?>) {
boolean flux = isFluxConsumer(key, (Consumer<?>) target);
if (isolated) {
target = (T) new IsolatedConsumer((Consumer<?>) target);
target = new IsolatedConsumer((Consumer<?>) target);
}
if (!flux) {
target = (T) new FluxConsumer((Consumer<?>) target);
target = new FluxConsumer((Consumer<?>) target);
}
}
return target;

View File

@@ -32,6 +32,7 @@ import org.springframework.cloud.function.context.config.ContextFunctionCatalogA
import static org.assertj.core.api.Assertions.assertThat;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Dave Syer
@@ -88,7 +89,8 @@ public class BeanFactoryFunctionCatalogTests {
public void composeFunction() {
processor.register(new FunctionRegistration<>(new Foos()).names("foos"));
processor.register(new FunctionRegistration<>(new Bars()).names("bars"));
Function<Flux<Integer>, Flux<String>> foos = processor.lookup(Function.class, "foos,bars");
Function<Flux<Integer>, Flux<String>> foos = processor.lookup(Function.class,
"foos,bars");
assertThat(foos.apply(Flux.just(2)).blockFirst()).isEqualTo("Hello 4");
}
@@ -112,8 +114,9 @@ public class BeanFactoryFunctionCatalogTests {
processor.register(new FunctionRegistration<>(new Foos()).names("foos"));
Sink sink = new Sink();
processor.register(new FunctionRegistration<>(sink).names("sink"));
Consumer<Flux<Integer>> foos = processor.lookup(Consumer.class, "foos,sink");
foos.accept(Flux.just(2));
Function<Flux<Integer>, Mono<Void>> foos = processor.lookup(Function.class,
"foos,sink");
foos.apply(Flux.just(2)).subscribe();
assertThat(sink.values).contains("4");
}
@@ -121,8 +124,8 @@ public class BeanFactoryFunctionCatalogTests {
public void composeUniqueConsumer() {
Sink sink = new Sink();
processor.register(new FunctionRegistration<>(sink).names("sink"));
Consumer<Flux<String>> foos = processor.lookup(Consumer.class, "");
foos.accept(Flux.just("2"));
Function<Flux<String>, Mono<Void>> foos = processor.lookup(Function.class, "");
foos.apply(Flux.just("2")).subscribe();
assertThat(sink.values).contains("2");
}

View File

@@ -66,6 +66,7 @@ import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Dave Syer
@@ -115,11 +116,11 @@ public class ContextFunctionCatalogAutoConfigurationTests {
assertThat(context.getBean("foos")).isInstanceOf(Function.class);
assertThat(catalog.<Function<?, ?>>lookup(Function.class, "foos"))
.isInstanceOf(Function.class);
assertThat(catalog.<Consumer<?>>lookup(Consumer.class, "foos"))
.isInstanceOf(Consumer.class);
assertThat(catalog.<Supplier<?>>lookup(Supplier.class, "foos"))
.isInstanceOf(Supplier.class);
assertThat(inspector.getInputType(catalog.lookup(Function.class, "foos")))
.isEqualTo(String.class);
assertThat(inspector.getInputType(catalog.lookup(Consumer.class, "foos")))
assertThat(inspector.getOutputType(catalog.lookup(Supplier.class, "foos")))
.isEqualTo(Foo.class);
}
@@ -185,13 +186,13 @@ public class ContextFunctionCatalogAutoConfigurationTests {
@Test
public void composedConsumer() {
create(MultipleConfiguration.class);
assertThat(catalog.<Consumer<?>>lookup(Consumer.class, "foos,print"))
.isInstanceOf(Consumer.class);
assertThat(catalog.<Function<?, ?>>lookup(Function.class, "foos,print")).isNull();
assertThat(inspector.getInputType(catalog.lookup(Consumer.class, "foos,print")))
assertThat(catalog.<Consumer<?>>lookup(Consumer.class, "foos,print")).isNull();
assertThat(catalog.<Function<?, ?>>lookup(Function.class, "foos,print"))
.isInstanceOf(Function.class);
assertThat(inspector.getInputType(catalog.lookup(Function.class, "foos,print")))
.isAssignableFrom(String.class);
// The output type is the same as the output type of the last element in the chain
assertThat(inspector.getOutputType(catalog.lookup(Consumer.class, "foos,print")))
assertThat(inspector.getOutputType(catalog.lookup(Function.class, "foos,print")))
.isAssignableFrom(Void.class);
}
@@ -391,8 +392,9 @@ public class ContextFunctionCatalogAutoConfigurationTests {
public void simpleConsumer() {
create(SimpleConfiguration.class);
assertThat(context.getBean("consumer")).isInstanceOf(Consumer.class);
Consumer<Flux<String>> consumer = catalog.lookup(Consumer.class, "consumer");
consumer.accept(Flux.just("foo", "bar"));
Function<Flux<String>, Mono<Void>> consumer = catalog.lookup(Function.class,
"consumer");
consumer.apply(Flux.just("foo", "bar")).subscribe();
assertThat(context.getBean(SimpleConfiguration.class).list).hasSize(2);
}
@@ -464,9 +466,9 @@ public class ContextFunctionCatalogAutoConfigurationTests {
+ "::set",
"spring.cloud.function.compile.foos.type=consumer",
"spring.cloud.function.compile.foos.inputType=String");
assertThat(catalog.<Consumer<?>>lookup(Consumer.class, "foos"))
.isInstanceOf(Consumer.class);
assertThat(inspector.getInputWrapper(catalog.lookup(Consumer.class, "foos")))
assertThat(catalog.<Function<?, ?>>lookup(Function.class, "foos"))
.isInstanceOf(Function.class);
assertThat(inspector.getInputWrapper(catalog.lookup(Function.class, "foos")))
.isEqualTo(String.class);
@SuppressWarnings("unchecked")
Consumer<String> consumer = (Consumer<String>) context.getBean("foos");
@@ -602,7 +604,6 @@ public class ContextFunctionCatalogAutoConfigurationTests {
@EnableAutoConfiguration
@Configuration
protected static class AmbiguousConfiguration {
private List<Foo> list = new ArrayList<>();
@Bean
public Function<String, Foo> foos() {
@@ -611,8 +612,8 @@ public class ContextFunctionCatalogAutoConfigurationTests {
@Bean
@Qualifier("foos")
public Consumer<Foo> consumer() {
return value -> list.add(value);
public Supplier<Foo> supplier() {
return () -> new Foo("bar");
}
}

View File

@@ -28,14 +28,15 @@ import org.junit.After;
import org.junit.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ClassUtils;
import org.springframework.cloud.function.context.FunctionRegistration;
import org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration.ContextFunctionRegistry;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Dave Syer
@@ -124,7 +125,8 @@ public class ContextFunctionPostProcessorTests {
assertThat(foos.get().blockFirst()).isEqualTo("8");
assertThat(processor.getRegistration(foos).getNames())
.containsExactly("ints|foos");
assertThat(processor.getRegistration(foos).getType().getOutputWrapper()).isEqualTo(Flux.class);
assertThat(processor.getRegistration(foos).getType().getOutputWrapper())
.isEqualTo(Flux.class);
}
@Test
@@ -157,9 +159,9 @@ public class ContextFunctionPostProcessorTests {
Object target = create(Sink.class);
processor.register(new FunctionRegistration<>(target).names("sink"));
@SuppressWarnings("unchecked")
Consumer<Flux<String>> sink = (Consumer<Flux<String>>) processor
.lookupConsumer("sink");
sink.accept(Flux.just("Hello"));
Function<Flux<String>, Mono<Void>> sink = (Function<Flux<String>, Mono<Void>>) processor
.lookupFunction("sink");
sink.apply(Flux.just("Hello")).subscribe();
@SuppressWarnings("unchecked")
List<String> values = (List<String>) ReflectionTestUtils.getField(target,
"values");