[Java] ์๋ฐ ๋น๋๊ธฐ ํธ์ถํ๊ธฐ (How to make asynchronous call in java)
์๋ฐ์์ ๋น๋๊ธฐ ํธ์ถํ๊ธฐ (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์ ๋ํด์๋ ๋ค์ ํฌ์คํ ์์ ๋ค๋ฃจ๋๋ก ํ๊ฒ ์ต๋๋ค.
์ค๋๋ ์ฆํํํํ์ธ์~