본문 바로가기
server

RestTemplate

by ejlee 2022. 10. 7.

RestTemplate 이란?

HTTP 통신을 손쉬운 사용을 제공해주는 템플릿입니다. RESTful 형식에 맞게도 사용가능합니다!

Rest API 서비스를 요청후 응답 받을 수 있도록 설계되었으며 HTTP 프로토콜의 메소드(ex. GET, POST, DELETE, PUT)들에 적합한 여러 메소드들을 제공합니다

RestTemplate 의 특성은?

  • Spring 3.0 부터 지원하는 Spring의 HTTP 통신 템플릿
  • HTTP 요청 후 JSON, XML, String 과 같은 응답을 받을 수 있는 템플릿
  • RESTful 형식에 맞추어진 템플릿
  • Header, Content-Type등을 설정하여 타 서비스의 API 호출
  • Server to Server 통신에 사용

 

  • Spring 3부터 지원, REST API 호출이후 응답을 받을 때까지 기다리는 동기 방식
  • Spring 4에선 AsyncResttemplate 지원
  • Spring 5에선 WebClient 를 지원

 

어떤 기능들이 있나

getForObject GET HTTP GET 요청 후 결과는 객체로 반환
getForEntity GET HTTP GET 요청 후 결과는 ResponseEntity로 반환
postForLocation POST HTTP POST 요청 후 결과는 헤더에 저장된 URL을 반환
postForObject POST HTTP POST 요청 후 결과는 객체로 반환
postForEntity POST HTTP POST 요청 후 결과는 ResponseEntity로 반환
delete DELETE HTTP DELETE 요청
headForHeaders HEADER HTTP HEAD 요청 후 헤더정보를 반환
put PUT HTTP PUT 요청
patchForObject PATCH HTTP PATCH 요청 후 결과는 객체로 반환
optionsForAllow OPTIONS 지원하는 HTTP 메소드를 조회
exchange Any 원하는 HTTP 메소드 요청 후 결과는 ResponseEntity로 반환
execute Any Request/Response의 콜백을 수정

RestTemplate 사용

header 에 값을 넣어서 호출하거나, multipartFile 로 파일을 전송한다던가, 값만 가지고온다던가

생각외로 통신하는데 많은 기능들이 내장되어있습니다

@Override
public void scheduleBroadcast() {
    Resttemplate resttemplate = new Resttemplate();
    String url = UriComponentsBuilder.fromUriString("")
            .path("")
            .queryParam("customerNumber", "")
            .build()
            .toUriString();

    ResponseEntity forEntity = restTemplate.getForEntity(url, BroadcastDto.class);

    log.info("entity : {}", forEntity.getStatusCodeValue());
}

@Override
public void scheduleBroadcastForObject() throws JsonProcessingException {
    Resttemplate resttemplate = new Resttemplate();
    String url = UriComponentsBuilder.fromUriString("")
            .path("")
            .queryParam("customerNumber", "")
            .build()
            .toUriString();

    BroadcastDto forEntity = restTemplate.getForObject(url, BroadcastDto.class);
}

간단하게 회사 API 를 개인 프로젝트에서 호출해보겠습니다

잠깐의 꿀팁

resttemplate 응답객체 만들때 항상 응답항목과 응답객체가 안맞다는 에러를 많이 접했습니다

그럴때마다 원인찾고, 다시 구조 맞춰주는게 시간 아깝더라구요

그래서 json 읽어들이고, 바로 클래스를 만들어주는 사이트가 있나 하고 찾아봤는데,

실제로 존재하더라구요

 

연동할 API 를 먼저 Postman 에서 호출하고

내려온 Json 을 전체 복사한 뒤에

https://json2csharp.com/code-converters/json-to-pojo

 

IntelliJ Pluginhttps://github.com/mars-men/GsonFormatPlus

 

해당 사이트에 붙여넣기하면

알아서 응답 DTO 를 생성해줍니다

 

