카테고리 없음

HTTP Client

히히심 2024. 2. 19. 23:51
728x90

1. RestTemplate

: 원래의 Spring에서 HTTP 요청을 보내기 위해 제공한 HTTP Client

- 오랫동안 사용된 만큼 여전히 사용처가 많음..

- since Spring 3.0...

- Spring 5 버전 이후로는 유지보수 단계.

- 명령형 인터페이스.

 

1) Template 준비하기

- RestTemplate을 Configuration에 Bean 객체로 등록하여 사용한다. 

- 이때 RestTemplateBuilder로 상세 설정을 해줄수도 있고 바로 생성도 가능하다.

//RestTemplateConfig class
@Configuration
public class RestTemplateConfig {

    //RestTemplateBuilder를 활용해 전체 서비스에서 사용할
    //기본 설정을 갖춘 RestTemplate을 Bean으로 등록 가능.
    @Bean
    public RestTemplate defaultRestTemplate(
            RestTemplateBuilder templateBuilder
    ) {
        //그냥 새로 생성해서 사용할 수도 잇음
//        RestTemplate restTemplate = new RestTemplate();
        return templateBuilder.rootUri("http://localhost:8081").build();
    }
}

 

2) GET 요청 보내기

- getForObject : 응답의 Response Body가 어떤 타입일지를 명확히 알고 있으며, 다른 Status Code 등이 필요하지 않을 경우. 돌아온 응답을 두번째 인자로 지정한 객체로 해석해서 반환해주는 메서드.

- getForEntity : 응답의 상세한 형태를 확인하고 싶다면 getForEntity를 사용. 다양하게 활용이 가능(getBody(), getStatusCode() 등)

- 응답 자료형을 잘 모르면 Object로 받아줄 수 있다. (배열은 ArrayList, JSON 객체는 LinkedHashMap으로 반환됨)

 

- ReadOne 하나의 정보 조회하기

  • getForObject
  • getForEntity
// ArticleTemplateClient class
@Component
@Slf4j
@RequiredArgsConstructor
public class ArticleTemplateClient {
    private final RestTemplate restTemplate;

    //GET
    public ArticleDto readOne(Long id) {
        //1. getForObject =객체를 받기위해 GET요청을 한다.
        ArticleDto response =
                restTemplate.getForObject(String.format("/articles/%d", id),
                        ArticleDto.class);
        log.info("response: {}", response);


        //2. getForEntity: ResponseEntity를 받기위해 GET요청을 보낸다.
        ResponseEntity<ArticleDto> responseEntity =
                restTemplate.getForEntity(String.format("/articles/%d", id),
                        ArticleDto.class);
        log.info("responseEntity: {}", responseEntity);
        log.info("status code: {}", responseEntity.getStatusCode());
        log.info("headers: {}", responseEntity.getHeaders());
        log.info("body: {}", responseEntity.getBody());

        //3. getForObject- Object 객체로 받아줄 수도 있다. 
        Object responseObject = restTemplate.getForObject(
                String.format("/articles/%d", id),
                Object.class);
        log.info("responseObject: {}", responseObject.getClass());

        response = responseEntity.getBody();
        return response;
    }
}

// ArticleController class
@Slf4j
@RestController
@RequestMapping("/articles")
@RequiredArgsConstructor
public class ArticleController {
    private final ArticleTemplateClient templateClient;

    @GetMapping("/{id}")
    public ArticleDto readOne(@PathVariable("id") Long id) {
        return templateClient.readOne(id);
    }
}

>> 결과는 요렇게 잘 나왔다. 

response와 responseEntity 모두 나왔고, 

responseObject도 나왔고, responseObject의 형태는 LinkedHashMap이라는 사실을 알 수 있다. 

 

- Read All 여러개의 정보 조회하기

: JSON 배열 응답의 경우에는 배열을 응답으로 받을 수 있다! (ex. ArticleDto[])

+ 또는 exchange를 사용하면 ParameterizedTypeReference를 사용할 수 있다. (List와 같은 Generic을 사용하는 클래스의 모습을 정의하기위한 클래스)

