RestTemplate ์ Deprecated ๋ ๊ฒ์ด๋ค.
์คํ๋ง ํ๋ ์์ํฌ์์ ์ ๊ณตํ๋ RestTemplate ํด๋์ค๋ฅผ ์ดํด๋ณด๋ฉด ์๋์ ๊ฐ์ ์ฝ๋ฉํธ๋ฅผ ํ์ธํ ์ ์๋ค.
NOTE: As of 5.0 this class is in maintenance mode, with only minor requests for changes and bugs to be accepted going forward. Please, consider using the org.springframework.web.reactive.client.WebClient which has a more modern API and supports sync, async, and streaming scenarios.
๊ฐ๋จํ ์์ฝํ๋ฉด RestTemplate ๋ณด๋ค๋ ๋ ํ๋์ ์ธ WebClient๋ฅผ ์ฌ์ฉํ๋ผ๋ ๋ป์ด๋ค. ์๋ง ํฅํ ๋ฒ์ ์์ Deprecated ๋์ง ์์๊น ์ถ๋ค.
RestTemplate๊ณผ WebClient ์์
Blocking I/O ๊ธฐ๋ฐ์ ๋๊ธฐ์(Synchronous) API์ธ RestTemplate๊ณผ Non-Blocking I/O ๊ธฐ๋ฐ์ ๋น๋๊ธฐ์(Asynchronous) API์ธ
WebClient๊ฐ ์ด๋ค ์ฐจ์ด๋ฅผ ๊ฐ๋์ง ์์ ๋ฅผ ํตํด ์ดํด๋ณด์.
RestTemplate
๋จผ์ RestTemplate์ ์ฌ์ฉํ๋ ์์ ๋ฅผ ์ดํด๋ณด์. ์ฐ์ ํธ์ถ์ ๋ฐ์ ์ปจํธ๋กค๋ฌ๋ฅผ ์ ์ธํ๋ค. ์๋๋ PathVariable๋ก ๋์ด์จ ์ด๋งํผ
๊ธฐ๋ค๋ ธ๋ค๊ฐ โDone!โ ๋ฌธ์์ด์ ๋ฐํํ๋ ์ปจํธ๋กค๋ฌ๋ค.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DelayController {
@GetMapping("/delay/{sec}")
public String delay(@PathVariable String sec) throws InterruptedException {
Thread.sleep(Integer.parseInt(sec) * 1000);
return "Done!";
}
}
์ด๋ฅผ ์คํํ ์ฝ๋๋ ์์ฑํ๋ค. ๊ฐ๋จํ๊ฒ ์คํ๋ง ๋ถํธ ์ ํ๋ฆฌ์ผ์ด์
์ด ๊ตฌ๋๋ ๋ ์คํ๋๋ ApplicationRunner๋ฅผ ํตํด์ ํ
์คํธ๋ฅผ ์งํํ ๊ฒ์ด๋ค.
API ํธ์ถ ํ ์๋ต์ด ์ฌ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ์๊ฐ์ ์ธก์ ํ๊ธฐ ์ํด ์คํ๋ง ํ๋ ์์ํฌ์์ ์ ๊ณตํ๋ StopWatch API๋ฅผ ์ฌ์ฉํ๋ค.
@Component
@Slf4j
@RequiredArgsConstructor
public class RestTemplateRunner implements ApplicationRunner {
private final RestTemplateBuilder restTemplateBuilder;
@Override
public void run(ApplicationArguments args) {
RestTemplate restTemplate = restTemplateBuilder.build();
StopWatch stopWatch = new StopWatch();
log.info("start!");
stopWatch.start();
String resultFor5Sec = restTemplate.getForObject("http://localhost:8080/delay/5", String.class);
log.info("resultFor5Sec: {}", resultFor5Sec);
String resultFor10Sec = restTemplate.getForObject("http://localhost:8080/delay/10", String.class);
log.info("resultFor10Sec: {}", resultFor10Sec);
stopWatch.stop();
log.info("result: {}", stopWatch.getTotalTimeSeconds());
}
}
์คํ ๊ฒฐ๊ณผ๋ ์ด๋ป๊ฒ ๋ ๊น? ๋ก๊ทธ๊ฐ ์ถ๋ ฅ๋ ์๊ฐ์ ๋ณด๋ฉด ์ ์ ์๋ฏ์ด ์ฒ์ โstart!โ ๊ฐ ์ฐํ ์๊ฐ ๋ถํฐ 15์ด ํ์ ๊ฒฐ๊ณผ๊ฐ ์ถ๋ ฅ๋์๋ค.
์ฆ, RestTemplate์ Blocking I/O ๊ธฐ๋ฐ์ด๊ธฐ ๋๋ฌธ์ ๋ชจ๋ ์์ฒญ์ ๋๋ง์น ๋๊น์ง ๊ธฐ๋ค๋ฆฐ๋ค.
2020-10-21 00:22:18.774 main ... : start!
2020-10-21 00:22:23.948 main ... : resultFor5Sec: Done!
2020-10-21 00:22:33.960 main ... : resultFor10Sec: Done!
2020-10-21 00:22:33.961 main ... : result: 15.18629589
WebClient (with WebFlux)
๋ค์์ผ๋ก WebClient๋ฅผ ์ด์ฉํ์ฌ ๋น๋๊ธฐ ํธ์ถ์ ํด๋ณด์. Non-Blocking I/O ๊ธฐ๋ฐ์ด๊ธฐ ๋๋ฌธ์ ๋น๋๊ธฐ์ ์ผ๋ก Http ํธ์ถ์ ํ ์ ์๋ค.
์์ ์ ์ธํ DelayController ์ฝ๋๋ ๋์ผํ๊ฒ ์ฌ์ฉํ๋ฉฐ ์ด๋ฒ์๋ ๋์ผํ๊ฒ ApplicationRunner๋ฅผ ์ด์ฉํ์ฌ ํ
์คํธํ๋ค.
@Component
@Slf4j
@RequiredArgsConstructor
public class WebClientRunner implements ApplicationRunner {
private final WebClient.Builder webClientBuilder;
@Override
public void run(ApplicationArguments args) {
WebClient webClient = webClientBuilder.build();
StopWatch stopWatch = new StopWatch();
log.info("start!");
stopWatch.start();
Mono<String> resultFor5Sec = webClient.get()
.uri("http://localhost:8080/delay/5", String.class)
.retrieve()
.bodyToMono(String.class);
resultFor5Sec.subscribe(result -> {
log.info("resultFor5Sec: {}", result);
if(stopWatch.isRunning()) {
stopWatch.stop();
}
log.info("result(5Sec): {}", stopWatch.getTotalTimeSeconds());
stopWatch.start();
});
Mono<String> resultFor10Sec = webClient.get()
.uri("http://localhost:8080/delay/10", String.class)
.retrieve()
.bodyToMono(String.class);
resultFor10Sec.subscribe(result -> {
log.info("resultFor10Sec: {}", result);
if(stopWatch.isRunning()) {
stopWatch.stop();
}
log.info("result(10Sec): {}", stopWatch.getTotalTimeSeconds());
stopWatch.start();
});
}
}
๊ฒฐ๊ณผ๋ ์ด๋ป๊ฒ ๋ ๊น? ์์ ์ดํด๋ณธ RestTemplate๊ณผ ๋ค๋ฅด๋ค. ์์ ํธ์ถ์ ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ๋น๋๊ธฐ์ ์ผ๋ก ํธ์ถ์ด ์ด๋ค์ง๊ธฐ ๋๋ฌธ์ 5์ด, 10์ด๊ฐ
์์๋๋ API ํธ์ถ์ ๋์์ ์งํํ๋ค.
๋ WebClient ์คํ ๊ฒฐ๊ณผ์์ ์ดํด๋ณผ ๊ฒ์ด ์๋ค. ๋ฐ๋ก ์ค๋ ๋ ์ด๋ฆ์ด๋ค. ๋
ผ๋ธ๋กํน(Non-Blocking) ์ฒ๋ฆฌ๋ฅผ ํ๊ธฐ ์ํ ์์ปค ์ค๋ ๋๊ฐ ์ถ๋ ฅ๋๋
๊ฒ์ ํ์ธํ ์ ์๋ค. RestTemplate ์์๋ ๋ชจ๋ main ์ค๋ ๋์๋ค. ๊ฒ๋ค๊ฐ ๋ ๊ฐ์ ํธ์ถ์ ๊ฐ๊ฐ ๋ค๋ฅธ ์์ปค ์ค๋ ๋๊ฐ ์คํํ ๊ฒ๋ ์ ์ ์๋ค.
2020-10-21 00:32:22.255 main ... : start!
2020-10-21 00:32:27.843 ctor-http-nio-2 ... : resultFor5Sec: Done!
2020-10-21 00:32:27.844 ctor-http-nio-2 ... : result(5Sec): 5.589219641
2020-10-21 00:32:32.809 ctor-http-nio-3 ... : resultFor10Sec: Done!
2020-10-21 00:32:32.810 ctor-http-nio-3 ... : result(10Sec): 10.554501304
๊ธฐ์กด ์ฝ๋๋ฅผ ์ฎ๊ธธ ํ์๋ ์๋ค.
์คํ๋ง ํ๋ ์์ํฌ 5 ๋ฒ์ ์ ์ฌ์ฉํ๊ธฐ ์ํด RestTemplate ์ฝ๋๋ฅผ ๋ชจ๋ WebClient๋ก ๋ณ๊ฒฝํ ํ์๋ ์๋ค.
ํ์ง๋ง ํฅํ ๋์์ ์ํด ์๋กญ๊ฒ ์์ฑํ๋ ์ฝ๋๋ WebClient๋ก ์์ฑํ๋ ๊ฒ์ด ์ข๋ค. RestTemplate์ฒ๋ผ WebClient๋ฅผ ๋๊ธฐ ํธ์ถ์ ํ๋๋ก
ํ ์ ์์ง๋ง ๋ฐ๋๋ก RestTemplate์ WebClient์ฒ๋ผ ๋น๋๊ธฐ์ ์ธ ํธ์ถ์ ํ๋๋ก ํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
์ฆ, RestTemplate์ ์ฌ์ฉํ๋ค๋ฉด ํธ์ถ ์ดํ ์๋ต(response)์ด ์ฌ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ๋๊ธฐ ๋ก์ง์ด ๊ณ์๋ ์ ๋ฐ์ ์๋ค.
์ฐธ๊ณ ๋ก ๋น๋๊ธฐ๋ก ๋์ํ๋ ์ ํ๋ฆฌ์ผ์ด์
์์ ๋ธ๋กํน(Blocking) API๋ฅผ ํธ์ถํ๋ ๊ฒฝ์ฐ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค. ์๋ฅผ ๋ค๋ฉด Reactor์ Mono์ ๊ฒฝ์ฐ
Non-Blocking Thread ์ธ ์ํ์์ block() ๊ณผ ๊ฐ์ ๋ฉ์๋๋ฅผ ํธ์ถํ๊ฒ ๋๋ ๊ฒฝ์ฐ IllegalStateException ์์ธ๋ฅผ ๋์ง๋ค.
๋์ผ๋ก ์์ ์์ ์ฌ์ฉํ ApplicationRunner์ StopWatch์ ๋ํ ๋ด์ฉ์ ์๋ ๊ธ์ ์ฐธ๊ณ ํ์๋ฉด ๋ฉ๋๋ค.