Reactor响应式编程系列导航
Context
主要用来上下文中数据的传递,因为在响应式编程中经常会出现这两种场景:
因此需要引入一个Context
用来解决上述情况中的数据传递问题。
Demo如下:
public static Scheduler custom_Scheduler() {
Executor executor = new ThreadPoolExecutor(
10,
10,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1000),
Executors.defaultThreadFactory()
);
return Schedulers.fromExecutor(executor);
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void flux_generate4() {
final Random random = new Random();
Flux.generate(ArrayList::new, (list, sink) -> {
int value = random.nextInt(100);
list.add(value);
System.out.println("所发射元素产生的线程: "+Thread.currentThread().getName());
sink.next(value);
sleep(1000);
if (list.size() == 20) {
sink.complete();
}
return list;
}).publishOn(custom_Scheduler(),1)
.map(x -> String.format("[%s] %s", Thread.currentThread().getName(), x))
.subscribe(System.out::println);
sleep(20000);
}
结果如下:一次订阅,但是元素的消费线程却始终在切换。
这种情况会带来一个问题,若存在这么一种情况:我们需要在线程的上下文中存储一些对象数据,方便我们在整个执行的过程中使用他们,在生命周期结束的时候将存储的数据进行销毁。 所以我们该如何存储呢?
方案1:使用ThreadLocal
,但是这会有个缺点,在我们自定义线程池的时候,在调度的时候几个订阅关系可能会共用一个线程池,故不同订阅关系之间可能会由于ThreadLocal
而产生数据泄露。
方案2:使用Reactor提供的ContextAPI
来解决,其用于服务Flux
或者Mono
单个订阅关系的上下文数据存储。
Demo1:从官网案例出发(版本3.4+):运行结果不会报错即可。
@Test
public void contextSimple1() {
String key = "message";
Mono<String> r = Mono.just("Hello")
.flatMap(s -> Mono.deferContextual(ctx ->
Mono.just(s + " " + ctx.get(key))))
.contextWrite(ctx -> ctx.put(key, "World"));
StepVerifier.create(r).expectNext("Hello World").verifyComplete();
}
若不报错,也就是说最终的结果变成了Hello World
为什么会有这样的结果呢?
contextWrite()
往Context
中写入一个键值对,key:message
,value:World
。faltMap()
中,通过Mono.deferContextual()
获得一个Context
对象,可以取出对应key的value值,并进行拼接。同时我们还能从该例子中发现,设置上下文的操作contextWrite()
在调用链的最底端,说明了什么?订阅是从下游流向上游的。 若将上述案例改成:
Demo2:改变contextWrite()
的调用位置:
@org.junit.Test
public void test() {
String key = "message";
Mono<String> r = Mono.just("Hello")
.contextWrite(ctx -> ctx.put(key, "World"))
.flatMap(s -> Mono.deferContextual(ctx ->
Mono.just(s + " " + ctx.get(key))));
StepVerifier.create(r)
.expectNext("Hello World")
.verifyComplete();
}
contextWrite()
是从下往上传递的,因此并不会经过flatMap()
。flatMap()
时,抛出了异常,因为Context
为空的。Demo3:存在多个contextWrite()
的情况,且对应key一样
@org.junit.Test
public void test3() {
String key = "message";
Mono<String> r = Mono.just("Hello")
.flatMap(s -> Mono.deferContextual(ctx ->
Mono.just(s + " " + ctx.get(key))))
.contextWrite(ctx -> ctx.put(key, "Reactor"))
.contextWrite(ctx -> ctx.put(key, "World"));
StepVerifier.create(r)
.expectNext("Hello Reactor")
.verifyComplete();
}
最后期望的结果是:Hello Reactor
,原因可以这么理解:
Mono.deferContextual(c-> c.get(key))
:获得Context
中指定Key对应的Value值。contextWrite(c -> c.put(key, value)
:往Context
中塞入一个键值对。Mono.deferContextual(c ->Mono.just(c.get(key)))
1.从Mono.deferContextual
出发:
public static <T> Mono<T> deferContextual(Function<ContextView, ? extends Mono<? extends T>> contextualMonoFactory) {
return onAssembly(new MonoDeferContextual<>(contextualMonoFactory));
}
↓↓↓看下MonoDeferContextual这个类↓↓
final class MonoDeferContextual<T> extends Mono<T> implements SourceProducer<T> {
final Function<ContextView, ? extends Mono<? extends T>> contextualMonoFactory;
MonoDeferContextual(Function<ContextView, ? extends Mono<? extends T>> contextualMonoFactory) {
this.contextualMonoFactory = Objects.requireNonNull(contextualMonoFactory, "contextualMonoFactory");
}
@Override
public void subscribe(CoreSubscriber<? super T> actual) {
Mono<? extends T> p;
// 该方法决定了Context和订阅者之间的绑定关系
Context ctx = actual.currentContext();
try {
p = Objects.requireNonNull(contextualMonoFactory.apply(ctx),
"The Mono returned by the contextualMonoFactory is null");
}
catch (Throwable e) {
Operators.error(actual, Operators.onOperatorError(e, ctx));
return;
}
p.subscribe(actual);
}
}
``
2.我们先来分析下`actual.currentContext();`这个代码:
```java
public interface CoreSubscriber<T> extends Subscriber<T> {
default Context currentContext(){
return Context.empty();
}
}
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
public interface Context extends ContextView {
static Context empty() {
return Context0.INSTANCE;
}
}
3.可见这里是返回了一个Context0
的实例,而Context0
是Context
接口的一个子类,来看下它的结构:
final class Context0 implements CoreContext {
static final Context0 INSTANCE = new Context0();
@Override
public Context put(Object key, Object value) {
Objects.requireNonNull(key, "key");
Objects.requireNonNull(value, "value");
return new Context1(key, value);
}
}
大家有没有发现接口Context
的子类的名称后面都带着数字
意思就是:
Context
对象中已经有一个键值对了,那么纠结和之前存在的键值对重新创建一个ContextX
对象ContextN
。因此如果我们创建两个一样的键值对,可以看下Context2
类相关的put
方法:
@Override
public Context put(Object key, Object value) {
Objects.requireNonNull(key, "key");
Objects.requireNonNull(value, "value");
if(this.key1.equals(key)){
return new Context2(key, value, key2, value2);
}
if (this.key2.equals(key)) {
return new Context2(key1, value1, key, value);
}
return new Context3(this.key1, this.value1, this.key2, this.value2, key, value);
}
很明显,当 key
相同的时候,其执行的并不是 update
操作,而是重新new了一个对象。
紧接着讲一个Demo,在Demo3的基础上,增加一个订阅关系:
@org.junit.Test
public void test4() {
String key = "message";
Mono<String> r = Mono
.deferContextual(ctx -> Mono.just("Hello " + ctx.get(key)))//3
.contextWrite(ctx -> ctx.put(key, "Reactor"))//2
.flatMap( s -> Mono.deferContextual(ctx ->
Mono.just(s + " " + ctx.get(key))))//4
.contextWrite(ctx -> ctx.put(key, "World"));//1
StepVerifier.create(r)
.expectNext("Hello Reactor World")//5
.verifyComplete();
}
可见最终的结果是Hello Reactor World
:
Context
,key
是message
。key
还是message
,同样的写入了一个Context
,但是根据上文的说法,这里并不会对已有的Context
进行更新操作,而是重新创建了一个对象。因此第二次和第一次写入的Context
并不是同一个。Context
是最近的一次,也就是第二步中生成的Context
对象。deferContextual()
源码我们发现,最后会调用一个subscribe()
方法,也就是产生订阅关系,那么这里调用了两次deferContextual()
,也就是有两个订阅关系,那么自然而然的就会调用两次flatMap
方法flatMap
拼接的是最近Context
中的内容,也就是Reactor
。第二次写入则拼接World
。也就成了最终的结果Hello Reactor World
。一句话就是:Context
是与 Subscriber
关联的,而每一个操作符访问的 Context
来自其下游的 Subscriber
。
Demo:在flatMap
方法内部去写一个Context
,看看会有什么样的结果:
@org.junit.Test
public void test5() {
String key = "message";
Mono<String> r = Mono.just("Hello")
.flatMap(s -> Mono
.deferContextual(ctxView -> Mono.just(s + " " + ctxView.get(key)))
)
.flatMap(s -> Mono
.deferContextual(ctxView -> Mono.just(s + " " + ctxView.get(key)))
.contextWrite(ctx -> ctx.put(key, "Reactor"))
)
.contextWrite(ctx -> ctx.put(key, "World"));
StepVerifier.create(r)
.expectNext("Hello World Reactor")
.verifyComplete();
}
按照以往的说法,从下往上看,离源最近的Context的值是Reactor,应该优先输出,那么最终的结果是Hello World Reactor
?
subscriberContext
写"Reactor
是flatMap
方法中内部序列的一部分。flatMap
则看不到它。Context
具有不变性,也就是若存在key相同的情况下,会再创建一个新的Context
。faltMap
)中写入的Context
,相当于是一个局部变量,对于全局的主序列而言不可见。deferContextual()
,就会产生一个订阅关系,即多一个Subscriber
,因为源码中最后调用了xxx.subscribe()
。Context
是与 Subscriber
一对一关联的,而每一个操作符访问的 Context
来自其下游的 Subscriber
。static final String HTTP_CORRELATION_ID = "reactive.http.library.correlationId";
Mono<Tuple2<Integer, String>> doPut(String url, Mono<String> data) {
Mono<Tuple2<String, Optional<Object>>> dataAndContext =
// 在延迟内,提取相关性ID密钥的值,并合并
data.zipWith(Mono.deferContextual(c ->
Mono.just(c.getOrEmpty(HTTP_CORRELATION_ID)))
);
return dataAndContext.<String>handle((dac, sink) -> {
// isPresent():即当前序列中是否有这个元素,有的话就返回true
// 也就是如果该秘钥存在于上下文当做,那么就将相关性的ID作为标题
if (dac.getT2().isPresent()) {
sink.next("PUT <" + dac.getT1() + "> sent to " + url +
" with header X-Correlation-ID = " + dac.getT2().get());
} else {
sink.next("PUT <" + dac.getT1() + "> sent to " + url);
}
sink.complete();
})
.map(msg -> Tuples.of(200, msg));
}
@org.junit.Test
public void contextForLibraryReactivePut() {
Mono<String> put = doPut("书库", Mono.just("Java编程"))
// 这里带上了对应的Id
.contextWrite(Context.of(HTTP_CORRELATION_ID, "123"))
.filter(t -> t.getT1() < 300)// getT1 获取第一个元素
.map(Tuple2::getT2);
// 最终的输出结果
StepVerifier.create(put)
.expectNext("PUT sent to 书库" +
" with header X-Correlation-ID = 123")
.verifyComplete();
}
若将代码contextWrite(Context.of(HTTP_CORRELATION_ID, "123"))
进行修改,如:
.contextWrite(Context.of("xxx", "123"))