RestTemplate 특성과 올바르게 사용하기

기존 레거시 에 Resttemplate 을 사용하는 걸 보면 필요할 때마다

Resttemplate 객체를 생성해서 사용하는 걸 많이 봤습니다.

 

이렇게 사용하는게 나쁜 건 아니지만, 메모리가 효율적으로 사용되지않고,

기본적으로 제공해주는 기능들의 단점이 많습니다.

저렇게 많은 곳에서 RestTemplate 를 사용할 때마다, 객체가 생성될텐데,

생성된 객체는 Heap 메모리에 남게됩니다.

GC(가비지 컬렉터) 가 주기적으로 사용하지않는 메모리를 정리해주겠지만..

GC 가 청소하지못할 정도로 많은 RestTemplate 객체들이 생성된다면.. Out Of Memory 가 발생할겁니다..

 

그럼 어떻게 효율적으로 RestTemplate 를 사용해야할까요?

new 해서 RestTemplate 를 사용하면 어떻게 동작할까요?

 

먼저 new 해서 RestTemplate 를 사용하면 어떻게 동작하는지 짧고 간단하게 알아봅시다!

JDK가 기본으로 제공하는 HttpURLConnection을 따라가게됩니다.

그러므로 SimpleClientHttpRequestFactory 라는 객체가 자동적으로 생성자로 되어 기능이 동작하게되는데,

 

public abstract class HttpAccessor {

	/** Logger available to subclasses. */
	protected final Log logger = HttpLogging.forLogName(getClass());

	private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();

 

connection pool 이 기본적으로 사용하는 HttpClient 와 팩토리 구현체에 존재하지만,

IP 마다, host 마다 connection pool 을 제어 해줄 수 있지만..

전체 connection pool 을 제어해줄 수 없습니다..

 

connection pool 을 관리하는 KeepAliveCache 객체가 static 변수이다보니 서로 다른 SimpleClientHttpRequestFactory여도 동일한 커넥션 풀을 참조합니다

조건에 맞지않으면 closeServer() 가 호출되어 연결을 끊어버리니

 

[KeepAliveCache]

기본적으론 5개 혹은 http.maxConnection 에 정해진 개수 이상 호출하면 호출이 되지않고 에러가 발생하게 됩니다

 

 

public class HttpClient extends NetworkClient {
    // ...
    public void finished() {
        if (reuse) /* will be reused */
            return;
        keepAliveConnections--;
        poster = null;
        if (keepAliveConnections > 0 && isKeepingAlive() &&
               !(serverOutput.checkError())) {
            /* This connection is keepingAlive && still valid.
             * Return it to the cache.
             */
            putInKeepAliveCache();
        } else {
            closeServer();
        }
    }
 

이를 방지하기 위해 커넥션을 재사용하고 제한 할 필요가 있습니다.

Resttemplate 의 Config 를 구성해봅시다

Bean 부터 등록해주어야하는데요..

