스프링 리액터 시작하기 (7) webflux.fn 이용해 서버 띄우기

brian Byeon·2022년 5월 2일
0

0. 자료의 출처

https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-fn-running

오해가 없도록 어떤 text를 인용했는지 원문을 첨부합니다.🤗

1. Running a server

A more typical option, also used by Spring Boot, is to run with a DispatcherHandler-based setup through the WebFlux Config, which uses Spring configuration to declare the components required to process requests. The WebFlux Java configuration declares the following infrastructure components to support functional endpoints:

RouterFunctionMapping: Detects one or more RouterFunction<?> beans in the Spring configuration, orders them, combines them through RouterFunction.andOther, and routes requests to the resulting composed RouterFunction.

HandlerFunctionAdapter: Simple adapter that lets DispatcherHandler invoke a HandlerFunction that was mapped to a request.

ServerResponseResultHandler: Handles the result from the invocation of a HandlerFunction by invoking the writeTo method of the ServerResponse.

The preceding components let functional endpoints fit within the DispatcherHandler request processing lifecycle and also (potentially) run side by side with annotated controllers, if any are declared. It is also how functional endpoints are enabled by the Spring Boot WebFlux starter.

지난 시간에 이어, webFlux에서 functional endpoints를 이용해 server를 만들어봅니다. 이제 서버를 만드는데 중요한 것은 @Controller 클래스 밑에 @RequestMapping을 붙여놓은 메서드와 같은 역할을 하는 Handler Function들입니다. Router 함수들은 요청의 url이나 HTTP method에 맞춰 이 Handler Function들을 리턴해주게 되어 있습니다.

이제 앞서 이런 라우터함수들을 HttpHandler로 바꿔서 서버의 config에 전달하면 된다고 했습니다. 그런데 더 좋은 옵션은 Spring Boot에서는 기본적으로 사용되는 DispatcherHandler를 사용해서 웹서버를 띄우는 것입니다. DispatcherHandler는 WebFlux Config를 통해 setup이 되어서 Spring configuration을 사용해 request를 처리하기 위한 components를 만들어냅니다. WebFlux Java Configuration은 다음 컴포넌트를 선언해서 functional endpoints를 지원합니다.

  • RouterFunctionMapping: Spring configuration에서부터 1개 이상의 RouterFunction<?>을 찾아 RouterFunction.andOther()로 연결시킵니다. 그리고 모든 router function들이 연결되었을 때 이를 통해 route를 처리하게 합니다
  • HandlerFunctionAdapter: request에 알맞은 route를 갖는 handlerFunction을 DispatcherHandler가 실행토록 합니다
  • ServerResponseResultHandler: HandlerFunction에 의해 실행된 결과를 ServerResponse의 writeTo 메서드를 통해 handle합니다.

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Bean
    public RouterFunction<?> routerFunctionA() {
        // ...
    }

    @Bean
    public RouterFunction<?> routerFunctionB() {
        // ...
    }

    // ...

    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        // configure message conversion...
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // configure CORS...
    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        // configure view resolution for HTML rendering...
    }
}

이런게 WebFlux java configuration의 예시이다.
저기 등록된 RouterFunction들이 연결되어 사용될 것이다.

2. WebFlux app from scratch

//https://spring.io/guides/gs/reactive-rest-service/

1. https://start.spring.io/

를 이용하거나, spring initializer를 통해서 Java8 이상의 SDK를 선택해서 dependency로 spring web reactive를 추가한다.

2. src/main/java/hello 라는 패키지를 만들고,

package hello;

public class Greeting {
    private String message;

    public Greeting() {

    }
    public Greeting(String message){
        this.message=message;
    }
    public String getMessage(){
        return this.message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString(){
        return "Greeting{"+"message'"+message+'\''+'}';
    }
}

Greeting.java생성

3. 이제 functional endpoint를 사용하기 위해 Handler function을 제작하자.

package hello;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

@Component
public class GreetingHandler {
    
    public Mono<ServerResponse> hello(ServerRequest request){
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(new Greeting("Hello, Spring!")));
    }
}

같은 경로에 GreetingHandler.java 생성

이 클래스는 항상 Hello Spring이라는 Greeting 객체를 이용해 Json body를 돌려줄 것이다. streams of item을 return 해도 되고.

4. Router제작

우리는 router를 사용해서 /hello 라는 route를 노출시킬것이다.
src/main/java/hello/GreetingRouter.java를 생성하자

package hello;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;

import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;

@Configuration(proxyBeanMethods = false)
public class GreetingRouter {

