[Spring] 스프링에서 RESTful API와 통신하기

ssunn·2025년 1월 26일

spring

목록 보기
2/2
post-thumbnail

소셜 로그인 기능을 구현할 때 외부 api와 통신해야해서 무작정 RestTemplate을 사용했다.
RestTemplate 왜 쓰셨어요? 라고 물어보신다면
지피티가 된댔어요ㅎ
거의 뭐 무지성 코딩이라고 봐도 무방함^^

그래서 이번에는 RestTemplate이 뭐하는 앤지
요즘은 왜 RestTemplate 대신에 WebClient를 쓰는지
가장 최근에는 이 WebClient마저 대신하는 RestClient가 나왔는데 얘는 또 무슨 기능을 갖고 있는지 알아보려고 한다.

RestTemplate

RestTemplate은 이름에서 예상했듯이 Restful API와 통신하기 위해 만들어진 클래스이다. (별도의 라이브러리인가 했는데 아니다. spring web 의존성 추가하면 자동으로 따라오는 아이다.)
주로 외부 api와 통신하기 위해 사용하는데, 내부 api 호출할 때도 사용한다고 한다. Restful API면 모두 호출 가능하기 때문에 필요시에 내부 api 호출에도 사용할 수 있다.

RestTemplate의 역할

주요 특징

1. 동기식 HTTP 통신

RestTemplate 클래스는 동기적으로 동작한다. 즉, 요청을 보내고 응답이 돌아오기까지는 호출이 차단(블로킹)된다는 것이다. 추후 서술하겠지만 이것이 WebClient 등장에 지대한 영향을 미쳤다.

*동기식 요청(Synchronous Request) & 블로킹 요청(Blocking Request): 클라이언트에서 요청을 보내면 결과가 반환될 때까지 대기를 하는 것을 의미. unblocked 상태가 되면 다음 요청에 대해 처리를 수행한다.
따라서 동기식 요청을 하면 앞선 요청이 완료되기까지 대기가 발생하고, 사용자가 많아질수록 thread waiting이 길어지게 된다.

blocking request process

*비동기식 요청(Asynchronous Request) & 논블로킹 요청(Non-Blocking Request): 블로킹 요청의 thread waiting 문제를 보완하기 위한 방식이라고 이해하면 쉽다. 요청을 보내고 결과가 반환되지 않더라도 그 다음 요청을 보낼 수 있는 것을 의미한다. 상호 요청이 많은 분산 시스템이나 대용량 트래픽 처리가 필요할 때 보통 논블로킹 요청을 사용한다. spring에서는 webflux에서 논블로킹 요청을 지원한다.

non-blocking request process

이 두가지 요청 방식을 표로 비교해보았다.

구분블로킹(Blocking)논블로킹(Non-Blocking)
작동 방식요청이 완료될 때까지 현재 스레드가 멈춤요청이 비동기로 처리되어, 스레드가 계속 실행됨
스레드 활용요청당 하나의 스레드 필요여러 요청을 하나의 스레드로 처리 가능
처리 속도스레드 대기 시간 때문에 병목 현상 발생 가능대기 시간 동안 다른 작업을 처리 가능하여 효율적
코드 복잡도상대적으로 간단콜백, 프로미스, 리액티브 프로그래밍으로 복잡할 수 있음
사용 예시Java InputStream / OutputStreamJava NIO, CompletableFuture, Reactive Streams
성능스레드가 많이 필요하여 고비용스레드 수를 절약하여 고성능 처리 가능
적합한 환경I/O 작업이 짧고, 요청 수가 적은 시스템고성능, 대규모 I/O 중심 애플리케이션
응답 시간작업 완료까지 기다려야 함다른 작업과 병렬로 처리 가능
예제HTTP 동기 요청 (예: RestTemplate)HTTP 비동기 요청 (예: WebClient, AsyncHttpClient)

2. 다양한 HTTP 메서드 지원

GET, POST, DELETE, PATCH, PUT 등 지원

3. 객체 매핑

요청/응답 데이터를 객체로 직렬화/역직렬화 할 수 있다. 이를 위해 RestTemplate은 기본적으로 MessageConverter를 사용한다.

*MessageConverter: 요청 및 응답 body의 데이터 형식을 변환하고 요청 및 응답 헤더의 콘텐츠 형식을 설정한다. RestTemplate은 다양한 MessageConverter를 지원하고, MessageConverter는 애플리케이션에서 직접 추가할 수 있다.

RestTemplate restTemplate = new RestTemplate();

HttpHeaders headers = new HttpHeaders();
HttpEntity<String> entity = new HttpEntity<>(headers);

