카테고리 없음

Spring6 @HttpExchange

머룽 2023. 4. 23. 14:06

작년 말부터 spring에서 6.0대 버전을 개발하고 있다. 현재는 M4(5월기준)까지 나온 상태이고 7월쯤에 M5가 나올 예정이다. 예정대로 진행된다면 올해 말 늦으면 내년초에는 아마 GA 버전이 출시 될 예정이다. 로드맵 기준으로는 올해 10월쯤 예상한다.

spring6에 몇몇가지의 변화가 있을예정이다. 예를들어 spring6은 java17이 baseline 버전이다. java17을 사용 못한다면 spring5.x 버전 spirng boot 2.x 버전을 사용해야 한다. 아마 당분간 꾸준히 지원할 예정이니 차근차근 마이그레이션해도 상관없다.

그 중 필자가 제일 관심있어 보이면 기능을 하나 가져왔다. 아직 개발 단계이니 넓은 마음으로 보자. 이런 비슷한 라이브러리는 몇가지 존재 한다. okhttp 로 유명한 Square의 retrofit와 OpenFeign 의 feign이 가장 유명하다. 더 있는지는 모르겠지만 android에서는 retrofit를 가장 많이 쓰고 server 쪽에서는 feign을 많이 쓰는것 같다. 아마도 spring cloud 에서 feign을 지원해서 그런거 같다.

retrofit와 feign을 알고 있다면 대충 어떤 기능인지 알 수 있을 것이다.

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

위는 Retrofit의 sample 코드이다. 위처럼 interface 만으로 Http Api로 변환할 수 있다.

기본 사용

Retrofit기능처럼 spring6에도 동일한 기능이 추가 되었다. 이제는 retrofit나 feign를 사용하지 않아도 interface만으로 api를 호출 할 수 있다.

이제 spring 의 기본 사용법을 한번 살펴보자.

@Bean
GreetingClient gitHubClient() {
    return HttpServiceProxyFactory.builder(new WebClientAdapter(WebClient.builder().build()))
            .build()
            .createClient(GreetingClient.class);
}

@HttpExchange(value = "http://localhost:8080", contentType = "text/html")
interface GreetingClient {

    @GetExchange("/greeting")
    Flux<Greeting> greetings();

    @GetExchange("/greeting/{message}")
    Mono<Greeting> greetings(@PathVariable String message);

    @PostExchange(value = "/greeting", contentType = "application/json")
    Mono<Greeting> greetings(@RequestBody Greeting greeting);
}

@EventListener
public void start(ApplicationStartedEvent ignore) {
    GreetingClient greetingClient = gitHubClient();
    greetingClient.greetings(new Greeting("hello"))
            .then(greetingClient.greetings("hello"))
            .doOnNext(System.out::println)
            .flatMapMany(__ -&gt; greetingClient.greetings())
            .subscribe(System.out::println);
}

기본 사용법은 위와 같다. @HttpExchange, @GetExchange, @PostExchange ... 등으로 설정하여 GET, POST, PUT, DELETE 메서드를 만들 수 있다. (http method 다 존재한다.)

GreetingClient의 proxy 객체를 만들어 bean으로 등록하면 설정은 끝이다. 간단하다. HttpServiceProxyFactory 클래스에는 몇가지 메서드가 존재한다. addCustomResolver는 custom한 parameter를 만들떄 사용하면 된다. setReactiveAdapterRegistry는 spring이 지원해주지 않는 다른 비동기 라이브러리를 사용할때 설정하면 된다. setConversionService 추가 적으로 데이터 변환이 있을 경우 사용하면 된다. 기본 설정은 DefaultConversionService을 사용한다. setBlockTimeout은 block 메서드를 사용할경우 timeout을 지정할 수 있다.

물론 아마 더 메서드가 추가 될 예정인듯 하다.

HttpExchange

다음은 HttpExchange 대해서 알아보자. 사실 @RequestMapping과 사용법은 거의 동일하다. url, method, contentType, accept이 존재 한다. 이건 다 아는 내용이니 설명하지 않겠다. 메서드 별로 사용할 수 있도록 @GetExchange, @PostExchange, @DeleteExchange 등 다 존재 하니 한번 살펴보면 되겠다. 어려운 내용은 없다.

