부트 개념과 활용-32.RestTemplate과 WebClient, 커스터마이징(스프링 REST 클라이언트)
스프링 REST 클라이언트 1부: RestTemplate과 WebClient
RestTemplate
Blocking I/O 기반의 Synchronous API
RestTemplateAutoConfiguration
프로젝트에 spring-web 모듈이 있다면 RestTemplateBuilder를 빈으로 등록해 줍니다.
https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#rest-client-access
구현 코드(RestTemplate)
@RestController
public class SampleController {
@GetMapping("/hello")
public String hello() throws InterruptedException {
Thread.sleep(5000l);
return "hello";
}
@GetMapping("/world")
public String world() throws InterruptedException {
Thread.sleep(3000l);
return "world";
}
}
@Component
public class RestRunner implements ApplicationRunner {
@Autowired
RestTemplateBuilder restTemplateBuilder;
@Override
public void run(ApplicationArguments args) throws Exception {
RestTemplate restTemplate = restTemplateBuilder.build();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// TODO /hello
String helloResult = restTemplate.getForObject("http://localhost:8080/hello", String.class);
System.out.println(helloResult);
// TODO /world
String worldResult = restTemplate.getForObject("http://localhost:8080/world", String.class);
System.out.println(worldResult);
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
}
실행결과
hello
world
StopWatch '': running time = 8135775176 ns
---------------------------------------------
ns % Task name
---------------------------------------------
8135775176 100%
WebClient
Non-Blocking I/O 기반의 Asynchronous API
WebClientAutoConfiguration
프로젝트에 spring-webflux 모듈이 있다면 WebClient.Builder를 빈으로 등록해 줍니다.
https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-client
실습 코드(WebClient)
의존성 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
@Component
public class RestRunner implements ApplicationRunner {
@Autowired
WebClient.Builder builder;
@Override
public void run(ApplicationArguments args) throws Exception {
WebClient webClient = builder.build();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// TODO /hello
Mono<String> helloMono = webClient.get().uri("http://localhost:8080/hello")
.retrieve()
.bodyToMono(String.class);
// 이 상태까지는 진행되지 않음.(Non-Blocking)
// subscribe 해야 진행 (Asynchronous 처리
helloMono.subscribe(s -> {
System.out.println("helloMono : " + s);
if(stopWatch.isRunning()) {
stopWatch.stop();
}
System.out.println(stopWatch.prettyPrint());
stopWatch.start();
});
// TODO /world
Mono<String> worldMono = webClient.get().uri("http://localhost:8080/world")
.retrieve()
.bodyToMono(String.class);
worldMono.subscribe(s -> {
System.out.println("worldMono : " + s);
if(stopWatch.isRunning()) {
stopWatch.stop();
}
System.out.println(stopWatch.prettyPrint());
stopWatch.start();
});
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
}
실행결과
StopWatch '': running time = 151808503 ns
---------------------------------------------
ns % Task name
---------------------------------------------
151808503 100%
2019-12-25 19:55:54.658 INFO 34203 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-12-25 19:55:54.659 INFO 34203 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2019-12-25 19:55:54.662 INFO 34203 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 3 ms
worldMono : world
StopWatch '': running time = 151808503 ns
---------------------------------------------
ns % Task name
---------------------------------------------
151808503 100%
helloMono : hello
StopWatch '': running time = 2113159529 ns
---------------------------------------------
ns % Task name
---------------------------------------------
151808503 007%
1961351026 093%
world가 먼저 찍힌 것을 볼 수 있다.
앞에서 thread sleep을 준 게 world 3초 / hello 5초
world 는 3초만 기다리면 되니까 먼저 찍힌걸 확인할 수 있다.
즉, 코드는 쭉 흘러가고(Non-Blocking 기반),
3초가 지난 후에 worldMono 응답이 도착해서, 스탑워치가 정지되고 찍힌 후, 다시 스타트.
그 이후 2초가 지난 후에 응답이 도착해서(helloMono) hello가 찍히고 스탑워치가 출력되는 걸 볼 수 있다.
RestTemplate vs WebClient
RestTemplate보다는 WebClient를 사용하는 것을 더 추천.
여러가지 API 호출 후 조합하는 것도 가능하고, 유연하게 활용이 가능하다. (+ 시간적인 측면)
a,b,c 호출 및 a,b를 호출해서 받은 값을 c에 활용한다던가.. 등
만약 호출이 단순하거나, 사용하는 호출이 적다면 RestTemplate을 사용해도 큰 차이는 없다.
스프링 REST 클라이언트 2부: 커스터마이징
RestTemplate
- 기본으로 java.net.HttpURLConnection 사용.
- 커스터마이징
- 로컬 커스터마이징
- 글로벌 커스터마이징
- RestTemplateCustomizer
- 빈 재정의
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
@Bean
public RestTemplateCustomizer restTemplateCustomizer(){
return restTemplate -> {
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
// java.net 의 http 커넥션이 아닌 아파치 http 클라이언트를 사용
};
}
WebClient
- 기본으로 Reactor Netty의 HTTP 클라이언트 사용.
- 커스터마이징
- 로컬 커스터마이징
- 글로벌 커스터마이징
- WebClientCustomizer
- 빈 재정의
지역적인 커스터마이징
@Override
public void run(ApplicationArguments args) throws Exception {
WebClient webClient = builder
.baseUrl("http://localhost:8080") // base url 설정 (지역적인 커스터마이징)
.build();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// TODO /hello
Mono<String> helloMono = webClient.get().uri("/hello") // base url +
.retrieve()
.bodyToMono(String.class);
// 이 상태까지는 진행되지 않음.(Non-Blocking)
// subscribe 해야 진행 (Asynchronous 처리
helloMono.subscribe(s -> {
System.out.println("helloMono : " + s);
if(stopWatch.isRunning()) {
stopWatch.stop();
}
System.out.println(stopWatch.prettyPrint());
stopWatch.start();
});
// TODO /world
Mono<String> worldMono = webClient.get().uri("/world") // base url +
.retrieve()
.bodyToMono(String.class);
worldMono.subscribe(s -> {
System.out.println("worldMono : " + s);
if(stopWatch.isRunning()) {
stopWatch.stop();
}
System.out.println(stopWatch.prettyPrint());
stopWatch.start();
});
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
전역적인 커스터마이징
모든 빌더는 기본적으로 base url이 세팅된 상태로 다른 빈들에 주입이 된다.
@SpringBootApplication
public class RestApplication {
public static void main(String[] args) {
SpringApplication.run(RestApplication.class, args);
}
@Bean
public WebClientCustomizer webClientCustomizer(){
return new WebClientCustomizer() {
@Override
public void customize(WebClient.Builder webClientBuilder) {
webClientBuilder.baseUrl("http://localhost:8080");
}
};
}
}
//-> 람다 변환(동일)
@Bean
public WebClientCustomizer webClientCustomizer(){
return webClientBuilder -> webClientBuilder.baseUrl("http://localhost:8080");
}