์๋ฐ์์ ๋น๋๊ธฐ ํธ์ถํ๊ธฐ (AsyncRestTemplate, ListenableFuture)
์๋ ํ์ธ์, ์ต๊ทผ์ ๊ด๋ฆฌ์์ฉ ์ฑ์ ๊ฐ์ ํ๋ค๊ฐ ๋น๋๊ธฐ ์ฒ๋ฆฌ์ ๋ํด์ ๊ณต์ ํ๊ณ ์ถ์ ๊ฒ์ด ์์ด ํฌ์คํ ์ ์์ฑํฉ๋๋ค.
์ ๊ฐ ๊ด๋ฆฌ์ค์ธ ๊ด๋ฆฌ์์ฉ ์ฑ์ Spring Framework 4, Java 8, Bootstrap 3 ๊ธฐ๋ฐ์ผ๋ก ์์ฑ๋์ด์์ต๋๋ค.
์ค๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ์ ๊ทธ๋ ์ด๋ ํ๊ณ ์ถ์ง๋ง ๋ฉ์ธ ์ ๋ฌด๊ฐ ์๋๋ค๋ณด๋ ์งฌ๋ ๋ ์กฐ๊ธ์ฉ ์ ๊ทธ๋ ์ด๋ ํด์ฃผ๊ณ ์๋๋ฐ ๋ถํธ์คํธ๋ฉ์ด๋ ์คํ๋ง ๋ฒ์ ์ ์ฌ๋ฆฌ๊ธฐ์ ์์ ๋์ด ๋ง์๋ณด์ฌ์ ์๊ฑด๋๋ฆฌ๊ณ ์๋ค์.
์๋ฌดํผ ์๋ฐ์์ ๋น๋๊ธฐ๋ก ํธ์ถ์ ํ๋ ๋ฐฉ๋ฒ์ ํฌ๊ฒ ๋ ๊ฐ์ง๊ฐ ์๋ ๊ฒ ๊ฐ์ต๋๋ค.
์ฒซ ๋ฒ์งธ๋ callee ๋ฉ์๋ ์ชฝ์์ @Async ๋ฅผ ๋ถ์ฌ์ ๋น๋๊ธฐ๋ก ๋์ํ๊ฒ ํ๋ ๋ฐฉ๋ฒ์ด ์๊ณ ์
์ด ๋ฐฉ๋ฒ์ callee๊ฐ ์ธ๋ถ ํ์ด๊ฑฐ๋ ์ธ๋ถ ํ์ฌ์ผ ๊ฒฝ์ฐ ๋ถ๊ฐ๋ฅํ๊ฑฐ๋ ์ผ์ด ๋นจ๋ฆฌ ์งํํ๊ธฐ ์ด๋ ค์ธ ์ ์์ต๋๋ค.
๊ตณ์ด ํน๋ณํ ์ด์ ๊ฐ ์์ง ์์์ด์ callee์ชฝ์ ์์ฒญ์ ํ ํ์๋ ์ ํ ์์ต๋๋ค.
ํ์ง๋ง callee๊ฐ ๋ด๋ถ์ ์๋ค๋ฉด ์ํคํ ์ฒ ๊ตฌ์กฐ์ ์ด ๋ฐฉ๋ฒ์ ์ ํํ ์๋ ์์ต๋๋ค.
๋ ๋ฒ์งธ๋ caller๊ฐ ํธ์ถ๋ฐฉ์์ ์ง์ ๋น๋๊ธฐ๋ก ํธ์ถํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
callee๊ฐ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ๋์ง caller๋ ์ ๊ฒฝ์ธ ํ์๊ฐ ์์ฃ .
๋จ์ง ์๋ต๋ง ์ ๋ฐ์์ ์ฒ๋ฆฌํด์ฃผ๋ฉด ๋ฉ๋๋ค.
์ค๋ ์๊ฐํด ๋๋ฆด ๋ด์ฉ์ ๋ ๋ฒ์งธ ๋ฐฉ์์ธ caller ์ ์ฅ์์ ๋น๋๊ธฐ๋ก ํธ์ถํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
์คํ๋ง์ ์ฌ์ฉํ์ ๋ค๋ฉด API ํธ์ถํ์ค ๋ RestTemplate์ ์ฌ์ฉํ์ค ๊ฒ๋๋ค.
๊ทผ๋ฐ ์คํ๋ง์์๋ ๋น๋๊ธฐ ํธ์ถ์ ์ํด์ ์ด๋ฏธ AsyncRestTemplate ์ ์ ๊ณตํ๊ณ ์์ต๋๋ค.
AsyncRestTemplate ์ ์ด์ฉํด์ API๋ฅผ ํธ์ถํ๋ ๋ฐฉ๋ฒ์ RestTemplate๊ณผ ๋ค๋ฅผ๊ฒ ์์ฃ .
๊ทธ๋ฆฌ๊ณ ๋น์ฐํ๋ ์๋ต์ ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ๋ค์ ํ๋ก์ธ์ค๋ฅผ ์ฒ๋ฆฌํ๊ฒ ๋ฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๊ทธ ์๋ต์ ListenableFuture<ResponseEntity<T>> ํ์ ์ผ๋ก ๋ฐ์ต๋๋ค.
๊ฐ๋จํ ์์ ๋ฅผ ํ๋ฒ ๋ณด๊ฒ ์ต๋๋ค.
// Autowired๋ก ์ฃผ์
๋ ์ํ๋ผ ๊ฐ์ ํฉ๋๋ค.
private final AsyncRestTemplate asyncRestTemplate;
public void test() {
URI uri = UriComponentsBuilder.fromUriString("http://localhost")
.path("/test")
.build()
.toUri();
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
HttpEntity<String> requestEntity = new HttpEntity<>(headers);
HttpMethod httpMethod = HttpMethod.POST;
ListenableFuture<ResponseEntity<Void>> res =
asyncRestTemplate.exchange(uri, httpMethod, requestEntity, Void.class);
log.info("ํธ์ถ ์ฑ๊ณต!!");
}
์ ์ฝ๋๋ http://localhost/test ๋ฅผ POST ๋ฐฉ์์ผ๋ก ํธ์ถํ๊ณ ์์ต๋๋ค. ํค๋์ application/json;charset=UTF-8 ์ ๋ณด๋ ๋ฃ์ด์ฃผ๊ณ ์๊ณ ์ ์๋ต๋ฉ์์ง์ ๋ํด์๋ Void๋ก ์ฒ๋ฆฌํ๊ณ ์์ต๋๋ค. ์ด๋ค ๋ฉ์์ง๋ฅผ ์๋ต์ผ๋ก ์ฃผ๋์ง ์ ๊ฒฝ์์ด๋ค๋ ๊ฑฐ๊ฒ ์ฃ .
๊ทธ๋ฆฌ๊ณ ํธ์ถ์ ๋ํ ๊ฒฐ๊ณผ๋ฅผ ListenableFuture<ResponseEntity<Void>> res ์ ๋ฐ๊ฒ ๋ฉ๋๋ค.
ํ์ง๋ง ์๋ต์ ๋ฐ๊ธฐ ์ ์ ํธ์ถํ์๋ง์ ํธ์ถ ์ฑ๊ณต!! ๋ก๊ทธ๋ฅผ ์ถ๋ ฅํ๊ฒ ๋ฉ๋๋ค.
๊ทธ๋ฌ๋ฉด ๊ทธ๋ ๊ฒ ๋ฐ์ ์๋ต์ ๊ฒฐ๊ณผ์ ๋ฐ๋ผ ์ ์๊ณผ ์คํจ๋ฅผ ๊ตฌ๋ถํด์ ์์ธ์ฒ๋ฆฌ๋ฅผ ํด์ค์ผ ํ ํ ๋ฐ ๊ทธ๊ฑด ์ด๋ป๊ฒ ํ ๊น์?
res ์ callback ํจ์๋ฅผ ๋ฃ์ด์ฃผ๋ฉด ๋ฉ๋๋ค.
์ฝ๋ฐฑ์ ์ถ๊ฐํ ์์ ๋ฅผ ๋ณด๊ฒ ์ต๋๋ค.
// Autowired๋ก ์ฃผ์
๋ ์ํ๋ผ ๊ฐ์ ํฉ๋๋ค.
private final AsyncRestTemplate asyncRestTemplate;
public void test() {
URI uri = UriComponentsBuilder.fromUriString("http://localhost")
.path("/test")
.build()
.toUri();
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
HttpEntity<String> requestEntity = new HttpEntity<>(headers);
HttpMethod httpMethod = HttpMethod.POST;
ListenableFuture<ResponseEntity<Void>> res = asyncRestTemplate.exchange(uri, httpMethod, requestEntity, Void.class);
res.addCallback(new ListenableFutureCallback<ResponseEntity<Void>>() {
@Override
public void onFailure(Throwable th) {
log.error("์คํจ");
}
@Override
public void onSuccess(ResponseEntity<Void> voidResponseEntity) {
if (HttpUtil.isNot2xxSuccessful(voidResponseEntity)) {
log.error("์คํจ, ์๋ต์ฝ๋:{}", voidResponseEntity.getStatusCodeValue();
} else {
log.info("์ฑ๊ณต");
// TODO: ์ถ๊ฐ ์์
}
}
});
}
์ฌ๊ธฐ์๋ annonymous ListenableFutureCallback ํด๋์ค๋ฅผ ๋ง๋ค์ด์ addCallback ๋ฉ์๋์ ๋๊ฒจ์ฃผ์์ต๋๋ค.
onFailure ์ onSuccess ๋ฉ์๋๋ฅผ ๊ตฌํํด์ฃผ๋ฉด ๋๋๋ฐ์ ์ด๋ ์ฃผ์ํ ์ ์ด ํ๋ ์์ต๋๋ค.
onSuccess ๋ฉ์๋๊ฐ ํธ์ถ๋๋ ์ผ์ด์ค๊ฐ 2xx ์ฑ๊ณต์ธ ์ผ์ด์ค๋ง์ด ์๋๋ผ๋ ์ ์ ๋๋ค.
4xx ์ผ์ด์ค๋ onSuccess ๋ฉ์๋๋ฅผ ํธ์ถํ๊ฒ ๋ฉ๋๋ค.
onFailure๋ SocketTimeout๊ณผ ๊ฐ์ ์์ธ๊ฐ ๋ฐ์ํ๋ฉด ํธ์ถ๋ฉ๋๋ค.
๋ฐ๋ผ์ ์ค์ง์ ์ผ๋ก ์ฐ๋ฆฌ๊ฐ ์ํ๋ ์ฑ๊ณต ์ response entity์์ ์๋ต์ฝ๋๋ก ํ์ธํด์ค์ผ ํฉ๋๋ค.
์ฐธ๊ณ ๋ก ์ ์ฝ๋์์ HttpUtil.isNot2xxSuccessful(voidResponseEntity) ๋ฉ์๋๋ ์๋์ฒ๋ผ ์ฝ๋ฉ๋์ด ์์ต๋๋ค.
public class HttpUtil {
public static boolean is2xxSuccessful(ResponseEntity responseEntity) {
return HttpStatus.Series.valueOf(responseEntity.getStatusCodeValue())
.equals(HttpStatus.Series.SUCCESSFUL);
}
public static boolean isNot2xxSuccessful(ResponseEntity responseEntity) {
return !is2xxSuccessful(responseEntity);
}
}
ListenableFuture<ResponseEntity<T>> ์์ ์ ๊ณตํ๋ addCallback ๋ฉ์๋ ์๊ทธ๋์ฒ๋ ์๋ ๋ ๊ฐ์ง๊ฐ ์์ต๋๋ค.
void addCallback(ListenableFutureCallback<? super T> var1);
void addCallback(SuccessCallback<? super T> var1, FailureCallback var2);
์ ์์ ์์๋ ์ฒซ ๋ฒ์งธ addCallback ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ต๋๋ค๋ง, ๋ง์ฝ SuccessCallback์ FailureCallback์ ๋๋ ์ ๊ตฌํํ์ฌ ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด ๊ฐ๊ฐ ๊ตฌํํด์ addCallback ๋ฉ์๋์ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌํด์ฃผ์ ๋ ๋ฉ๋๋ค.
์ถ๊ฐ๋ก AsyncRestTemplate ์ ์คํ๋ง์์ ์ ๊ณตํด์ฃผ๋ ๊ธฐ๋ณธ ํด๋์ค๋ฅผ ์ฌ์ฉํด๋ ๋์ง๋ง ํ์์์ ๋ฑ ์ค์ ์ ๋ฐ๊ฟ์ ์ฌ์ฉํด์ผํ๋ค๋ฉด ์๋ ์ฝ๋๋ฅผ ์ฐธ๊ณ ํ์๋ฉด ๋์์ด ๋์ค๊ฒ๋๋ค.
@Bean
public AsyncRestTemplate asyncRestTemplate2() {
SimpleAsyncTaskExecutor simpleAsyncTaskExecutor = new SimpleAsyncTaskExecutor();
simpleAsyncTaskExecutor.setTaskDecorator(new MdcTaskDecorator());
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
simpleClientHttpRequestFactory.setConnectTimeout(30_000); // ๋ฐ๋ฆฌ์ด ๋จ์
simpleClientHttpRequestFactory.setReadTimeout(30_000); // ๋ฐ๋ฆฌ์ด ๋จ์
simpleClientHttpRequestFactory.setTaskExecutor(simpleAsyncTaskExecutor);
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(simpleClientHttpRequestFactory);
return asyncRestTemplate;
}
static class MdcTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
// Web thread context
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
try {
// @Async thread context
MDC.setContextMap(contextMap);
runnable.run();
} finally {
MDC.clear();
}
};
}
}
์ด์์ผ๋ก AsyncRestTemplate์ ์ด์ฉํ์ฌ ์๋ฐ์์ ๋น๋๊ธฐ ํธ์ถํ๊ธฐ์ ๋ํด์ ์์๋ดค์ต๋๋ค.
ํ์ง๋ง AsyncRestTemplate์ Spring 5๋ก ๋์ด๊ฐ๋ฉด์ deprecated ๋์๊ณ , WebClient๋ฅผ ์ฌ์ฉํ๋๋ก ๊ถ์ฅํ๊ณ ์์ต๋๋ค.
Deprecated as of Spring 5.0, in favor of org.springframework.web.reactive.function.client.WebClient
WebClient์ ๋ํด์๋ ๋ค์ ํฌ์คํ ์์ ๋ค๋ฃจ๋๋ก ํ๊ฒ ์ต๋๋ค.
์ค๋๋ ์ฆํํํํ์ธ์~