본문 바로가기
IT/Spring boot

[Spring boot] Spring Boot 3.2 RestClient의 등장

by kyu-nahc 2024. 8. 18.
반응형

 

Spring Http Client

Spring Framework는 RestTemplate, WebClient와 같은 Http Client를 지원하고 있다.

하지만 이들은 각각 문제점을 가지고 있었다.

대표적으로 RestTemplate은 사용이 직관적이지 못하며, Blocking 동기식으로 동작한다.

따라서 서비스 로직이 굉장히 복잡하거나 오래 걸릴 경우 쓰레드 차단으로 인해 지연이 발생할 수 있다.

 

Spring Framework 3.0 버전에 도입된 후, Template과 유사한 클래스에 Http의 모든 기능을 노출하면,

너무 많은 메소드가 overload 된다는 문제가 존재하였다. 따라서 Spring Framework 5에서는

RestTemplate 대신 리액티브 프로그래밍 기반인 WebClient를 사용하게 되었다.

 

WebClient와 HttpInterface는 싱글 쓰레드에서 동작하는 Non-Blocking 및 반응형이라,

여러 요청을 동시에 수행하고 응답을 비동기 식으로 처리할 수 있었다.

하지만 WebClient는 Spring MVC (spring-boot-starter-web) 에서 기본적으로 제공하지 않기 때문에,

Spring WebFlux ( spring-boot-starter-webflux) 라이브러리를 별도로 추가해야 한다.

WebFlux는 리액티브 프로그래밍을 지원하고, WebClient도 이에 맞춰 동작하도록 설계되었기 때문에,

Spring MVC에서 사용하지 않는 많은 라이브러리가 추가될 수밖에 없는 상황이었다.

 

이에 대한 대안으로 만들어진 것이 RestClient이며, 서블릿 버전의 WebClient이다.

스프링은 Spring 6.1, SpringBoot 3.2부터 새로운 동기식 HTTP 호출 도구인 RestClient를 도입하였다.

RestClient를 사용하면 WebClient와 유사한 방식으로 사용 가능하며,

기존의 message converters, request factories 및 RestTemplate의 구성 요소들을 사용할 수 있다.

 

RestClient ( Get / Post / Put / Delete )

RestClient 객체 생성

RestClient firstClient = RestClient.create();
RestClient secondClient = RestClient.create(new RestTemplate());
RestClient thirdClient = RestClient.builder()
                            .baseUrl("http://localhost:5000")
                            .build();

 

create()라는 static 메소드를 사용하면 RestClient 객체를 생성할 수 있다.

create의 파라미터로 RestTemplate을 넘겨준다면,

기존에 작성된 RestTemplate의 설정을 기반으로 만들 수도 있다.

또한 builder도 사용할 수 있는데, builder를 사용하면

default url, path variables, headers, interceptors, initializers 등을 설정할 수 있다.

 

RestClient Get 요청

RestClient를 이용한 기본적인 Get 요청은 첫 번째 컨트롤러의 메소드와 같다.

만약 요청 상태 코드나 헤더 등이 필요하다면, ResponseEntity로 반환받을 수도 있다.

@GetMapping("/test")
public ResponseEntity<?> getEmailProcess(){
    RestClient restClient = RestClient.create();

    String result = restClient.get()
            .uri("http://localhost:5000/auth/chan/getEmail")
            .retrieve()
            .body(String.class);
            
    return ResponseEntity.ok(result);
}

@GetMapping("/test")
public ResponseEntity<?> getEmailProcess(){
    RestClient restClient = RestClient.create();

    ResponseEntity<String> result = restClient.get()
            .uri("http://localhost:5000/auth/chan/getEmail")
            .retrieve()
            .toEntity(String.class);

    log.info("Response Status - {}", result.getStatusCode());
    log.info("Response headers - {}",result.getHeaders());
    log.info("Response body - {}", result.getBody());
    return ResponseEntity.ok(result.getBody());
}

 

RestClient 역시 Json 변환을 지원하며, 마찬가지로 내부에서는 message converters를 사용한다.

따라서 클래스로 변환하여 값을 반환받을 수도 있다.

@GetMapping("/test")
public ResponseEntity<?> restClientTestProcess(){
    RestClient restClient = RestClient.create();

    User user = restClient.get()
            .uri("http://localhost:5000/auth/member/test")
            .accept(MediaType.APPLICATION_JSON)
            .retrieve()
            .body(User.class);
    return ResponseEntity.ok(user);
}

 

 

RestClient Post 요청

 

POST 요청은 다음과 같이 작성할 수 있다.

리소스 객체를 생성하고 contentTypeJSON으로 설정해 준다.