// ArticleTemplateClient
    //readAll
    public List<ArticleDto> readAll() {
        //1. getForObject
        ArticleDto[] response =
                restTemplate.getForObject("/articles",
                        ArticleDto[].class);
        log.info("response type: {}", response.getClass());

        //2. getForEntity
        ResponseEntity<ArticleDto[]> responseEntity =
                restTemplate.getForEntity("/articles", ArticleDto[].class);
        log.info("responseEntity: {}", responseEntity);
        log.info("status code: {}", responseEntity.getStatusCode());

        // exchange : 일반적인 상황에서 HTTP요청의 모든 것(메서드, 헤더, 바디 등)을 묘사하여 요청하기 위한 메서드
        // +ParamterizedTypeReference<T>를 사용하면 List로 반환한다.
        ResponseEntity<List<ArticleDto>> responseListEntity =
                restTemplate.exchange(
                        //String url
                        "/articles",
                        // HttpMethod method
                        HttpMethod.GET,
                        // @Nullable HttpEntity<?> requestEntity
                        null,
                        // Class<T> responseType
                        new ParameterizedTypeReference<>() {}
                );
        log.info("response parameterized: {}", responseListEntity.getBody().getClass());

        //3. getForObject - Object
        Object responseObject = restTemplate.getForObject("/articles", Object.class);
        log.info("responseObject : {}", responseObject.getClass());

        //array를 list로 변환
//        return List.of(response); 혹은
        return Arrays.stream(response).toList();
    }
}

// ArticleController class
@Slf4j
@RestController
@RequestMapping("/articles")
@RequiredArgsConstructor
public class ArticleController {
    private final ArticleTemplateClient templateClient;

    @GetMapping
    public List<ArticleDto> readAll() {
        return templateClient.readAll();
    }
}

>> 결과 로그를 보면

1) response type으로 ArticleDto 나왔고,

2) responseEntity 전체와 status code 200 OK로 나왔다

3) response parameterized된 responseEntity의 클래스는 ArrayList로 잘 저장이 되어있다.

4) Object 형식으로 저장한 것도 ArrayList 형식으로 저장되어있다는 것을 알 수 있다. 

 

 

3) POST

- postForObject : 객체를 받기 위해 POST 요청을 한다.

- postForEntity : ResponseEntity를 받기 위해 POST 요청을 한다.

// ArticleTemplateClient class
@Component
@Slf4j
@RequiredArgsConstructor
public class ArticleTemplateClient {
    private final RestTemplate restTemplate;

    public ArticleDto create(ArticleDto dto) {
        // postForObject : 객체를 받기위해 POST 요청을 한다.

        ArticleDto response =
                restTemplate.postForObject(
                        //1.요청 url
                        "/articles",
                        //2.RequestBody,
                        dto,
                        //  3.Response Body의 Type
                        ArticleDto.class);
        log.info("response: {}", response);

        //postForEntity : ResponseEntity를 받기 위해 POST 요청을 한다.
        ResponseEntity<ArticleDto> responseEntity =
                restTemplate.postForEntity("/articles",
                dto,
                ArticleDto.class);
        log.info("responseEntity: {}", responseEntity);
        log.info("status code: {}", responseEntity.getStatusCode());
        log.info("headers: {}", responseEntity.getHeaders());
        log.info("body: {}", responseEntity.getBody());

        response = responseEntity.getBody();
        return response;
    }
}

// ArticleController class
@Slf4j
@RestController
@RequestMapping("/articles")
@RequiredArgsConstructor
public class ArticleController {
    private final ArticleTemplateClient templateClient;

    @PostMapping
    public ArticleDto create(
            @RequestBody
            ArticleDto dto) {
        return templateClient.create(dto);
    }
}

 

>> 짠 Insert 정확히 들어갔다. 한 메서드에 두 번의 post 요청이 들어가게 된다. 

1) response

2) responseEntity 요렇게 두번의 삽입이 일어난다!

 

4) PUT&DELETE

- PUT

: exchange 메서드를 이용해 HTTP 요청을 상세 묘사해준다. & 이 때 Body를 HttpEntity로 만들어주어야 한다. 

> PUT의 경우 GET이나 POST와 같이 응답을 반환하는 메서드가 없기 때문에..

