mm Home

Spring 5 features 본문

개발/Spring

Spring 5 features

jess_m 2017. 11. 10. 17:17

 

Spring 5 features

  • 리액티브 프로그래밍 모델
    • 어노테이션 기반 MVC + 함수형 MVC 같이 지원. 기존의 어노테이션 기반을 사용할 수도 있지만, 함수형으로 MVC 설정을 할 수도 있다.
    • 간단하게 설정을 어떻게 하는지 방법만 봐보자. 다음 글에서 web-flux에 대하여 자세히 설명하겠다.
    • 라우터를 통해 request를 적절한 핸들러에 라우팅한다.
    • 핸들러가 컨트롤러가 된다.
    • 아래와 RouterFunction을 설정해서 스프링의 Bean으로 생성한다면 DispatcherHandler 에서 관련 빈들을 다 불러들여서 해당 Handler로 라우팅한다.
    • public class BookHandler {
        
          public Mono<ServerResponse> listBooks(ServerRequest request) {
              return ServerResponse.ok()
                  .contentType(APPLICATION_JSON)
                  .body(repository.allPeople(), Book.class);
          }
            
          public Mono<ServerResponse> getBook(ServerRequest request) {
              return repository.getBook(request.pathVariable("id"))
                  .then(book -> ServerResponse.ok()
                  .contentType(APPLICATION_JSON)
                  .body(fromObject(book)))
                  .otherwiseIfEmpty(ServerResponse.notFound().build());
          }
          // Plumbing code omitted for brevity
      }
    • BookHandler handler = new BookHandler();
        
      RouterFunction<ServerResponse> personRoute =
          route(
              GET("/books/{id}")
              .and(accept(APPLICATION_JSON)), handler::getBook)
              .andRoute(
          GET("/books")
              .and(accept(APPLICATION_JSON)), handler::listBooks);



  • WebClient 클래스 추가
    • RestTemplate을 대체할 비동기 논블록킹 HTTP 클라이언트
    • Mono<Book> book = WebClient.create("http://localhost:8080")
            .get()
            .url("/books/{id}", 1234)
            .accept(APPLICATION_JSON)
            .exchange(request)
            .then(response -> response.bodyToMono(Book.class));



    • 람다를 통한 스프링 빈 등록 가능

      • 람다를 통해 단일 메서드를 가지는 Supplier 타입의 빈 등록이 가능하다.

      • GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(Book.class, () -> new
        Book(context.getBean(Author.class))
        );





    • spring-webflux 통합 테스트 지원
      • WebTestClient 가 서버를 띄우지 않고 WebFlux 서버 구조에 바로 바인딩할 수 있다.

      • RouterFunction bookRouter = RouterFunctions.route(
        RequestPredicates.GET("/books"),
        request -> ServerResponse.ok().build()
        );

        WebTestClient
        .bindToRouterFunction(bookRouter)
        .build().get().uri("/books")
        .exchange()
        .expectStatus().isOk()
        .expectBody().isEmpty();





  • Junit 5
    • 새로운 어노테이션
      • @TestFactory – denotes a method that is a test factory for dynamic tests
      • @DisplayName – defines custom display name for a test class or a test method
      • @Nested – denotes that the annotated class is a nested, non-static test class
      • @Tag – declares tags for filtering tests
      • @ExtendWith – it is used to register custom extensions
      • @BeforeEach – denotes that the annotated method will be executed before each test method (previously @Before)
      • @AfterEach – denotes that the annotated method will be executed after each test method (previously @After)
      • @BeforeAll – denotes that the annotated method will be executed before all test methods in the current class (previously @BeforeClass)
      • @AfterAll – denotes that the annotated method will be executed after all test methods in the current class  (previously @AfterClass)
      • @Disable – it is used to disable a test class or method (previously @Ignore)
    • 예외 처리 테스트 가능
      • @Test
        void shouldThrowException() {
            Throwable exception = assertThrows(UnsupportedOperationException.class, () -> {
              throw new UnsupportedOperationException("Not supported");
            });
            assertEquals(exception.getMessage(), "Not supported");
        }
         
        @Test
        void assertThrowsException() {
            String str = null;
            assertThrows(IllegalArgumentException.class, () -> {
              Integer.valueOf(str);
            });
        }
    • 동적 테스트
      • 테스트 런타임시 테스트에 대한 정의를 하고 실행할 수 있게끔 DynamicTest를 지원한다.
      • @TestFactory
        public Stream<DynamicTest> translateDynamicTestsFromStream() {
            return in.stream()
              .map(word ->
                  DynamicTest.dynamicTest("Test translate " + word, () -> {
                    int id = in.indexOf(word);
                    assertEquals(out.get(id), translate(word));
                  })
            );
        }


 




각 feature 들을 자세히 살펴보질 않아서... 대략적으로 적어봤다. 관심이갔던 web-flux 부분은 좀 더 살펴보고 있는데, 다음에 더 자세히 적어보겠다.

간략하게 정리를 하면.




 

 

Spring 5 - web-flux 예제

reactive web → web flux란 이름으로 변경됨. 

Reactive Programming을 지원하기 위한 스프링의 새로운 프로젝트.

 


참고로 spring boot 2.0(아직 M6) 부터 default 컨테이너를 netty 이용함.

  • netty는 비동기 블록킹 개발에 가장 많이 사용됨.
  • tomcat 을 기본으로 하지 않는다. Servlet 기술에 의존하지 않도록 web flux가 개발된 것.



