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์— ๋Œ€ํ•œ ๋‚ด์šฉ์€ ์•„๋ž˜ ๊ธ€์„ ์ฐธ๊ณ ํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.