somoly.tistory.com
Published 2023. 3. 14. 18:11
Spring 6 의 HTTP Interface SpringBoot

소개

 

Spring 6과 Spring Boot 3에서는 Java 인터페이스를 사용하여 선언적 HTTP 서비스를 정의할 수 있습니다. 이 접근 방식은 Feign과 같은 널리 사용되는 HTTP 클라이언트 라이브러리에서 영감을 얻었으며, Spring Data에서 리포지토리를 정의하는 방식과 유사합니다.

여기에서는 먼저 HTTP 인터페이스를 정의하는 방법을 살펴보겠습니다. 그런 다음 사용 가능한 메서드 어노테이션과 지원되는 메서드 매개변수 및 반환값을 확인합니다. 다음으로, 선언된 HTTP Exchange를 수행하는 프록시 클라이언트인 실제 HTTP 인터페이스 인스턴스를 생성하는 방법을 살펴보겠습니다.

마지막으로 선언적 HTTP 인터페이스와 해당 프록시 클라이언트의 예외 처리 및 테스트를 수행하는 방법을 확인합니다.

 

 

Http Interface

 

선언적 HTTP 인터페이스에는 HTTP 교환을 위한 주석이 달린 메서드가 포함되어 있습니다. 주석이 달린 Java 인터페이스를 사용하여 원격 API 세부 정보를 간단히 표현하고 Spring이 이 인터페이스를 구현하고 교환을 수행하는 프록시를 생성하도록 할 수 있습니다. 이렇게 하면 상용구 코드를 줄이는 데 도움이 됩니다.

 

Exchange Method

 

HTTP 인터페이스와 해당 교환 메서드에 적용할 수 있는 루트 어노테이션은 @HttpExchange입니다. 인터페이스 수준에서 적용하면 모든 교환 메소드에 적용됩니다. 콘텐츠 유형이나 URL 접두사와 같이 모든 인터페이스 메소드에 공통된 속성을 지정하는 데 유용할 수 있습니다.

모든 HTTP 메소드에 대한 추가 어노테이션을 사용할 수 있습니다:

HTTP GET 요청의 경우 @GetExchange
HTTP POST 요청을 위한 @PostExchange
HTTP PUT 요청을 위한 @PutExchange
HTTP PATCH 요청을 위한 @PatchExchange
HTTP DELETE 요청을 위한 @DelectExchange

 

    interface ProductService {
        @GetExchange("/products")
        String getProducts();

        @GetExchange("/products/{id}")
        String getProduct(@PathVariable("id") int id);

        @PostExchange("/products/add")
        Map<String, Integer> saveProduct(@RequestBody String product);

        @DeleteExchange("/products/{id}")
        String deleteProduct(@PathVariable("id") int id);
    }

 

 

Method Parameter

 

예제 인터페이스에서는 메소드 매개변수에 @PathVariable 및 @RequestBody 어노테이션을 사용했습니다. 또한 교환 메서드에 다음과 같은 메서드 매개변수 집합을 사용할 수 있습니다:

URI: 어노테이션 속성을 재정의하여 요청에 대한 URL을 동적으로 설정합니다.
HttpMethod: 요청에 대한 HTTP 메서드를 동적으로 설정하여 어노테이션 속성을 재정의합니다.
요청 헤더: 요청 헤더 이름과 값을 추가하며, 인수는 맵 또는 멀티밸류맵일 수 있습니다.
경로 변수: 요청 URL에 자리 표시자가 있는 값을 대체합니다.
요청 본문: 요청의 본문을 직렬화할 객체 또는 Mono 또는 Flux와 같은 반응형 스트림 퍼블리셔로 제공합니다.
요청 매개변수 이름과 값을 추가하며, 인수는 Map 또는 MultiValueMap일 수 있습니다.
쿠키 값: 쿠키 이름과 값을 추가하며, 인수는 Map 또는 MultiValueMap일 수 있습니다.
요청 매개변수는 콘텐츠 유형 "application/x-www-form-urlencoded"에 대해서만 요청 본문에서 인코딩된다는 점에 유의해야 합니다. 그렇지 않으면 요청 매개변수가 URL 쿼리 매개변수로 추가됩니다.

 

 