// ArticleTemplateClient class
    // PUT
    public ArticleDto update(Long id, ArticleDto dto) {
    	// 이렇게 표현해볼 수 있지만, 이렇게 되면 해당 id를 가진 객체가 수정이 되는 것이 아니라
        // id가 null로 나오며 RequestBody ArticleDto 그 자체가 들어가게 된다. 
        // 반환도 하지 않음. (void put())
        // 그러므로 이렇게 사용하지 않기!
        restTemplate.put(String.format("/articles/%d", id),
                dto);

        //해답은 exchange
        ResponseEntity<ArticleDto> responseEntity =
                restTemplate.exchange(
                        // String url
                        String.format("/articles/%d", id),
                        // HttpMethod method
                        HttpMethod.PUT,
                        // HttpEntity<?> requestEntity
                        new HttpEntity<>(dto),
                        // Class<T> responseType
                        ArticleDto.class);
        log.info("status code: {}", responseEntity.getStatusCode());
        log.info("updated:: {}", responseEntity.getBody());

        dto = responseEntity.getBody();
        return dto;
    }
}

// ArticleController class
@Slf4j
@RestController
@RequestMapping("/articles")
@RequiredArgsConstructor
public class ArticleController {
    private final ArticleTemplateClient templateClient;

    @PutMapping("/{id}")
    public ArticleDto update(@PathVariable("id") Long id,
                             @RequestBody ArticleDto dto) {
        return templateClient.update(id, dto);
    }
}

>> exchange 메서드로 수정 완료! id가 null이 아니다~~~!

 

- DELETE

: DELET도 마찬가지로 delete()메서드가 아닌 exchange 메서드를 사용해야 한다. +  이 때 자료형은 Void.class를 활용.

// ArticleTemplateClient
    //DELETE
    public void delete(Long id) {
    	// 응답을 반환하지 않으므로 사용 X
        restTemplate.delete(String.format("/articles/%d", id));

        // 역시 해답은 exchange
        // ResponseEntity<Void>: Response Body가 비어있는 응답
        ResponseEntity<Void> responseEntity = restTemplate.exchange(
                String.format("/articles/%d", id),
                HttpMethod.DELETE,
                null,
                Void.class
        );
        //response는 article에서 보내서 받은 응답을 받아오는 것임.
        log.info("status code: {}", responseEntity.getStatusCode());
    }
}

//ArticleController class
@Slf4j
@RestController
@RequestMapping("/articles")
@RequiredArgsConstructor
public class ArticleController {
    private final ArticleTemplateClient templateClient;

    @DeleteMapping("{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable("id") Long id) {
        templateClient.delete(id);
    }
}

 

>> 4~7까지 삭제 완료.

 

5) Url 구성하기

- String.format() 대신 가변인자로 uriVariables 또는 Map<String, ?> uriVariables을 받는다. >> 인자로 전달한 URL의 일부분을 값으로 대치해서 활용한다. 

RestTemplate class 내의 메서드

// ArticleTemplateClient class
    //readAll
    public List<ArticleDto> readAll() {
        // ... 중략

	// 1. URL 인자 대체하기, 가변갯수인자
        Object responsePage = restTemplate.getForObject(
                // String url
                "/articles/paged?page={page}&limit={limit}",
                // Class<T> responseType
                Object.class,
                // Object ... uriVariables
                0, //page
                5 //limit
        );
        log.info("response object page: {}", responsePage);

	// 2. URL 인자 대체하기, Map<String, Object>
        Map<String, Object> uriVariables = new HashMap<>();
        uriVariables.put("page", 0);
        uriVariables.put("limit", 5);
        responsePage = restTemplate.getForObject(
                "/articles/paged?page={page}&limit={limit}",
                Object.class,
                uriVariables
        );
        log.info("response object page: {}", responsePage);

        return Arrays.stream(response).toList();
    }

>> 이렇게 페이징 되어 결과가 잘 나온다. 

그러나, Query를 많이 추가해야 하는 경우 URL 자체가 길어질 수 있다 >> 이 때 UriComponentsBuilder를 사용한다.

 

- UriComponentsBuilder 사용.

이 때 Builder 패턴 형식으로 Path나 Query Parameter를 설정할 수 있다. 

 

