Spring

WebClient 사용할때 주의 (6편)

ybs 2021. 12. 15. 23:26
반응형

WebClient 로 요청을 보내고 받은 응답 객체는 아래와 같다. 이때 message 값이 nullable 하다고 해보자.

public class ResponseDto {
	private String id;
	@Nullable
	private String message;

	public String getMessage() {
		return message;
	}
}

 

 

ResponseDto 로 deserialize 한 후에 map 연산자를 이용해 message 를 가져오면 NPE가 발생한다.

webClientBuilder
	... 생략
	.retrieve()
	.bodyToMono(ResponseDto.class)
	.map(responseDto -> responseDto.getMessage())
	.onErrorResume(throwable -> {
		return Mono.error(new RuntimeException(throwable));
	});

 

responseDto 가 null이어서 NPE 가 발생하는게 아니라 map 연산자 람다 body 에서 null이 리턴되고 아래 mapper.apply(t) 결과가 null이 되면서 NPE 가 발생한다. 그래서 밑에있는 onErrorResume으로 간다.

 

이 문제를 해결하기 위한 방법이 3가지나 되서 정리했다. 첫번째는 map 대신 mapNotNull 연산자를 사용한다.

.mapNotNull(it -> it.getMessage())

 

mapNotNull 은 mapper.apply(t) 결과가 null이 아닐때만 동작하므로 아까같은 NPE 가 발생하지 않는다.

 

두번째는 먼저 message getter 를 Optional 로 감싼다. 

public class ResponseDto {
	private String id;
	@Nullable
	private String message;

	public Optional<String> getMessage() {
		return Optional.ofNullable(message);
	}
}

 

그후 flatMap 과 Mono.justOrEmpty 연산자를 이용한다.  물론 Mono.justOrEmpty 인자로 Optional 뿐만 아니라 null 도 받을 수 있기 때문에 getMessage 를 반드시 Optional 객체로 감쌀 필요는 없다.

webClientBuilder
	... 생략
	.retrieve()
	.bodyToMono(ResponseDto.class)
	.flatMap(it -> Mono.justOrEmpty(it.getMessage()))
	.onErrorResume(throwable -> {
		return Mono.error(new RuntimeException(throwable));
	});

 

마지막 세번째는 두번째와 비슷하게 ResponseDto 객체의 message getter 를 Optional 로 감싸지만 flatMap 대신 다시 mapNotNull 연산자를 사용한다. 하지만 첫번째와 다르게 getMessage 만 써서는 안되고(Optional 타입을 리턴하므로) orElse(null) 연산자를 추가해 값이 있으면 String 타입으로 없으면 null 이 리턴되게 한다.

webClientBuilder
	... 생략
	.retrieve()
	.bodyToMono(ResponseDto.class)
	.mapNotNull(it -> it.getMessage().orElse(null))
	.onErrorResume(throwable -> {
		return Mono.error(new RuntimeException(throwable));
	});
반응형