[Spring Boot] Rest Template

Rest Template이란?

Spring은 REST 서비스의 endpoint를 호출하는 2가지 방법을 제공한다. 방법은 동기, 비동기 방식이 존재하며 이번 Post에서는 동기 방식인 REST template에 대해 알아보고자 한다.
REST Template은 Spring 3.0부터 지원이 되었으며 REST API호출 이후 응답을 받을 때까지 기다리는 방식이다.


📌Rest Template의 특징

  • 시나리오에 대한 템플릿을 HTTP 방식으로 제공해준다.
  • HTTP 서버와의 통신을 단순화해준다.
  • RESTful원칙을 지킨다.
  • json,xml을 쉽게 응답받는다.
  • 단순한 호출로 복잡한 작업을 쉽게 하도록 하여 기계적이고 반복적인 코드들을 깔끔하게 정리해준다. (JdbcTemplate, RedisTemplate 등과 동일한 원칙으로 설계되었다.)
  • REST서비스를 호출하도록 설계되어 HTTP protocol의 메서드에 맞는 여러 메서드를 제공한다.

📌RestTemplate 동작원리

  • HttpClient는 HTTP를 사용하여 통신하는 범용 라이브러리이다.
  • RestTemplate은 HttpClient를 HttpEntity의 json, xml등으로 추상화해서 제공해준다.
  • Rest Template가 없다면 json,xml라이브러리를 사용하여 직접 변환하여야한다.
  1. 어플리케이션이 RestTemplate를 생성하고, URI, HTTP메소드 등의 헤더를 담아 요청한다.
  2. RestTemplate 는 HttpMessageConverter 를 사용하여 requestEntity 를 요청메세지로 변환한다.
  3. RestTemplate 는 ClientHttpRequestFactory 로 부터 ClientHttpRequest 를 가져와서 요청을 보낸다.
  4. ClientHttpRequest 는 요청메세지를 만들어 HTTP 프로토콜을 통해 서버와 통신한다.
  5. RestTemplate 는 ResponseErrorHandler 로 오류를 확인하고 있다면 처리로직을 태운다.
  6. ResponseErrorHandler 는 오류가 있다면 ClientHttpResponse 에서 응답데이터를 가져와서 처리한다.
  7. RestTemplate 는 HttpMessageConverter 를 이용해서 응답메세지를 java object(Class responseType) 로 변환한다.
  8. 어플리케이션에 반환된다.

출처: 빨간색코딩


📌지원하는 여러 Method들

출처: https://advenoh.tistory.com/46


📌사용 예제

Ex) getForObject, getForEntity

getForObject와 getForEntity같은 경우는 parameter로 request를 요청할 uri와 데이터를 받아 저장할 객체의 type을 적어줘야한다.

아래 getForEntity를 사용한 코드의 경우는 Json type으로 데이터를 받기 위해 DTO를 사용하여 받는 데이터 타입을 DTO인 userResponse.class설정한 예시이다.

RestTemplate (getForEntity사용)

@Service
public class RestTemplateService {

    // http://localhost:9090/api/server/hello 로 요청해서 response를 받아오기
    public UserResponse hello(){
        // uri 주소 생성
        URI uri = UriComponentsBuilder
                .fromUriString("http://localhost:9090") //http://localhost에 호출
                .path("/api/server/hello")
                .queryParam("name", "steve")  // query parameter가 필요한 경우 이와 같이 사용
                .queryParam("age", 10)
                .encode()
                .build()
                .toUri();

        System.out.println(uri.toString());

        RestTemplate restTemplete = new RestTemplate();

        ResponseEntity<UserResponse> result = restTemplete.getForEntity(uri, UserResponse.class);
        // entity로 데이터를 가져오겠다(Get)~~
        System.out.println(result.getStatusCode());
        System.out.println(result.getBody());

        return result.getBody();
    }
}

DTO를 사용하지 않고 String, Integer와 같은 데이터 타입으로도 결과를 받을 수 있다. 이러한 경우에는 getForObject를 사용하여야하며 아래와 같이 responseType class를 String.class와 같이 적어주면 된다.

String result = restTemplete.getForObject(uri, String.class);
// getForObject는 Get Request를 uri로 보내서 결과를 두번째 parameter로 적은 Object타입으로 받아온다.

// getForEntity로 하면 결과를 Entity로 받아 http status와 data등 여러 데이터 확인이 가능하다.
ResponseEntity<String> result = restTemplete.getForEntity(uri, String.class);

controller의 코드는 다음과 같이 GetMapping으로 Method를 수행시켰을 때 RestTemplate을 사용한 method를 호출하여 return하는 방식으로 사용된다.

~/req URI로 Get request가 오면 getHello 메서드가 실행이되며 restTemplateService의 hello 메서드가 호출이된다. hello 메서드의 경우 코드를 보면 Server의 주소인http://localhost:9090/api/server/hello주소로 Get Request를 보내서 결과를 받아온다.

Controller

@RestController
@RequestMapping("/api/client")
public class ApiController {

    private final RestTemplateService restTemplateService;

    public ApiController(RestTemplateService restTemplateService) {
        this.restTemplateService = restTemplateService;
    }

    @GetMapping("/req")
    public Req<UserResponse> getHello(){
        return restTemplateService.genericExchange();
    }
}

Ex) postForEntity

postForEntity는 getForEntity와 다르게 2번째 parameter로 request object를 넘겨줘야한다.