// ArticleTemplateClient class
   //readAll
    public List<ArticleDto> readAll() {
		//... 중략
        
        // 3. UriComponentsBuilder 사용
        log.info("UriComponentsBuilder 사용: "+UriComponentsBuilder.fromUriString("/articles")
                .queryParam("page", 0)
                .queryParam("limit", 5)
                .toUriString());
        return Arrays.stream(response).toList();
    }

이렇게 url을 설정해줄수가 있다

 

** 단, 인자로 %, & 와 같이 URL 상에 표현될 때 특수한 역할이 있는 문자열의 경우....문제가 생길 수 있다. 

> UriComponentsBuilder에서 한번 URL Encoding이 진행된다. 이를 다시 RestTemplate에 전달할 경우 이중으로 Encoding이 될 수 있다. 

// ArticleTemplateClient class
   //readAll
    public List<ArticleDto> readAll() {
		//... 중략
        
        // /test?foo=%25%26 -> /test?foo=%2525%2526
        log.info(UriComponentsBuilder.fromUriString("/articles/test")
                .queryParam("foo", "%&")
                .toUriString());
        // /test?foo=%& -> /test?foo=%25%26
        log.info(UriComponentsBuilder.fromUriString("/test")
                .queryParam("foo", "%&")
                .build(false)
                .toUriString());
    }

>> build를 false로 설정하여 이중으로 Encoding되지 않도록 하였다. 

 

2. WebClient

: 반응형(Reactive) 웹 개발의 수요에 맞춰서 등장한 HTTP Client

- 어떤 함수의 결과에 따라 동작하지 않고, 어떤 엔티티의 변화에 반응해서 동작하는 프로그래밍. 

(반응형 웹은 미래에 다시 알아보자..)

(cf. Bootstrap도 반응형 UI이지만 이때에는 Responsive Web)

 

1) 반응형 웹 관련 의존성 추가.

// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-webflux'

 

2) WebClient 준비하기

: class가 아닌 interface로, builder를 이용해 인스턴스 생성. + 여러가지 기본 설정도 가능. 

// WebClientConfig
@Configuration
public class WebClientConfig {
    @Bean
    public WebClient defaultWebClient() {
        // 그냥 새로 생성해서 사용할수도 있음
        // WebClient webClient = WebClient.create();

        // RestTemplate에 비해 선언형 (함수형) 구조를 가진다.
        return WebClient.builder()
                .baseUrl("http://localhost:8081")
                //처음부터 Header를 기록해두기 때문에 바꿀수가 없다.
                .defaultHeader("test", "foo")
                //request의 header의 default값을 설정해준거기 때문에 바꿀 수 있다.
                .defaultRequest(request -> request.header("test", "value"))
                .defaultStatusHandler(
                        HttpStatusCode::isError,
                        response -> {
                            throw new ResponseStatusException(response.statusCode());
                        }
                )
                .build();
    }
}

 

1) 요청 보내기

- Builder를 사용하는 느낌으로 요청을 보낸다. 

- get(), post() 메서드를 이용하여 메서들 설정하고 HTTP 요청의 모습을 묘사한다.

 

 

- POST 

  • (1) ArticleDto로 받기 : bodyToMono()
  • (2) ResponseEntity로 받기 : toEntity()
  • Mono : 결과가 돌아올 때 어떻게 행동할지를 정의하기 위한 Reactor. 즉시 데이터를 활용하는 것이 아닌, 비동기로 데이터가 들어오는 것을 의미
  • block : 실제로 데이터가 도착할 때까지 대기해, 원하는 형태의 응답으로 돌려준다.
// ArticleWebClient class

@Slf4j
@Component
@RequiredArgsConstructor
public class ArticleWebClient {
    private final WebClient webClient;

