/**
* Context 기본 예제
* - contextWrite() Operator로 Context에 데이터 쓰기 작업을 할 수 있다.
* - Context.put()으로 Context에 데이터를 쓸 수 있다.
* - deferContextual() Operator로 Context에 데이터 읽기 작업을 할 수 있다.
* - Context.get()으로 Context에서 데이터를 읽을 수 있다.
* - transformDeferredContextual() Operator로 Operator 중간에서 Context에 데이터 읽기 작업을 할 수 있다.
*/
@Slf4j
public class Example11_1 {
public static void main(String[] args) throws InterruptedException {
Mono
.deferContextual(ctx ->
Mono
.just("Hello" + " " + ctx.get("firstName"))
.doOnNext(data -> log.info("# just doOnNext : {}", data))
)
.subscribeOn(Schedulers.boundedElastic())
.publishOn(Schedulers.parallel())
.transformDeferredContextual(
(mono, ctx) -> mono.map(data -> data + " " + ctx.get("lastName"))
)
.contextWrite(context -> context.put("lastName", "Jobs"))
.contextWrite(context -> context.put("firstName", "Steve"))
.subscribe(data -> log.info("# onNext: {}", data));
Thread.sleep(100L);
}
}
위의 예제 코드에서 contextWrite
Opearator 가 context 에 데이터를 쓰는 부분임을 알 수 있다.
읽는 방식은 두가지 입니다
deferContextual
transformDeferredContextual
11-1 예제에서 두 case 모두 볼 수 있는데, deferContextual
같은 경우 Context 에 저장된 데이터와 원본 데이터 소스의 처리를 지연시키는 역할을 합니다. 또한 파라미터로 정의된 람다 표현식의 람다 파라미터 ctx 는 Context 타입이 아니라 ContextView 타입인데, Context 에 데이터를 읽을 때는 이를 사용해야 한다는 것을 알 수 있습니다. (쓸 때는 그냥 Context)
put(key, value)
: key / value 형태로 Context 에 값을 쓴다.
of(key1, value2, key2, value2...)
: key / value 형태로 Context에 여러개의 값을 쓴다
putAll(contextView)
: 현재 Context 와 파라미터로 입력된 ContextView 를 merge 한다
delete(key)
: Context 에서 key 에 해당하는 value 를 삭제한다
get(key)
: ContextView 에서 key 에 해당하는 value 를 반환한다
getOrEmpty(key)
: ContextView 에서 key 에 해당하는 value 를 Optional 로 래핑해서 반환한다
getOrDefault(key, default value)
: ContextView 에서 key 에 해당하는 value 를 가져오고, 해당하는 value 가 없으면 기본값 제공
hasKey(key)
: ContextView 에서 특정 key 가 존재하는지 확인한다.
isEmpty
: Context 가 비어있는지 확인한다.
size
: Context 내에 있는 key/value 의 개수를 반환한다.
/**
* Context의 특징 예제
* - Context는 각각의 구독을 통해 Reactor Sequence에 연결 되며 체인의 각 Operator는 연결된 Context에 접근할 수 있어야 한다.
*/
@Slf4j
public class Example11_5 {
public static void main(String[] args) throws InterruptedException {
final String key1 = "company";
Mono<String> mono = Mono.deferContextual(ctx ->
Mono.just("Company: " + " " + ctx.get(key1))
)
.publishOn(Schedulers.parallel());
mono.contextWrite(context -> context.put(key1, "Apple"))
.subscribe(data -> log.info("# subscribe1 onNext: {}", data));
mono.contextWrite(context -> context.put(key1, "Microsoft"))
.subscribe(data -> log.info("# subscribe2 onNext: {}", data));
Thread.sleep(100L);
}
}
/**
* Context의 특징 예제
* - Context는 Operator 체인의 아래에서부터 위로 전파된다.
* - 따라서 Operator 체인 상에서 Context read 메서드가 Context write 메서드 밑에 있을 경우에는 write된 값을 read할 수 없다.
*/
@Slf4j
public class Example11_6 {
public static void main(String[] args) throws InterruptedException {
String key1 = "company";
String key2 = "name";
Mono
.deferContextual(ctx ->
Mono.just(ctx.get(key1))
)
.publishOn(Schedulers.parallel())
.contextWrite(context -> context.put(key2, "Bill"))
.transformDeferredContextual((mono, ctx) ->
mono.map(data -> data + ", " + ctx.getOrDefault(key2, "Steve"))
)
.contextWrite(context -> context.put(key1, "Apple"))
.subscribe(data -> log.info("# onNext: {}", data));
Thread.sleep(100L);
}
}
/**
* Context의 특징
* - inner Sequence 내부에서는 외부 Context에 저장된 데이터를 읽을 수 있다.
* - inner Sequence 외부에서는 inner Sequence 내부 Context에 저장된 데이터를 읽을 수 없다.
*/
@Slf4j
public class Example11_7 {
public static void main(String[] args) throws InterruptedException {
String key1 = "company";
Mono
.just("Steve")
.transformDeferredContextual((stringMono, ctx) ->
ctx.get("role"))
.flatMap(name ->
Mono.deferContextual(ctx ->
Mono
.just(ctx.get(key1) + ", " + name)
.transformDeferredContextual((mono, innerCtx) ->
mono.map(data -> data + ", " + innerCtx.get("role"))
)
.contextWrite(context -> context.put("role", "CEO"))
)
)
.publishOn(Schedulers.parallel())
.contextWrite(context -> context.put(key1, "Apple"))
.subscribe(data -> log.info("# onNext: {}", data));
Thread.sleep(100L);
}
}
위의 코드에서 flatMap 내부 시퀀스에서 쓴 Context 의 role 값은 외부에서 읽지 못한다.
/**
* Context 활용 예제
* - 직교성을 가지는 정보를 표현할 때 주로 사용된다.
*/
@Slf4j
public class Example11_8 {
public static final String HEADER_AUTH_TOKEN = "authToken";
public static void main(String[] args) {
Mono<String> mono =
postBook(Mono.just(
new Book("abcd-1111-3533-2809"
, "Reactor's Bible"
,"Kevin"))
)
.contextWrite(Context.of(HEADER_AUTH_TOKEN, "eyJhbGciOi"));
mono.subscribe(data -> log.info("# onNext: {}", data));
}
private static Mono<String> postBook(Mono<Book> book) {
return Mono
.zip(book,
Mono
.deferContextual(ctx ->
Mono.just(ctx.get(HEADER_AUTH_TOKEN)))
)
.flatMap(tuple -> {
String response = "POST the book(" + tuple.getT1().getBookName() +
"," + tuple.getT1().getAuthor() + ") with token: " +
tuple.getT2();
return Mono.just(response); // HTTP POST 전송을 했다고 가정
});
}
}
@AllArgsConstructor
@Data
class Book {
private String isbn;
private String bookName;
private String author;
}
위 코드의 main 시퀀스의 가장 아래쪽에서 contextWrite 로 토큰 정보를 저장하기 때문에 체인 어느 위치에서든 Context 에 접근할 수 있다.
이렇게 Context 는 이렇게 인증 정보 같은 직교성(독립성) 을 가지는 정보를 전송하는데 적합하다.