2021.04.02 - [Java/Reactive] - 간단한 Flux method 테스트 (Reactive WebFlux) 2
위의 글이 길어져 추가로 작성한다.
마지막에 context가 불변객체로 작성 되며, Context0 ~ Context5, ContextN과 같이 만들어져 있다고 하였는데 이와 관련하여 책에서 아래와 같이 테스트 한 코드가 있다.
@Test
public void contextTest() {
printCurrentContext("top")
.subscriberContext(Context.of("top", "context"))
.flatMap(__ -> {
return printCurrentContext("middle");
})
.subscriberContext(Context.of("middle", "context"))
.flatMap(__ -> {
return printCurrentContext("bottom");
})
.subscriberContext(Context.of("bottom", "context"))
.flatMap(__ -> {
return printCurrentContext("initial");
})
.block();
}
private void print(String id, Context context) {
log.debug("id : {}, context : {}", id, context);
}
private Mono<Context> printCurrentContext(String id) {
return Mono.subscriberContext().doOnNext(context -> print(id, context));
}
위의 내용을 테스트 해 보면 아래와 같이 나오게 된다.
08:23:53.481 [main] DEBUG reactor.util.Loggers - Using Slf4j logging framework
08:25:20.163 [main] DEBUG com.example.demo.reactive.FluxTest - id : top, context : Context3{bottom=context, middle=context, top=context}
08:25:20.168 [main] DEBUG com.example.demo.reactive.FluxTest - id : middle, context : Context2{bottom=context, middle=context}
08:25:20.168 [main] DEBUG com.example.demo.reactive.FluxTest - id : bottom, context : Context1{bottom=context}
08:25:20.168 [main] DEBUG com.example.demo.reactive.FluxTest - id : initial, context : Context0{}
bottom은 Context1 객체, middle은 Context2 객체, top은 Context3 객체이다.
각 ContextX의 객체는 파라미터의 개수에 따라 정의 되어 있는데 무슨 이유에서 이렇게 만든지는 모르겠지만 N/2 의 개수로 ContextX가 된다.
어째든 여기서 말하고자 하는 것은 context가 변할 때 같은 객체가 아니라 기존 객체에 새로운 객체를 merge 하여 만들어 낸다는 것이다.
subscriberContext 메소드를 들어가보면 아래와 같이 되어 있다.
@Deprecated
public final Mono<T> subscriberContext(Context mergeContext) {
return subscriberContext(c -> c.putAll(mergeContext.readOnly()));
}
default Context putAll(ContextView other) {
if (other.isEmpty()) return this;
if (other instanceof CoreContext) {
CoreContext coreContext = (CoreContext) other;
return coreContext.putAllInto(this);
}
ContextN newContext = new ContextN(this.size() + other.size());
this.stream().sequential().forEach(newContext);
other.stream().sequential().forEach(newContext);
if (newContext.size() <= 5) {
// make it return Context{1-5}
return Context.of((Map<?, ?>) newContext);
}
return newContext;
}
context의 putAll을 호출하고 있고 putAll 메소드는 this와 other를 가지고 ContextN 객체를 newContext로 만들어 리턴하고 있다.
이러한 내용을 볼 때 context의 내용이 변경되는 경우에는 기존 내용을 서로 공유할 수 없음을 알 수 있고, 이전 글의 마지막 부분에서 왜 HashMap에 담아 값을 전달했는지 알 수 있다.
다만 코드 상으로 initial 부터 만들어지고 bottom > middle > top 으로 진행되면서 기존 context에 새로운 context가 merge 되는 것으로 예상되나 디버깅 해보면 top > middle > bottom > initial로 진행이 된다. 조금 더 확인이 필요 해 보인다.
의문을 가지고 조금 더 디버깅을 해 보니 다음과 같다.
putAll은 Context.java 파일이 아닌 CoreContext.java에서 구현된 소스를 타고 있다.
@Override
default Context putAll(ContextView other) {
if (other.isEmpty()) return this;
if (other instanceof CoreContext) {
CoreContext coreContext = (CoreContext) other;
return coreContext.putAllInto(this);
}
ContextN newContext = new ContextN(this.size() + other.size());
this.unsafePutAllInto(newContext);
other.stream().sequential().forEach(newContext);
if (newContext.size() <= 5) {
// make it return Context{1-5}
return Context.of((Map<?, ?>) newContext);
}
return newContext;
}
other가 CoreContext 중 하나라서 coreContext.putAllInto 메소드가 실행 되며 putAllInto 메소드는 Context0 ~ ContextN 까지 구현되어 있다. coreContext의 타입에 따라 각기 다른 클래스의 메소드가 실행 되며 숫자에 따라 구현 방식이 아래와 같이 조금씩 다르게 구현되어 있다.
// Context0.java
@Override
public Context putAllInto(Context base) {
return base;
}
// Context1.java
@Override
public Context putAllInto(Context base) {
return base.put(key, value);
}
// Context2.java
@Override
public Context putAllInto(Context base) {
return base
.put(this.key1, this.value1)
.put(this.key2, this.value2);
}
...
// Context5.java
@Override
public Context putAllInto(Context base) {
return base
.put(this.key1, this.value1)
.put(this.key2, this.value2)
.put(this.key3, this.value3)
.put(this.key4, this.value4)
.put(this.key5, this.value5);
}
// put example by Context2.java
@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);
}
결국 put에서의 구현은 new ContextX 식으로 만들어지게 된다.
실제 디버깅을 해 보면 putAll에서 this와 other가 merge 되면서 Context의 생성 되는 순서가 initial > bottom > middle > top 식으로 올라가는 것이 보이게 된다.
'Java > Reactive' 카테고리의 다른 글
Flux Test 중 희한한 현상 (0) | 2021.04.05 |
---|---|
간단한 Flux method 테스트 (Reactive WebFlux) 2 (0) | 2021.04.02 |
간단한 Flux method 테스트 (Reactive WebFlux) (0) | 2021.03.26 |
Reative 관련 북마크 (0) | 2021.01.27 |