    public ArticleDto create(ArticleDto dto) {
    	// 1. ArticleDto로 받기 : bodyToMono()
        //WebClient는 HTTP 요청을 Build한다고 생각해보자.
        ArticleDto response = webClient
                //POST 요청이다.
                .post()
                // uri 경로 설정
                .uri("/articles")
                //Body 설정
                .bodyValue(dto)
                // 여기부터 응답을 어떻게 처리할지로 넘어간다.
                .retrieve()
                //응답을 Mono<ArticleDto>롤 받는다. (반응형 아니면 mono)
                .bodyToMono(ArticleDto.class)
                // 응답을 대기해 결과를 돌려받는다. (동기식 처리)
                .block();
        log.info("response: {}", response);

	//2. ResponseEntity로 받기 : toEntity()
        ResponseEntity<ArticleDto> responseEntity =
                webClient
                        //POST 요청이다.
                        .post()
                        // uri 경로 설정
                        .uri("/articles")
                        //Body 설정
                        .bodyValue(dto)
                        // 여기부터 응답을 어떻게 처리할지
                        .retrieve()
                        // ResponseEntity가 담긴 Mono를 받는다.
                        .toEntity(ArticleDto.class)
                        .block();
        log.info("responseEntity: {}", responseEntity);
        response = responseEntity.getBody();
        return response;
    }
}

>> 이렇게 잘 insert 되고 있다.

 

 

- GET

  • readOne()
  • readAll()
// ArticleWebClient class

@Slf4j
@Component
@RequiredArgsConstructor
public class ArticleWebClient {
    private final WebClient webClient;

	//readOne .get() 
    public ArticleDto readOne(Long id) {
    	// 1. ArticleDto로 받기
        ArticleDto response = webClient
                .get()
                .uri("/articles/{id}", id)
                .retrieve()
                .bodyToMono(ArticleDto.class)
                .block();
        log.info("Read one ~ response: {}", response);

		// 2. Map을 활용한 uriVariables를 인자로 넣어 요청하기 
        Map<String, Object> uriVariables = new HashMap<>();
        uriVariables.put("id", id);
        response = webClient
                .get()
                .uri("/articles/{id}", uriVariables)
                .retrieve()
                .bodyToMono(ArticleDto.class)
                .block();
        log.info("Read One ~ map response: {}", response);
        return response;
    }

	//readAll .get()
    public List<ArticleDto> readAll() {
        //ParameterizedTypeReference 활용
        List<ArticleDto> response =  webClient.get()
                .uri("/articles")
                .retrieve()
                .bodyToMono(new ParameterizedTypeReference<List<ArticleDto>>() {
                })
                .block();
        log.info("ReadAll ~ response type: {}", response.getClass());
        return response;
    }

}

이렇게 잘 나온당~~ 아 참고로 Controller에서 ArticleWebClient를 주입받아 얘를 불러 메서드 사용해주는 걸 잊으면 안된다! 

 

- PUT & DELETE 

// ArticleWebClient class

@Slf4j
@Component
@RequiredArgsConstructor
public class ArticleWebClient {
    private final WebClient webClient;

    public ArticleDto update(Long id, ArticleDto dto) {
        Map<String, Object> uriVariables = new HashMap<>();
        uriVariables.put("id", id);
        ArticleDto response = webClient.put()
                .uri("/articles/{id}", uriVariables)
                .bodyValue(dto)
                .retrieve()
                .bodyToMono(ArticleDto.class)
                .block();
        log.info("response : {}", response);

        ResponseEntity<ArticleDto> responseEntity = webClient.put()
                .uri("/articles/{id}", uriVariables)
                .bodyValue(dto)
                .retrieve()
                .toEntity(ArticleDto.class)
                .block();
        log.info("ResponseEntity로 받기 response : {}", responseEntity.getBody());
        return responseEntity.getBody();
    }

    public void delete(Long id) {
        Map<String, Object> uriVariables = new HashMap<>();
        uriVariables.put("id", id);
        ResponseEntity<?> responseEntity = webClient.delete()
                .uri("/articles/{id}", uriVariables)
                .retrieve()
                .toBodilessEntity()
                .block();
        log.info("delete status code: {}", responseEntity.getStatusCode());
    }

}

 

3. RestClient

: RestTemplate을 대체하는 새로운 동기식 HTTP Client

- WebClient와 거의 비슷하다!

- Mono 대신 Body 객체, ResponseEntity가 반환되는 것 외엔 거의 동일하다.

 

- config