    @Bean
    public RestTemplate restTemplateToDefault() {
        return new RestTemplate();
    }

이런식으로 객체를 생성해서 Bean 을 등록해주는 방법이 있습니다.

엇 근데 코드를 보니 connection 을 따로 제어하는 코드는 안보이지않습니까?

이제부터 하나씩 진행해야합니다

Connection pool 을 관리하고 실제로 Http 통신을 실행하는 팩토리 인터페이스를 생성할겁니다

원래 기본적으로 new 를 해서 사용하면 위에서 말했듯이 SimpleClientHttpRequestFactory 가

생성자로 넘겨진다고했습니다

 

하지만 계속 문제가 많다고 지적했구요

다른 팩토리를 사용해봅시다

 

HttpComponentsClientHttpRequestFactory 를 사용해보죠

RestTemplate 내부를 보니 팩토리 구현체의 상위인 인터페이스로 주입받는것도 확인이 되네요

public RestTemplate(ClientHttpRequestFactory requestFactory) {
   this();   
   setRequestFactory(requestFactory);
  }

이 팩토리 구현체로 통신의 time out 을 제어할 수 있습니다

@Bean(name = "restTemplateToDefault")
    public RestTemplate restTemplateToDefault() {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setConnectTimeout(300)                                  
        factory.setReadTimeout(300)                                     
        return new RestTemplate(factory);
    }

꼭 팩토리 구현체를 재정의해주면 new RestTemplate() 에 담아주어야합니다

이제 SimpleClientHttpRequestFactory 의 가장 문제였던 Connection pool 을 새로 제어해보죠

HttpClient 는 Http 통신의 기능을 해주는 인터페이스입니다

JDK 에서 지원해주는 HttpClient 가 아닌

Apache 에서 지원해주는 HttpClient 를 사용해보겠습니다

CloseableHttpClient httpClient = HttpClientBuilder.create()
            .setMaxConnTotal(120)   // 전체 connection pool
            .setMaxConnPerRoute(100)    //IP + Port 당 생성할 수 있는 connection pool
            .setConnectionTimeToLive(5, TimeUnit.SECONDS)   // keep - alive
            .build();

apache 의 HttpClient 를 사용하기 위한 팩토리 객체를 생성해줍시다

그런 후에 위에서 HttpClient 설정해두었던 정보를 Apache HttpClient 구현체인 팩토리에 전달해줍니다

HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(httpClient);  //HttpComponentsClientHttpRequestFactory 생성자에 주입

new RestTemplate(factory) 하는 방법도 있지만, timeOut 설정도 해야하기 때문에

RestTemplateBuilder 를 사용해서 지정해주었습니다.

return restTemplateBuilder
            .requestFactory(() -> factory)
            .setConnectTimeout(Duration.ofMillis(5000)) //TimeOut, 5초
            .setReadTimeout(Duration.ofMillis(5000))    //TimeOut, 5초
            .build();

이제 사용하는 쪽에서 RestTemplate restTemplate = new RestTemplate() 를 하지않고

저희가 커스텀마이징하고 Bean으로 등록한 RestTemplate 주입해서 사용할 수 있습니다

public class OpenApiServiceImpl implements OpenApiService{

    private RestTemplate restTemplate;

    @Autowired
    public OpenApiServiceImpl(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

...

[기존 코드를 수정]

만약 여러곳에 RestTemplate 관련된 세팅들이 있어서 충돌이 난다면?

@Configuration
public class RestTemplateConfig {

    @Bean(name = "anotherResttemplate")
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {

@Bean 어노테이션에 이름을 붙여주면 됩니다

사용하는 쪽에선 @Qualifier 어노테이션을 이용할겁니다

어떤 이름의 Bean 을 가져올건지 지목해주는 기능을 합니다

특정이름의 Bean 을 가져오게끔 설정해두면 정상적으로 동작하는 걸 확인할 수 있습니다.

@Autowired
public OpenApiServiceImpl(@Qualifier("anotherResttemplate") RestTemplate restTemplate) {
    this.restTemplate = restTemplate;
}
 

이상으로 발표를 마치겠습니다.

이번 테크 데이를 준비하면서 놓쳤던 부분을 확인 하다보니, 간과하며 넘어간 부분들은 새로알게 되었습니다.

예를 들어.. 왜 팩토리 인터페이스를 굳이 사용했는지?

RestTemplate 이전에는 어떻게 개발했길래, HttpURLConnection 을 피하려고하는지?

등등.. 저도 발표준비하면서, 궁금한 점이 여러개 있었습니다. resttemplate 사용 이전의 히스토리가 궁금하더라구요..

위키 댓글이나 어디든 물어봐주시면, 저도 관심갖고 최대한 알아내서 공유하겠습니다.

감사합니다.

'server' 카테고리의 다른 글

jdbc 에 대해서 araboja  (0) 2022.04.26

댓글