b

springframework 5.0 webflux module 본문

spring framework

springframework 5.0 webflux module

dev.bistro 2017. 4. 23. 11:42


오늘 날자 기준으로 SpringFramework는  v5.0.0.M5 (링크) 가 나와있고, RC1 버전이 5월 예정되어 있지만 힘들지싶다 (링크)  Spring 5에는 webflux 모듈이 새로 추가되었지만,  사실 Spring5 마일스톤에서 착실히 진행 중이던  spring-web-reactive가 이름이 바뀐 모듈이다. (JIRA)

0. webflux 모듈은 webmvc를 대체하는 가?

우선 spring-mvc 모듈은 여전히 남아 있다. 기존에서 @RestController 을 사용하던 방식을 여전히 사용 할 수 있다.  (즉 webflux따위 몰라도 된다)

그리고 새로운 방법으로도 사용 할 수 있다. 링크 에서 말하는 것처럼 webflux 모듈은 완전히 새로운 방법 (Java8 람다를 이용한 라우팅/핸들링)과 @Controller와 handling을 사용하는 중간 적인 방법도 사용 가능하다.

요즘들어서 통합테스트의 어려움,  몇년간 Scala에서 느끼던 재미를 Java에서는 느끼지 못해 아쉬웠는데, webflux가 좀 해소해주지 않을까 해서 살펴볼 예정이다.

1. 프로젝트의 생성

Intellij2017.1 버전은 New Project 할때 SpringBoot 2.0.0-SNAPSHOT을 지원한다. 그러므로 쉽게 프로젝트를 생성 할 수 있다. 아니라면  링크 의 pom.xml,  Application.java를  참고한다.

2. Hello Controller 만들기

기존의 mvc모듈이 @RestController, @RequestMapping으로 이루어져 있다면, webflux는 RouterFunction, HandlerFunctions 2개 인터페이스를 알 면 된다. 그리고 HttpServletRequest,HttpServletResponse는 잊고, ServerRequest, ServerResponse를 기억 하면 된다.

가장 기본적인 ‘hello world’를 반환하는 /hello를 만들어보도록 하자.

public class HelloRouter {
    RouterFunction router() {
      return route(RequestPredicates.path("/hello"), helloHandler());
    }
 
    HandlerFunction helloHandler() {
        return request -> ServerResponse.ok().body(fromObject("Hello World"));
    }
}


작성하고 Application.java를 springboot로 실행해고 http://localhost:8080/hello 를 호출해도 404 NotFound 이다. 왜일까? 
문서 상으로 Running Server (링크)가 있지만 좀 귀찮은 방식이라 좀 더 찾아보니 링크 처럼 spring-boot-starter-webflux는 RouterFunction beans에 대해서 자동으로 디텍팅을 해준다고 한다. 

그래서 class HelloRouter 위에 @Configuration 와, router() 메소드 위에 @Bean을 붙여준 후 다시 http://localhost:8080/hello 를 실행해본다면 ‘Hello World’ 메세지를 확인 할 수 있을 것이다.

@Configuration
public class HelloRouter {
    @Bean
    RouterFunction router() {
        return route(RequestPredicates.path("/hello"),
                     request -> ServerResponse.ok()
                                              .body(fromObject(new Date())));
    }
}
처럼 좀 더 괜찮은 방식으로 써도 괜찮다. 

3. 여러개의 Router ( Controller ) 만들기 


 위의 예제를 보면 1개의 API는 1개의 Router를 만들고 Bean으로 등록해야 하는 것 같다. 하지만 그렇게 불편해지면 다 안쓰겠지
@Bean
RouterFunction router() {
    return route(GET("/hello"), request -> ServerResponse.ok().body(fromObject("Hello-World")))
        .andRoute(GET("/time"), request -> ServerResponse.ok().body(fromObject(new Date())))
        .andRoute(DELETE("/delete"), request -> {
                      System.out.println("delete");
                      return ServerResponse.ok().body(Mono.empty());});
}


처럼 andRoute를 이용해서 체이닝을 할 수도 있고 많은 static method를 이용해서 URI Mapping도 좀 더 짧게 코딩 할 수 있다. 아래 처럼 메소드 래퍼런스를 이용하여 좀 더 깔끔하게 정리할 수도 있다.

public class HelloHandler {
 
  public Mono helloWorld(ServerRequest request){
    return ok().body(fromObject("Hello-World"));
  }
 
  public Mono time(ServerRequest request){
    return ok().body(fromObject(new Date()));
  }
 
  public Mono printlog(ServerRequest request){
    System.out.println("delete");
    return ServerResponse.ok().body(Mono.empty());
  }
}
 
@Configuration
public class HelloRouter {
  HelloHandler helloHandler = new HelloHandler();
  @Bean
  RouterFunction router() {
    return route(GET("/hello"), helloHandler::helloWorld)
      .andRoute(GET("/time"), helloHandler::time)
      .andRoute(DELETE("/delete"), helloHandler::printlog);
    }
  }

4. Request에서 Parameter 받아오기 


고전적인 방식의 PathVariable이랑, Command pattern인 RequestBody의 구현에 대해서 아래 코드를 참고 하면 된다.
public Mono getName(ServerRequest request){
   String name = request.pathVariable("name");
   return ok().body(fromObject(name));
}
 
//curl -H "Content-Type: application/json" -X POST -d '{"id":"id","name":"name"}' http://localhost:8080/param/body
public Mono getBody(ServerRequest request){
   Mono notFound = ServerResponse.notFound().build();
   Mono user = request.bodyToMono(User.class);
   return user.flatMap(u -> ok().body(u).switchIfEmpty(notFound));
}
springboot가 스타트업 될때 로깅메시지를 보면 ReactiveWebServerApplicationContext와 함께 /hello 매핑 정보가 없다는 것도 볼 수 있다.
2017-04-22 21:07:59.915  INFO 1806 --- [           main] .r.c.ReactiveWebServerApplicationContext : Refreshing org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext@1dd92fe2: startup date [Sat Apr 22 21:07:59 KST 2017]; root of context hierarchy
2017-04-22 21:08:00.701  WARN 1806 --- [           main] o.h.v.m.ParameterMessageInterpolator     : HV000184: ParameterMessageInterpolator has been chosen, EL interpolation will not be supported
2017-04-22 21:08:00.909  INFO 1806 --- [           main] o.s.w.r.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.reactive.resource.ResourceWebHandler]
2017-04-22 21:08:00.910  INFO 1806 --- [           main] o.s.w.r.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.reactive.resource.ResourceWebHandler]


gitlink : https://github.com/bistros/webflux-sample


Comments