// RestClient class
@Configuration
public class RestClientConfig {
    @Bean
    // RestClient.Builder를 활용해 전체 서비스에서 사용할
    // 기본 설정을 갖춘 RestClient Bean으로 등록 가능
    public RestClient defaultRestClient() {
        // RestClient restClient = RestClient.create();
        return RestClient.builder()
                .baseUrl("http://localhost:8081")
                // authentication에 관한 header 설정 가능.
//                .defaultHeader("Authentication", "Bearer token")
                .defaultHeader("test0", "foo")
                .requestInitializer((request -> request.getHeaders().add("test", "header")))
                .defaultRequest(request ->
                        request.header("test1", "bar"))
                .defaultStatusHandler(
                        HttpStatusCode::isError, (request, response)
                                -> {
                            throw new ResponseStatusException(response.getStatusCode());
                        }
                )
                .build();
    }
}

 

- 동기식 WebClient

: 바로 응답 객체가 반환된다!

- POST

// ArticleRestClient class
@Slf4j
@Component
@RequiredArgsConstructor
public class ArticleRestClient {
    private final RestClient restClient;

    public ArticleDto create(ArticleDto dto) {
        ArticleDto response = restClient.post()
                .uri("/articles")
                .body(dto)
                .retrieve()
                .body(ArticleDto.class);
        log.info("response: {}", response);

        ResponseEntity<ArticleDto> responseEntity =
                restClient.post()
                        .uri("/articles")
                        .body(dto)
                        .retrieve()
                        .toEntity(ArticleDto.class);
        log.info("responseEntity: {}", responseEntity);
        response = responseEntity.getBody();
        return response;
    }
}

 

- GET

// ArticleRestClient class
@Slf4j
@Component
@RequiredArgsConstructor
public class ArticleRestClient {
    private final RestClient restClient;

    public ArticleDto readOne(Long id) {
        ResponseEntity<ArticleDto> responseEntity = restClient.get()
                .uri("/articles/{id}", id)
                .retrieve()
                .toEntity(ArticleDto.class);
        log.info("responseEntity, Object...: {}", responseEntity);

        Map<String, Object> uriVariables = new HashMap<>();
        uriVariables.put("id", id);
        responseEntity = restClient.get()
                .uri("/articles/{id}", id)
                .retrieve()
                .toEntity(ArticleDto.class);
        log.info("responseEntity, Map: {}", responseEntity);
        return responseEntity.getBody();
    }

    public List<ArticleDto> readAll() {
        return restClient.get()
                .uri("/articles")
                .retrieve()
                .body(new ParameterizedTypeReference<>() {
                });
    }
}

 

- PUT, DELETE

// ArticleRestClient class
@Slf4j
@Component
@RequiredArgsConstructor
public class ArticleRestClient {
    private final RestClient restClient;

    public ArticleDto update(Long id, ArticleDto dto) {
        Map<String, Object> uriVariables = new HashMap<>();
        uriVariables.put("id", id);
        ResponseEntity<ArticleDto> responseEntity = restClient.put()
                .uri("/articles/{id}", uriVariables)
                .body(dto)
                .retrieve()
                .toEntity(ArticleDto.class);
        log.info("update responseEntity: {}", responseEntity);
        return responseEntity.getBody();
    }

    public void delete(Long id) {
        ResponseEntity<Void> responseEntity = restClient
                .delete()
                .uri("/articles/{id}", id)
                .retrieve()
                .toBodilessEntity();
        log.info("delete responseEntity: {}", responseEntity);
    }
}

 

4. HTTP Interface

: Java의 interface를 바탕으로 HTTP 요청을 선언적 방식으로 묘사하는 방법.

- Spring 6 이후.

 

1) interface 정의하기

- CRUD 관련 메서드 5가지를 inteface에 정의한다. 

// ArticleHttpInterface 
@HttpExchange("/articles")
public interface ArticleHttpInterface {
    //CRUD를 할거기때문에 해당하는 메서드를 다 만든다.
    ArticleDto create();
    List<ArticleDto> readAll();
    ArticleDto readOne();
    ArticleDto update();
    void delete();
}

 

- 각각 메서드의 실제 기능이 어떤 HTTP Method로 요청이 보내질지에 따라 > @<Method>Exchange 어노테이션을 추가한다. 

+ 요청에 추가할 Body나 Path 변수, Query Parameter 등을 매개변수로 추가해준다. 