요청 상태 코드나 헤더 등을 사용하기 위해서는 toBodilessEntity()을 이용하여

ResponseEntity<Void> 객체로 요청 응답을 받아준다.

@GetMapping("/test")
public ResponseEntity<?> restClientTestProcess(){
    RestClient restClient = RestClient.create();
    Message postMessage = new Message(1,"Post Test");

    ResponseEntity<Void> response = restClient.post()
        .uri("http://localhost:5000/api/test")
        .contentType(MediaType.APPLICATION_JSON)
        .body(postMessage)
        .retrieve()
        .toBodilessEntity();
    return ResponseEntity.ok(postMessage);
}

 

RestClient Put 요청

 

Put을 이용한 리소스 업데이트는 Post 요청과 거의 동일한 형태이다.

만약 body에 담을 데이터가 존재한다면, contentType을 설정하고, body 부분에 데이터를 넣어준다.

마찬가지로 요청 상태 코드나 헤더 등을 사용하기 위해서는 toBodilessEntity()을 이용하여

ResponseEntity<Void> 객체로 요청 응답을 받아준다.

@GetMapping("/test")
public ResponseEntity<?> restClientTestProcess(){
    RestClient restClient = RestClient.create();
    Message putMessage = new Message(1,"Change Put Test");

    ResponseEntity<Void> response = restClient.put()
            .uri("http://localhost:5000/api/test")
            .contentType(MediaType.APPLICATION_JSON)
            .body(putMessage)
            .retrieve()
            .toBodilessEntity();
    return ResponseEntity.ok(putMessage);
}

 

RestClient Delete 요청

Delete를 사용하여 리소스 제거 요청을 보내는 코드이다.

delete()를 사용하며, 마찬가지로 요청 상태 코드나 헤더 등을 사용하기 위해서는 

toBodilessEntity()을 이용하여 ResponseEntity<Void> 객체로 요청 응답을 받아준다.

@GetMapping("/test")
public ResponseEntity<?> restClientTestProcess(){
    RestClient restClient = RestClient.create();

    ResponseEntity<Void> response = restClient.delete()
            .uri("http://localhost:5000/api/delete/1")
            .retrieve()
            .toBodilessEntity();
    return ResponseEntity.ok(response);
}

 

RestClient의 에러 핸들링

기본적으로 RestClient는 4xx 또는 5xx 상태 코드를 수신하면

RestClientException의 하위 클래스를 throw 한다.

해당 동작은 Status Handler를 사용하면 재정의할 수도 있다.

@GetMapping("/test")
public ResponseEntity<?> restClientTestProcess(){
    RestClient restClient = RestClient.create();

    User user = restClient.get()
            .uri("http://localhost:5000/auth/member/test")
            .accept(MediaType.APPLICATION_JSON)
            .retrieve()
            .onStatus(HttpStatusCode::is4xxClientError, ((request, response) -> {
                throw new UserNotFoundException(ErrorCode.USER_NOT_FOUND);
            }))
            .body(User.class);
    return ResponseEntity.ok(user);
}

 

 

RestClient의 exchange

exchange()를 사용하면 더욱 향상된 시나리오를 다룰 수 있다.

exchange 메소드를 통해 내부의 request와 response에 접근할 수 있기 때문이다.

참고로 exchange 메소드를 사용한다면, 이전에 설명했던 Status Handler는 적용되지 않는다.

왜냐하면 exchange를 통해 이미 전체 응답에 대한 접근을 제공하므로

필요한 모든 오류 처리를 수행할 수 있기 때문이다.

@GetMapping("/test")
public ResponseEntity<?> restClientTestProcess(){
    RestClient restClient = RestClient.create();
    ObjectMapper objectMapper = new ObjectMapper();

    User result = restClient.get()
            .uri("http://localhost:5000/auth/member/test")
            .accept(MediaType.APPLICATION_JSON)
            .exchange((request,response) -> {
                if (response.getStatusCode().is4xxClientError()){
                    throw new UserNotFoundException(ErrorCode.USER_NOT_FOUND);
                }else{
                    return objectMapper.readValue(response.getBody(), User.class);
                }
             });
    return ResponseEntity.ok(result);
}

 

RestClient는 현대적이고 사용하기 쉬운 API를 제공하는 동기식 Http Client이다.

RestTemplate과 WebClient 사용의 단점을 보완하며, Java 객체를 Http 요청으로 쉽게 변환하고,

Http 응답을 객체로 생성할 수 있는 Http 라이브러리에 대한 추상화를 제공해 준다. 

 

 

참고자료

https://blog.naver.com/seek316/223327989579

https://mangkyu.tistory.com/303

 

 

 

반응형

loading