ResponseEntity<String> response = restTemplate.exchange(uriBuilder.toUriString(),
                HttpMethod.POST, entity, String.class);

*직렬화(Serialization) & 역직렬화(Deserialization)

직렬화는 자바 객체를 외부 저장소에 저장하거나 네트워크를 통해 전송하기 위해 객체를 데이터 스트림으로 변환하는 과정이다. 이 때 객체의 필드값들이 데이터 스트림에 쓰여진다.

역직렬화는 데이터 스트림으로부터 객체를 재구성하는 과정이다. 데이터 스트림에서 읽은 값들이 객체의 필드값으로 설정된다. 이 때는 해당 데이터 스트림이 어떤 객체인지 알아야하므로 객체 클래스 정보가 함께 전달되어야한다.

4. 다양한 데이터 형식 지원

RestTemplate을 이용해 HTTP 요청 및 응답 처리를 수행하는 데에 여러가지 데이터 형식으로 처리를 할 수 있다. JSON, XML 및 바이너리 데이터 형식으로도 응답 처리를 할 수 있고, HTTP 요청 및 응답의 부분을 추출하거나 수정할 수 있다.

RestTemplate restTemplate = new RestTemplate();

HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/json");
headers.setContentType(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.set("Authorization", "Bearer " + accessToken);

5. 요청 헤더 및 쿼리 매개변수 설정

요청 헤더는 RestTemplateHttpHeaders 클래스를 사용해 설정할 수 있다. HttpHeaders 클래스의 add() 메서드를 사용해 요청 헤더에 새 항목을 추가할 수 있다.

쿼리 매개변수는 RestTemplateexchange() 메서드를 호출할 때 UriComponentsBuilder를 사용해 설정할 수 있다. UriComponentsBuilderqueryParam() 메서드를 사용해 쿼리 매개변수를 추가할 수 있다.

RestTemplate restTemplate = new RestTemplate();

UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(url)
                                                      .queryParam("grant_type",
                                                                      "authorization_code")
                                                      .queryParam("client_id",
                                                                      kakaoLoginProperties.client_id())
                                                      .queryParam("redirect_uri",
                                                                      kakaoLoginProperties.redirect_uri())
                                                      .queryParam("code", code);
                                                      
HttpHeaders headers = new HttpHeaders();
HttpEntity<String> entity = new HttpEntity<>(headers);

ResponseEntity<String> response = restTemplate.exchange(uriBuilder.toUriString(),
                HttpMethod.POST, entity, String.class);

6. 로깅

HTTP 요청 및 응답에 대한 로깅을 제공한다. RestTemplate Bean을 구성하는 데 사용되는 HttpClient는 HTTP 요청 및 응답을 로깅할 수 있는 HttpClientInterceptor를 제공하기 때문이다.
따라서 HttpClientInterceptor의 구현체로 재구성해 로깅 할 수 있다.

WebClient

RestTemplate의 가장 큰 문제점은 동기식 통신을 지원한다는 것이다. 사용자가 많지 않고 api 요청이 빈번하지 않은 시스템에서는 사용해도 큰 문제가 되지 않겠지만, 사용자가 많고 마이크로 모듈화로 분산 처리한 시스템의 경우 각각의 서버가 서로 통신해야하기 때문에 단일 모듈 시스템에 비해 api 통신이 많아지게 된다. 그렇게 되면 스레드 대기가 무한정 생기는 등의 문제로 이어진다. 이 문제를 해결하기 위해 비동기 통신을 지원하는 라이브러리를 만들게 되었고, 그것이 Spring Boot WebFlux이다. WebClientWebFlux의 일부 기능으로, 사용을 위해선 Spring Boot WebFlux 의존성을 추가해야한다.

*Spring Boot WebFlux: 반응형및 비동기적인 웹 애플리케이션 개발을 지원하는 모듈로, Reactive Streams 사양을 기반으로 비동기적인 이벤트 지향 프로그래밍을 통해 높은 확장성과 성능을 제공한다. (추후 WebFlux에 대한 포스팅 업로드 예정)

Spring MVC에서는 기본적으로 제공하지 않는 기능이기 때문에 WebClient를 사용하기 위해선 Spring WebFlux를 별도로 추가해야한다. 또, WebClient를 사용하면서 MVC에서는 사용하지 않는 많은 라이브러리 의존성들이 추가될 수 밖에 없다.
그래서 Spring Framework 6.1, Spring Boot 3.2.0 버전부터는 WebFlux 의존성 없이도 사용할 수 있는 동기식 HTTP 클라이언트인 RestClient가 도입되었다.

RestClient

RestClient는 RestTemplate과 WebClient의 인프라를 합친 결과물이다.
RestClient는 RestTemplate 기반의 구성 요소를 그대로 사용하는데, message converter, request factory, intercpetor 등이 그 예다.

RestClient의 기능을 하나하나 짚기엔 내용이 너무 길어질 것 같아서 위에서 RestTemplate에 사용했던 코드들을 RestClient를 사용하면 어떻게 되는지 정도만 예를 들어 설명하고, 후에 RestClient에 대한 보다 자세한 포스팅을 이어가도록 하겠다.

GET

RestClient restClient = RestClient.create();

String result = restClient.get()
  .uri("https://example.com")
  .retrieve()
  .body(String.class);

위 코드의 경우 단순히 RESTful API를 GET 해 온 결과를 String을 사용해 받은 경우이다. 만약 ResponseEntity로 결과를 받고 싶다면 아래처럼 구현할 수 있다.

RestClient restClient = RestClient.create();  // RestClient 객체 생성

ResponseEntity<String> result = restClient.get()
  .uri("https://example.com")
  .retrieve()
  .toEntity(String.class);

POST

RestTemplate을 사용했을 때 POST를 사용하면 content type이나 media type과 같은 헤더의 데이터 형식들을 일일이 set 함수를 사용해 지정해주어야했다. 이것을 RestClient에서는 메서드 체이닝으로 보다 손쉽게 구현할 수 있다.

Pet pet = ...
ResponseEntity<Void> response = restClient.post()
  .uri("https://petclinic.example.com/pets/new")
  .contentType(APPLICATION_JSON)
  .body(pet)
  .retrieve()
  .toBodilessEntity();

Error Handling

4xx이나 5xx 상태 코드를 받았을 때 에러 핸들링 처리도 가능하다. onStatus 메서드를 사용해서 CustomException 처리를 할 수 있다.

String result = restClient.get()
  .uri("https://example.com/this-url-does-not-exist")
  .retrieve()
  .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
      throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders())
  })
  .body(String.class);