아래의 코드의 경우는 내가 UserRequest DTO를 생성한 후 값을 임의로 넣어 Request object로 보내준 예시이다.

RestTemplate

    public UserResponse post() {
        // http://localhost:9090/api/server/user/{userId}/name/{userName} 로 post하기

        URI uri = UriComponentsBuilder
                .fromUriString("http://localhost:9090")
                .path("/api/server/user/{userId}/name/{userName}")
                .encode()
                .build()
                .expand(100, "steve") // {userId}, {userName}에 들어갈 값을 순차적으로 입력
                .toUri();

        System.out.println(uri);

        // object를 넣어주면 object mapper가 json으로 바꿔주고
        // rest template에서 http body에 json을 넣어줄 것이다.
        UserRequest req = new UserRequest();
        req.setName("steve");
        req.setAge(10);

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<UserResponse> response = restTemplate.postForEntity(uri, req, UserResponse.class);
        // uri에 req object를 보내서 응답은 UserResponse.class타입으로 받을 것이다!!
        System.out.println(response.getStatusCode());
        System.out.println(response.getHeaders());
        System.out.println(response.getBody());

        return response.getBody();
    }

Controller

    @GetMapping("/req")
    public UserResponse getHello(){
        return restTemplateService.post();
    }

Header값을 추가하여 같이 전송하는 방법

위의 예제의 경우는 Request를 요청할 때 Header의 정보전송은 따로 없이 데이터가 담긴 Request Object만을 request body에 넣어 Post하였다. 하지만 실무에서 RestTemplate을 사용할 때는 위의 예제와는 다르게 Header값을 추가하여 사용한다고 한다.

Header값과 데이터가 담긴 Object를 같이 보내려면 먼저 RequestEntity객체를 생성하고 RequestEntity객체안에 uri, header내용, request object들을 넣어줘야한다.
그 후 RestTemplate의 exchange()메서드안에 앞서 만든 RequestEntity객체와 response type을 넣어 ResponseEntity객체를 만들어 사용한다.

아직 공부 초기라 설명이 많이 미흡한 것 같다😥 아래 코드를 보면 이해가 훨씬 빠를 것이다.

RestTemplate

 public UserResponse exchange(){
        URI uri = UriComponentsBuilder
                .fromUriString("http://localhost:9090")
                .path("/api/server/user/{userId}/name/{userName}")
                .encode()
                .build()
                .expand(100, "steve") // {userId}, {userName}에 들어갈 값을 순차적으로 입력
                .toUri();

        System.out.println(uri);


        UserRequest req = new UserRequest();
        req.setName("steve");
        req.setAge(10);

        RequestEntity<UserRequest> requestEntity = RequestEntity
                .post(uri)
                .contentType(MediaType.APPLICATION_JSON)
                .header("x-authorization", "abcd")
                .header("custom-header", "ffff")
                .body(req);

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<UserResponse> response = restTemplate.exchange(requestEntity, UserResponse.class);
        // RequestEntity객체와 반환 type을 적어서 출력

        return response.getBody();
    }

코드에서 .header()의 경우 headerName, headerValues값이 순차적으로 들어갑니다.


Request body의 내용이 일정하지 않을 때 (Generic class를 사용)

마지막으로 아래와 같이 Request body의 내용이 header는 일치하나, body의 내용이 다른 경우가 존재할 경우 Generic class를 사용해 코드의 불필요한 중복 작성 없이 이용하는 법을 다뤄보려 합니다.

{
    "header": {
        "response_code": ""
    },
    "body": {
	불규칙한 body내용
    }
}

위의 Json의 형태처럼 Body의 내용이 일관되지 않고 여러 data type을 받는 경우 아래 코드와 같이 Generic Class를 생성하고 해당 객체를 이용하여 RequestEntity, ResponseEntity를 만들어야 합니다.

Req.java

public class Req<T> {
    // request의 body를 generic type으로 설정
    private Header header;

    private T resBody;
    
    ....
}

RestTemplate

    public Req<UserResponse> genericExchange(){
        URI uri = UriComponentsBuilder
                .fromUriString("http://localhost:9090")
                .path("/api/server/user/{userId}/name/{userName}/post2")
                .encode()
                .build()
                .expand(100, "steve")
                .toUri();

        System.out.println(uri);


        UserRequest userRequest = new UserRequest();
        userRequest.setName("steve");
        userRequest.setAge(10);

        Req req = new Req<UserRequest>();
        req.setHeader(new Req.Header());
        req.setResBody(userRequest);



        RequestEntity<Req<UserRequest>> requestEntity = RequestEntity
                .post(uri)
                .contentType(MediaType.APPLICATION_JSON)
                .header("x-authorization", "abcd")
                .header("custom-header", "ffff")
                .body(req);

        RestTemplate restTemplate = new RestTemplate();

        ResponseEntity<Req<UserResponse>> response = restTemplate.exchange(requestEntity, new ParameterizedTypeReference<Req<UserResponse>>(){});
        // Response type으로 Req<UserResponse>.class을 쓰고 싶은데 Generic에는 class를 붙일 수 없다.
        // 그래서 new ParameterizedTypeReference<Req<UserResponse>>(){}를 사용한다. <>안에 type은 앞에서 지정했기에 생략해도 괜찮다

        return response.getBody();
    }

Controller

    @GetMapping("/req")
    public Req<UserResponse> getHello(){
        return restTemplateService.genericExchange();
    }


Reference

좋은 웹페이지 즐겨찾기