Java/Reactive

간단한 Flux method 테스트 (Reactive WebFlux) 3

체리필터 2021. 4. 12. 15:46
728x90
반응형

2021.04.02 - [Java/Reactive] - 간단한 Flux method 테스트 (Reactive WebFlux) 2

 

간단한 Flux method 테스트 (Reactive WebFlux) 2

2021.03.26 - [Java/Reactive] - 간단한 Flux method 테스트 (Reactive WebFlux) 위 글이 길어져서 새롭게 포스팅을 더 작성한다. usingTest 일회성 리소스에 의존하는 스트림을 만들 때 using 메소드를 사용한다...

www.4te.co.kr

위의 글이 길어져 추가로 작성한다.

마지막에 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 식으로 올라가는 것이 보이게 된다.

 

 

728x90
반응형