Spring 5에 들어서 MVC 를 위한 web 프로젝트로 2개 중 1개를 선택해야 함.

  • web-mvc : 기존 JAVA EE의 Servlet 기반. MVC의 주체 DispatcherServlet
  • web-flux : reactive web. DispatcherServlet → DispatcherHandler 로 변경.



함수형의 MVC설정이 가능해진 것도 변화 중 하나지만 다른 큰 변화라면 Reactive Programming 을 지원하는 것이다.

Mono & Flux 클래스 추가

  • Publisher 클래스
  • Mono : 하나의 요소를 다룰 때
  • Flux : 하나 이상의 요소를 다룰 때
  • value를 지니고 있는 컨테이너 클래스.
  • publisher인데 Mono와 Flux로 객체 생성(데이터 stream 생성)은 어떻게 해야하나?
    • Optional과 동일하게 JPA로 구성시 자동으로 Flux나 Mono로 감쌀 수 있다. (Flux로 감싸는 경우 Flux<List<Object>> 가 아니라 Flux<Object> 이다.)
    • 아니면 just라는 메소드를 통해 객체를 생성할 수 있다.



Mono & Flux 는 모나드 클래스이면서 Publisher 이다.

값이 컨테이너에 담겨있기 때문에 값을 꺼내는 함수를 써야한다. 그런데 Publisher이기 때문에 Subscriber에 의해 구독되기 전에 값을 꺼내지 못한다.

Spring web-flux 에서 Mono나 Flux를 리턴해주면 저절로 subscribe 해준다. 

 

@GetMapping("subscribe")
public Flux<User> subscribe() {
 
    log.info("start");
 
    Flux<User> users = repository.users();
 
    //monad 이면서 lazy함
    //모나드 클래스이기 때문에 Map이라는 함수를 통해 값을 꺼냄. 아래는 flat 하게 펴서 User를 가져옴. map -> Flux<User>, flatMap -> User)
    //Flux 는 Publisher 이기 때문에 평가를 Subscriber에 의해 받는다.
    Flux<User> userFlux = users
            .flatMap(user -> getUser(user.getName()))
            .flatMap(user -> doSomethingBatchProcess(user)).log();
 
    log.info("end");
 
    return userFlux;
}


비동기 개발이기 때문에 start와 end 로그가 찍히고 실제 로직 동작이 이루어지는 것을 확인할 수 있다. 

subscribe() 메소드를 호출하기 전까지 동작을 하지 않는다고 보면 될것이다. 

userFlux라는 객체가 구독될때까지 지연평가되는 것이다. 



서비스 로직에서 직접 값을 꺼내야될 때는 .subscribe() 를 호출하면 된다. 빈 파라미터면 리턴값은 disposable.

Flux.just("", "", "")
        .subscribe( i -> System.out.println(i), //onNext
                error -> System.err.println("Error " + error), //onError
                () -> System.out.println("completed"),  //onCompleted
                s -> s.request(10));    //request

 

 

 


궁금해서 코드를 쭉 따라가본, 간략한 web-flux 의 Mono, Flux 처리 프로세스

  • 기본적인 Dispatch 처리 과정은 spring mvc와 비슷한것 같다.
  • DispatcherHandler 이 초기화될때 mvc의 전략 빈들을 주입. (아래의 3개 인터페이스는 스프링 4버전대의 인터페이스와 다르다)
    • 디폴트 빈들은 @EnableWebFlux 을 사용하면서 객체 생성이 될 것이다. 
    • 디폴트 빈에 대한 설정은 WebFluxConfigurationSupport 클래스에서 확인할 수 있다.
    • DispatcherHandler가 초기화할때 스프링 컨텍스트 내에 있는 빈들에서 해당 인터페이스 빈들을 불러들여 주입시킨다.
    • HandlerMapping : Request와 핸들러(메소드) 객체 매핑을 정의하는 인터페이스
    • HandlerAdapter : HandlerMapping이 핸들러(메소드)를 찾아주면, HandlerAdpter가 해당 컨트롤러의 핸들러에 전달하기 위한 Adapter 인터페이스
    • ResultHandler : HandlerAdapter 에 의해 반환된 값을 처리하기 위한 인터페이스
  • netty 동작 기반은 더 살펴볼 예정. 
    • NioEventLoop
  • 어쨋든 request가 오면 DispatcherHandler의 handle(ServerWebExchange exchange) 메소드를 호출한다.       (ServerWebExchange는 HTTP 요청/응답에 대한 인터페이스)
  • 여기서 체이닝걸려있는 함수를 거치며 결과를 반환하게 되는데...
    • 적절한 핸들러를 찾는다.
    • 핸들러를 실행한다.
      • 주입된 핸들러 아답터들을 순회하며 적절한 아답터를 찾는다. (supports)
      • 아답터를 통해서 핸들러 handle
    • 결과를 반환한다.
      • 주입된 resultHandler들 중 적절한 핸들러를 찾음.
      • 핸들러가 handleResult 한다. (결과를 뽑음)
      • @ResponseBody의 핸들러 중 Mono로 반환되는 경우를 예로 보면, ResponseBodyResultHandler 라는 클래스가 handleResult 메소드를 호출하여 결과값을 생성하여 write 한다.

 

 

 





시간이 날때 더 자세히 찾아서 정리해보아야겠다.







참고 :

https://www.ibm.com/developerworks/library/j-whats-new-in-spring-framework-5-theedom/

http://www.baeldung.com/junit-5





'개발 > Spring' 카테고리의 다른 글

Spring MVC - DispatcherServlet 동작 원리  (2) 2017.09.26
ComponentScan 동작 원리  (1) 2017.09.14
Comments