    @Bean
    public RouterFunction<ServerResponse> route(GreetingHandler greetingHandler){
        return RouterFunctions
                .route(GET("/hello").and(accept(MediaType.APPLICATION_JSON)),
                        greetingHandler::hello);
    }
}

greetingHandler::hello는 greetingHandler 클래스안의 메서드인데 인자가 하나도 없으면 이런식으로 쓸 수 있다. 아무튼 "/hello"에 대한 GET 요청에 우리는 hello 메서드(handler)를 연결시켰다.

5.WebClient 제작

Spring RestTemplate class는 blocking 코드이다. Reactive Application을 위해서는 async/non-blocking 코드를 쓰고 싶으니까, spring은 WebClient class를 제공해서 non-blocking 특성을 활용할 수 있게 한다.
src/main/java/hello/GreetingClient.java를 만든다.

package hello;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@Component
public class GreetingClient {
    private final WebClient client;

    //Spring Boot auto-configures a `WebClient.Builder` instance
    //이 builder를 사용해서 WebClient for our component 제작.
    public GreetingClient(WebClient.Builder builder){
        this.client=builder.baseUrl("http://localhost:8080").build();
    }
    public Mono<String> getMessage(){
        return this.client.get().uri("/hello").accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(Greeting.class)
                .map(Greeting::getMessage);
    }
}

WebClient는 reactive feature을 써서 Mono형태로 message의 content를 갖고 있으려고 한다. (Greeting::getMessage()로 리턴된 Mono) 이건 function API를 사용해 reactive operator들을 묶은 것이다. 이렇게 Reactive API에 익숙해지는 것은 시간이 걸릴수도 있으나 WebClient의 다양한 기능은 아주 유용하게 쓰일 것이다. 기존 Spring MVC에서도 사용할 수 있다.

6. main 메서드 제작

main메서드를 사용해 우리의 어플리케이션을 실행시키고, /hello url에서 메시지를 볼 수 있을 것이다.

src/main/java/hello/Applcation.java

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class Application {
    public static void main(String[] args){
        ConfigurableApplicationContext context =
                SpringApplication.run(Application.class,args);
        GreetingClient greetingClient = context.getBean(GreetingClient.class);
        //여기서 block을 하는 이유는 JVM이 message를 out하기도전에 꺼져버릴 수도 있기 때문
        System.out.println(">> message= "+greetingClient.getMessage().block());
    }
}

getMessage()에서 돌려받는것은 Mono이기 때문에 block()을 실행시켜서 동기적 실행으로 만든다.

@SpringBootApplication은 기존 boot와 같이 @Configuration, @EnableAutoConfiguration, @ComponentScan을 하게 한다. 그래서 GreetingClient 라는 @Component로 등록된 클래스가 빈으로 가져와진다.

이제 intellij나 eclipse를 사용하고 있다면, spring boot에 대한 기본 설정때문에 tomcat으로 서버가 실행될 것이다. 따라서 ./gradlew build를 통해서 build를 하고 .jar이나 .war파일을 실행시킬 것이다.

또한 오랜만에 gradlew build를 쓴다면 echo $JAVA_HOME을 해보아라. 해당 JDK 버전때문에 build error가 날 수도 있다. java11로 했다면 JAVA_HOME에도 jdk11이 있어야 한다.
또, build.gradle에 아래와 같은 dependency가 포함되어 있다면 webFlux를 사용하는 비동기식 웹서버 Netty가 아닌 tomcat을 사용하고 있을 수도 있다. 로그를 살펴보고 Netty가 아니라면 build.gradle의 dependency들을 살펴보자.

implementation 'org.springframework.boot:spring-boot-starter-web'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
2022-05-02 17:23:36.137  INFO 23951 --- [           main] hello.Application                        : Starting Application using Java 11.0.14.1 on AL02001808.local with PID 23951 SNAPSHOT.war started by user in /Users/user/study/sample_springflux
2022-05-02 17:23:36.139  INFO 23951 --- [           main] hello.Application                        : No active profile set, falling back to 1 default profile: "default"
2022-05-02 17:23:37.080  INFO 23951 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port 8080
2022-05-02 17:23:37.086  INFO 23951 --- [           main] hello.Application                        : Started Application in 1.214 seconds (JVM running for 1.48)
>> message= Hello, Spring!

다음과 같이 Netty로 실행되어야 한다.

URL로 접속하면 이런 결과물이 표시된다.

https://spring.io/guides/gs/reactive-rest-service/
여기까지 spring reactive tutorial이었다.

0개의 댓글