Exchange

exchange() 메서드는 HTTP 요청을 수행하고 요청 메서드, URL, 헤더, 본문, 응답 타입 등을 세부적으로 지정할 수 있는 강력한 메서드이다. 즉, 메서드 체이닝 없이 exchange() 하나로 이러한 설정들이 가능하다는 것이다. 또, exchange()를 사용하면 모든 응답에 대한 접근들을 제공하는 것이기 때문에 따로 error handling을 위한 작업을 해주지 않아도 된다고 한다..!!! (정말 만능처럼 보인다)
RestTemplate과 RestClient의 코드를 비교하면 다음과 같다.

RestTemplate

RestTemplate restTemplate = new RestTemplate();

// 요청 URL
String url = "https://api.example.com/resource";

// 요청 헤더와 본문 설정
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer token");
HttpEntity<String> entity = new HttpEntity<>(null, headers);

// 요청 실행
ResponseEntity<String> response = restTemplate.exchange(
    url, 
    HttpMethod.GET, 
    entity, 
    String.class
);

// 응답 확인
System.out.println(response.getBody());

RestClient

RestClient client = RestClient.builder().baseUrl("https://api.example.com").build();

// 요청 실행
ResponseEntity<String> response = client.exchange(
    "/resource",
    HttpMethod.GET,
    null, // 요청 본문이 없는 경우
    String.class
);

// 응답 확인
System.out.println(response.getBody());

후기

이번에는 관통 프로젝트를 하면서 잘 모르고 따라하기만 했던 부분을 다시 공부하는 시간을 가져보았다. RestTemplate도 외부 api 쓸 일 없으면 생소한데 WebClient니 RestClient니 새로 나온 라이브러리들이 끝도 없는 것 같아서 정말 배움에는 끝이 없구나 하는 생각이 들었다. Spring이 많이 쓰기도 쓰지만 계속해서 꾸준히 발전하는만큼 공부할 내용이 많아서 더 재밌는 것 같기도 하다. 이번 포스트에서 추후 포스트 업로드 하겠다는 내용들은 꼭 모두 올리도록 하겠다.(진짜로)

p.s. 참고로 이 포스트를 14일에 쓰기 시작했는데 26일에 올린다..ㅋㅋㅋㅋㅋ 다음부터는 미루지 말아야지..

참고

[Java] Spring Boot Web 활용 : RestTemplate 이해하기
[Java] Spring Boot Webflux 이해하기 -1 : 흐름 및 주요 특징 이해
New in Spring 6.1: RestClient

0개의 댓글