// ArticleHttpInterface interface
@HttpExchange("/articles")
public interface ArticleHttpInterface {
    // @<Method>Exchange 어노테이션은 해당 메서드가 실행될 때
    // HTTP Request의 메서드와 Path를 결정한다.

    // Path, Body, (Query) Parameter를 매개변수로
    // 나타내면 HTTP Request에 포함된다.

    //CREATE
    @PostExchange
    ArticleDto create(
            @RequestBody
            ArticleDto dto
    );

    //READ ALL
    @GetExchange
    List<ArticleDto> readAll();

    //READ ONE
    @GetExchange("/{id}")
    ArticleDto readOne(@PathVariable("id")
                       Long id);

    //UPDATE
    @PutExchange("/{id}")
    ArticleDto update(
            @PathVariable("id") Long id,
            @RequestBody ArticleDto dto
    );

    //DELETE
    @DeleteExchange("/{id}")
    void delete(@PathVariable("id") Long id);
}

 

2) Proxy 생성

- 실제로 요청을 실행할 HTTP Client를 이용해 Proxy 객체를 만들어야 한다. >> HttpServiceProxyFactory

  • RestClientAdapter.create(restClient) : restClient를 이용해 RestClientAdapter를 생성하고, 이를 통해 HTTP Client를 만든다.
  • HttpServiceProxyFactory.builderFor(...) : Proxy를 만드는 Factory를 생성
  • .build() : Factory를 빌드.
  • .createClient(ArticleHttpInterface.class) : 위에서 설정한 RestClient를 바탕으로 ArticleHttpInterface 인터페이스의 Proxy 객체를 생성.
  • 생성된 객체를 exchange 필드에 할당하여 외부 HTTP 서비스와 통신할 수 있도록 한다.
@Component
public class ArticleService {
    // 사용할 때는 구현체를 만들어주어야 한다.
    private final ArticleHttpInterface exchange;

	//생성자
    public ArticleService(
            //실제로 요청을 보내는 역할을 하는
            // HTTP Client 객체가 있어야 한다.
            RestClient restClient
    ) {
        exchange = HttpServiceProxyFactory
                //내가 사용할 HTTP Client를 사용할 수 있도록 설정.
                .builderFor(RestClientAdapter.create(restClient))
                // Proxy를 만드는 Factory를 만든다.
                .build()
                //해당 RestClient를 바탕으로 Proxy 객체를 만든다.
                .createClient(ArticleHttpInterface.class);
    }
}

 

3) 일반적인 Java 객체를 사용하듯 메서드 호출.

- 앞서 사용한 RestClient와 동일한 설정을 활용. +뿐만 아니라 WebClient, RestTemplate도 사용 가능 원한다면

- WebClient 사용시 반환형으로 Mono나 Flux를 사용 가능.

// ArticleService class
@Component
public class ArticleService {
    // 사용할 때는 구현체를 만들어주어야 한다.
    private final ArticleHttpInterface exchange;

    public ArticleService(
            //실제로 요청을 보내는 역할을 하는
            // HTTP Client 객체가 있어야 한다.
            // RestClient 객체를 받아와 구현체를 생성할 것.
            RestClient restClient
    ) {
        // Factory가 restClient를 이용하여 RestClientAdapter를 생성하고,
        // 이를 통해 HTTP Client를 만든다.
        // Factory.builderFor()를 사용하여
        exchange = HttpServiceProxyFactory
                //내가 사용할 HTTP Client를 사용할 수 있도록 설정.
                .builderFor(RestClientAdapter.create(restClient))
                // Proxy를 만드는 Factory를 만든다.
                .build()
                //해당 RestClient를 바탕으로 Proxy 객체를 만든다.
                .createClient(ArticleHttpInterface.class);
    }

    public ArticleDto create(ArticleDto dto) {
        return exchange.create(dto);
    }

    public ArticleDto readOne(Long id) {
        return exchange.readOne(id);
    }

    public List<ArticleDto> readAll() {
        return exchange.readAll();
    }

    public ArticleDto update(Long id, ArticleDto dto) {
        return exchange.update(id, dto);
    }

    public void delete(Long id) {
        exchange.delete(id);
    }
}

 

728x90