Return Values

 

예제 인터페이스에서 교환 메서드는 블로킹 값을 반환합니다. 그러나 선언적 HTTP 인터페이스 교환 메서드는 블로킹 및 반응형 반환값을 모두 지원합니다.

또한 상태 코드나 헤더와 같은 특정 응답 정보만 반환하도록 선택할 수도 있습니다. 서비스 응답에 전혀 관심이 없는 경우 무효를 반환할 수도 있습니다.

요약하자면, HTTP 인터페이스 교환 메서드는 다음과 같은 반환값 세트를 지원합니다:

void, Mono<Void>: 요청을 수행하고 응답 내용을 반환합니다.
HttpHeaders, Mono<HttpHeaders>: 요청을 수행하고 응답 내용을 릴리스한 후 응답 헤더를 반환합니다.
<T>, Mono<T>: 요청을 수행하고 응답 내용을 선언된 타입으로 디코딩합니다.
<T>, Flux<T>: 요청을 수행하고 응답 콘텐츠를 선언된 타입의 스트림으로 디코딩합니다.
ResponseEntity<Void>, Mono<ResponseEntity<Void>>: 요청을 수행하고 응답 내용을 릴리스한 후 상태와 헤더를 포함하는 ResponseEntity를 반환합니다.
ResponseEntity<T>, Mono<ResponseEntity<T>>: 요청을 수행하고, 응답 내용을 해제하고, 상태, 헤더, 디코딩된 본문이 포함된 ResponseEntity를 반환합니다.
Mono<ResponseEntity<Flux<T>>: 요청을 수행하고, 응답 콘텐츠를 릴리스하고, 상태, 헤더, 디코딩된 응답 본문 스트림이 포함된 ResponseEntity를 반환합니다.
ReactiveAdapterRegistry에 등록된 다른 비동기 또는 반응형 유형을 사용할 수도 있습니다.

 

 

API 테스트

 

#build.gradle.kts

plugins {
    java
    id("org.springframework.boot") version "3.0.4"
    id("io.spring.dependency-management") version "1.1.0"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-webflux")
}

 

@SpringBootApplication(proxyBeanMethods = false)
public class HttpInterfaceTestApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = new SpringApplicationBuilder(HttpInterfaceTestApplication.class)
                .web(WebApplicationType.NONE)
                .run(args);

        ProductService productService = ctx.getBean(ProductService.class);
        String products = productService.getProducts();
        System.out.println(products);

        String product = productService.getProduct(1);
        System.out.println(product);

        Map<String, Integer> newProduct = productService.saveProduct("""
                {"title":"New Item"}
                """);
        System.out.println(newProduct);

        String deleted = productService.deleteProduct(1);
        System.out.println(deleted);
    }

    @Bean
    public WebClient productWebClient() {
        return WebClient.create("https://dummyjson.com");
    }

    @Bean
    public ProductService productService(WebClient productWebClient) {
        HttpServiceProxyFactory httpServiceProxyFactory = HttpServiceProxyFactory
                .builder(WebClientAdapter.forClient(productWebClient))
                .build();
        return httpServiceProxyFactory.createClient(ProductService.class);
    }

    interface ProductService {
        @GetExchange("/products")
        String getProducts();

        @GetExchange("/products/{id}")
        String getProduct(@PathVariable("id") int id);

        @PostExchange("/products/add")
        Map<String, Integer> saveProduct(@RequestBody String product);

        @DeleteExchange("/products/{id}")
        String deleteProduct(@PathVariable("id") int id);
    }

}
profile

somoly.tistory.com

@RxCats

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!