Spring WebFlux framework
예전에는 Spring-Web-Reactive 였으며, Spring 5 의 메인테마는 JDK 9였는데 이제는 WebFlux 로 바뀌었음.
Reactive System
복수개의 서비스로 이루어진 분산 시스템이 정상 상황 뿐만 아니라 장애 상황에서도 일관된 동작을 보장해주는 시스템이며 Microservice 가 지향하는 방향이다.
용도
- 비동기-논블록킹 리엑티브 개발에 사용
- 효율적으로 동작하는 고성능 웹 어플리케이션 개발
- 서비스 간 호출이 많은 마이크로서비스 아키텍처에 적합
2가지 개발방식 지원
- 기존의 @MVC 방식 (@Controller, @RestController, @RequestMapping)
- 새로운 함수형 모델 (RouterFuction, HandlerFuction.)
새로운 요청 - 응답 모델
- 서블릿 스택과 API 에서 탈피 (서블릿 API 는 리엑티브 함수형 스타일에 적합하지 않음 - HttpServletRequest, HttpServletResponse)
- ServerRequest, ServerResponse
지원 웹 서버 / 컨테이너
- Servlet 3.1 + (Tomcat, Jetty, ... - 서블릿 3.1+ d의 비동기-논블럭킹 요청 처리 가능)
- Netty (비동기-논블럭킹 IO 웹서버)
- Undertow (비동기-논블럭킹 IO 웹서버)
새로운 함수형 모델을 이용한 WebFlux
@FunctionalInterface
public interface RouterFunction<T extends ServerResponse> {
/**
* ServerRequest request : WebFlux 버전의 웹 요청
* Mono<HandlerFuction<T>> : ServerResponse 를 리턴하는 HandlerFuction (웹 응답을 리턴하는 함수)
*/
Mono<HandlerFunction<T>> route(ServerRequest request);
}
HandlerFunction
- 함수형 스타일의 웹 핸들러 (컨트롤러 메소드)
- 웹 요청을 받아 웹 응답을 돌려주는 함수
함수형 WebFlux 가 웹 요청을 처리하는 방식
- 요청 매핑 : RouterFuction - url 같은 정보로 어떤 핸들러가 이 요청을 처리할지 결정함
- 요청 바인딩 -------------------- HandlerFunction
- 핸들러 실행 -------------------- HandlerFunction
- 핸들러 결과 처리(응답 생성) ----- HandlerFunction
WebFlux 함수형 Hello/{name} 작성
- 함수 2개 만들면 됨 (HandlerFuction 먼저 만들고, RouterFunction 에서 path 기준 매핑을 해준다)
// 기존 Spring mvc 구조
@RestConroller
public class MyController {
// RotuerFunction 으로.
@GetMapping("/hello/{name}")
// HandlerFuction 으로.
String hello(@PathVariable String name) {
return "Hello "+ name;
}
}
/**
* WebFlux 함수형
*
* HandlerFuction 은 FunctionalInterface 를 구현하는 것이기 때문에 람다식으로 작성한다.
* HandlerFunction.handle() 의 람다식 - Mono<T> handle(ServerRequest request);
/*
HandlerFuction helloHandler = req -> {
String name = req.pathVariable("name"); // ServerRequest.pathVariable() 로 {name} 추출
Mono<String> result = Mono.just("Hello "+ name); // 로직 적용 후 결과 값을 Mono 에 담는다 (결과를 리턴할 때 꼭 Mono에 담아서 리턴해야 한다)
/**
* 웹 응답을 ServerResponse 로 만든다.
* HTTP 응답에는 응답코드, 헤더, 바디가 필요.
* ServerResponse 의 빌더를 활용
* Mono 에 담긴 ServerResponse 타입으로 리턴
*/
Mono<ServerResponse> res = ServerResponse.ok().body(result, String.class);
return res;
}
/**
* WebFlux 함수형 위의 코드를 간단하게 작성 할 시.
* BodyInserters.fromObject() 의 도움을 받아 Mono 에 담는다
* 로직 결과 값을 바디에 담고 상태코드를 추가해 웹 응답으로 만드는데 content-type 이나 기타 헤더는 스프링에서 알아서 만들어준다.
*/
HandlerFuction helloHandler = req ->
ok().body(fromObject("Hello"+req.pathVariable("name")));
/**
* 이 전체 코드가 RouterFunction.route() 의 람다식 - Mono<HandlerFunction<T>> route(ServerRequest request);
*/
RouterFunction router = req ->
RequestPredicates.path("/hello/{name}").test(req) ? // 웹 요청 정보 중에서 URL 경로 패턴 검사
Mono.just(helloHandler) : Mono.empty();
// 조건에 맞으면 핸들러 함수를 Mono 에 담아서 반환, 조건에 맞지 않으면 빈 Mono 반환
WebFlux 함수형 Hello/{name} 작성 (HandlerFucntion 과 RouterFuction을 한 번에)
- RouterFunctions.route(predicate, handler)
Routerfunctions.route(RequestPredicates.path("/hello/{name}"),
req -> ServerResponse.ok().
body(fromObject("Hello" + req.pathVariable("name"))));
RotuerFunction 의 등록
- RouterFunction 타입의 @Bean 으로 만든다.
@Bean
RouterFunction helloPathVarRouter() {
return route(RequestPredicates.path("/hello/{name}"),
req -> ok().body(fromObject("Hello" + req.pathVariable("name"))));
}
위의 handler 로직이 복잡해졌을 때 분리시켜야 한다.
- handler 코드만 따로 선언한다.
- 메소드를 정의하고 메소드 참조로 가져온다.
// 다음과 같이 분리시키는 것이 좋다.
HandlerFunction handler = req -> {
String res = myService.hello(req.pathVariable("name"));
return ok().body(fromObject(res));
}
return route(path("/hello/{name}"), handler);
/**
* 람다식은 메소드 타입(파라미터 타입, 리턴 타입, 예외 던지는 것)이 일치하면 일반 메소드와 호환해서 사용할 수 있다. (메소드 레퍼런스)
* 다음의 코드는 위에서 분리시킨 handler() 메소드가 같은 의미인 코드이다.
* 아래와 같이 클래스 안에 메소드로 작성한 뒤, 메소드 타입이 일치한다면 아래의 코드와 같이 router function 을 정의할 때 사용할 수 있다.
* 이 장점은 하나의 컴포넌트 안에 메소드 형태로 여러 개를 만들어놓고 사용하면 편하다.
*/
@Component
public class HelloHandler {
@Autowired MyService myService;
Mono<ServerResponse> hello(ServerRequest req) {
String res = myService.hello(req.pathVariable("name"));
return ok().body(fromObject(res));
}
....
}
@Bean
RouterFunction helloRouter(@Autowired HelloHandler helloHandler) {
return route(path("/hello/{name}"), helloHandler::hello);
}
반응형