또한 다음과 같이 해도 무방하다.

@Bean
GreetingClient gitHubClient() {
    return HttpServiceProxyFactory.builder(new WebClientAdapter(WebClient.builder().baseUrl("http://localhost:8080").build()))
            .build()
            .createClient(GreetingClient.class);
}

interface GreetingClient {

    @GetExchange("/greeting")
    Flux<Greeting> greetings();

    @GetExchange("/greeting/{message}")
    Mono<Greeting> greetings(@PathVariable String message);

    @PostExchange(value = "/greeting", contentType = "application/json")
    Mono<Greeting> greetings(@RequestBody Greeting greeting);
}

위와 같이 baseUrl을 미리 설정해서 사용해도 된다.

리턴타입

다음은 리턴타입에 대해 알아보자. 여러 리턴타입을 지원한다. 기본적으로 spring6가 지원하는 비동기 타입은 reactor, rxjava3, mutiny, coroutines이 존재한다. 해당 타입들은 다 사용할 수 있다.

동기타입도 지원한다.

interface GreetingClient {

    @GetExchange("/greeting")
    List<Greeting> greetings();

    @GetExchange("/greeting/{message}")
    Greeting greetings(@PathVariable String message);

    @PostExchange(value = "/greeting", contentType = "application/json")
    Greeting greetings(@RequestBody Greeting greeting);
}

위와 같이 작성하면 동기타입으로도 작성할 수 있다.

ResponseEntity<T>, Optional<T> 타입도 지원한다.

interface GreetingClient {

    @GetExchange("/greeting/{message}")
    Optional<Greeting> greetingsOptional(@PathVariable String message);

    @PostExchange(value = "/greeting", contentType = "application/json")
    ResponseEntity<Greeting> greetings(@RequestBody Greeting greeting);

    // asynchronous

    @GetExchange("/greeting/{message}")
    Mono<ResponseEntity<Greeting>> greetings(@PathVariable String message);

    @GetExchange("/greeting")
    Mono<ResponseEntity<Flux<Greeting>>> greetings();

}

파라미터 타입

현재는 @RequestBody, @PathVariable, @RequestHeader, @RequestParam, @CookieValue, HttpMethod, URI가 존재 한다.

어노테이션기반의 Spring webmvc이나 webflux를 사용한적이 있다면 다 아는 내용이다. 어노테이션들은 다 아는 내용이니 예제만 간단히 보자.

interface GreetingClient {

    @PostExchange(value = "/greeting")
    Greeting greetings(@RequestBody Greeting greeting);

    @GetExchange("/greeting/{message}")
    Greeting greetingsOptional(@PathVariable String message);

    @GetExchange(value = "/greeting")
    Greeting greetingsHeader(@RequestHeader String foo);

    @GetExchange(value = "/greeting")
    Greeting greetingsParam(@RequestParam String foo);

    @GetExchange(value = "/greeting")
    Greeting greetingsCookie(@CookieValue String foo);


}

String만 작성했지만 Map<K,V>도 가능하다.

동적으로 method 나 uri을 변경하고 싶을때 HttpMethod, URI을 사용하면 된다.

interface GreetingClient {

    @PostExchange(value = "/greeting")
    Greeting greetings(HttpMethod method);

    @PostExchange(value = "/foo?")
    Greeting greetings(URI uri);

}

이제 막 개발을 시작하는 단계라 완벽하지는 않다. 아직 버그가 종종 있다. 지금 현재는 리턴타입이 Optional은 동작하지 않는다. (수정해서 pr을 날렸다) 스냅샷 버전으로 해야 동작한다. 아직 M5가 안나온 관계로..

또한 coroutines 의 suspend function 도 현재는 동작하지 않는다. 이거 역시 pr날렸는데 머지 될지는 모르겠다. 안타깝게 아직은 spring boot 쪽에선 움직임이 없는 것 같다. 조만간 움직임이 있지 않을까 싶다.

이렇게 오늘은 spring6에 나올 @HttpExchange에 대해서 알아봤다. 아직은 개발단계라서 언제 무엇이 변경 될지 모른다. 기능적으로는 변경 되지 않을 것 같아 보이는데.. 뭐 클래스명이나 메서드명 정도는 변경될수도 있을 것 같다.

오늘은 이만!