예
시작할 샘플 프로그램을 선택합니다. 다음은 Person 객체를 노출하기 위한 반응형 리소스 라이브러리입니다. 이 반응형 리소스 라이브러리는 Flux
public interface PersonRepository { Mono<Person> getPerson(int id); Flux<Person> allPeople(); Mono<Void> savePerson(Mono<Person> person);}
사용 방법을 소개합니다. 리소스 라이브러리를 노출하는 새로운 기능적 웹 프레임워크:
RouterFunction<?> route = route(GET("/person/{id}"), request -> { Mono<Person> person = Mono.justOrEmpty(request.pathVariable("id")) .map(Integer::valueOf) .then(repository::getPerson); return Response.ok().body(fromPublisher(person, Person.class)); }) .and(route(GET("/person"), request -> { Flux<Person> people = repository.allPeople(); return Response.ok().body(fromPublisher(people, Person.class)); })) .and(route(POST("/person"), request -> { Mono<Person> person = request.body(toMono(Person.class)); return Response.ok().build(repository.savePerson(person)); }));
여기서 실행 방법을 소개하고 Reactor Netty의 예를 소개합니다.
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route); ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler); HttpServer server = HttpServer.create("localhost", 8080); server.startAndAwait(adapter);
마지막으로 할 일 체험 요청을 해보세요:
$ curl ' {"name":"John Doe","age":42}
위 소개에는 많은 내용이 포함되어 있으니 좀 더 자세히 살펴보겠습니다!
핵심 컴포넌트
HandlerFunction, RouterFunction, FilterFunction 등 핵심 컴포넌트를 차례로 소개하면서 전체 프레임워크를 소개하겠습니다. 이 기사의 다른 유형과 함께 이 세 가지 인터페이스는 org.springframework.web.reactive.function 패키지에서 찾을 수 있습니다.
Handling Function
새 프레임워크의 시작점은 HandlerFunction
다음은 상태가 200이고 본문이 문자열로 포함된 메시지를 반환하는 "Hello World"의 처리 방법입니다.
HandlerFunction<String> helloWorld = request -> Response.ok().body(fromObject("Hello World"));
위와 같이 Reactor에 구축된 처리 방법은 Flux, Mono 또는 기타 해당 스트림(Reactive Streams)의 게시자를 반환 유형으로 받아들일 수 있습니다.
응답을 매개변수가 아닌 반환 값으로 사용하기 때문에 처리 방법 자체에는 부작용이 없다는 점에 유의해야 합니다(본질적으로 BiConsumer인 Servlet.service(ServletRequest, ServletResponse)와 비교)
라우팅 기능
인바운드 요청은 RouterFunction
다음은 인라인 처리 방식을 포함한 라우팅 방식의 예시이다. 이것은 약간 중복되어 보이지만 나중에 간소화할 것이므로 걱정하지 마십시오.
RouterFunction<String> helloWorldRoute = request -> { if (request.path().equals("/hello-world")) { return Optional.of(r -> Response.ok().body(fromObject("Hello World"))); } else { return Optional.empty(); } };
일반적으로 완전한 라우팅 방법을 작성할 필요는 없지만 RouterFunctions.route()를 정적으로 도입하면 요청 조건자(RequestPredicate)(예: Predicate
RouterFunction<String> helloWorldRoute = RouterFunctions.route(request -> request.path().equals("/hello-world"), request -> Response.ok().body(fromObject("Hello World")));
RequestPredicates.*를 정적으로 도입한 후 일치하는 경로, HTTP 메소드, 컨텐츠 유형 등 일반적으로 사용되는 판단 표현식을 사용할 수 있습니다. 등. 이렇게 하면 위의 예가 더욱 간소화됩니다.
RouterFunction<String> helloWorldRoute = RouterFunctions.route(RequestPredicates.path("/hello-world"), request -> Response.ok().body(fromObject("Hello World")));
组合功能
两个路由方法可以被组合成一个新的路由方法,可以路由任意处理方法:如果第一个路由不匹配则执行第二个。可以通过调用RouterFunction.and()方法实现,如下:
RouterFunction<?> route = route(path("/hello-world"), request -> Response.ok().body(fromObject("Hello World"))) .and(route(path("/the-answer"), request -> Response.ok().body(fromObject("42"))));
上面的例子如果路径匹配/hello-world会返回“Hello World”,如果匹配/the-answer则返回“42”。如果都不匹配则返回一个空的Optional对象。注意,组合的路由是按顺序执行的,所以应该将更通用的方法放到更明确的方法的前面。
请求判断式也是可以组合的,通过调研and或者or方法。正如预期的一样:and表示给定的两个判断式同时满足则组合判断式满足,or则表示任意判断式满足。如下:
RouterFunction<?> route = route(method(HttpMethod.GET).and(path("/hello-world")), request -> Response.ok().body(fromObject("Hello World"))) .and(route(method(HttpMethod.GET).and(path("/the-answer")), request -> Response.ok().body(fromObject("42"))));
实际上,RequestPredicates中的大部分判断式都是组合的!比如RequestPredicates.GET(String)是RequestPredicates.method(HttpMethod)和RequestPredicates.path(String)的组合。所以上面的例子可以重写为:
RouterFunction<?> route = route(GET("/hello-world"), request -> Response.ok().body(fromObject("Hello World"))) .and(route(GET("/the-answer"), request -> Response.ok().body(fromObject(42))));
方法引用
此外,目前为止我们的处理方法都是行内的lambda表达式。尽管这样很适合于实例和简短的例子,但是当结合请求路由和请求处理两个关注点时,可能就有变“混乱”的趋势了。所以我们将尝试将他们简化。首先,创建一个包含处理逻辑的类:
class DemoHandler { public Response<String> helloWorld(Request request) { return Response.ok().body(fromObject("Hello World")); } public Response<String> theAnswer(Request request) { return Response.ok().body(fromObject("42")); }}
注意,这两个方法的签名都是和处理方法兼容的。这样就可以方法引用了:
DemoHandler handler = new DemoHandler(); // or obtain via DI RouterFunction<?> route = route(GET("/hello-world"), handler::helloWorld) .and(route(GET("/the-answer"), handler::theAnswer));
过滤功能
由路由器函数进行映射的路由可以通过调用 RouterFunction.filter(FilterFunction
RouterFunction<?> route = route(GET("/hello-world"), handler::helloWorld) .and(route(GET("/the-answer"), handler::theAnswer)) .filter((request, next) -> { System.out.println("Before handler invocation: " + request.path()); Response<?> response = next.handle(request); Object body = response.body(); System.out.println("After handler invocation: " + body); return response; });
注意这里对下一个处理器的调用时可选的。这个在安全或者缓存的场景中是很有用的 (例如只在用户拥有足够的权限时才调用 next)。
因为 route 是一个没有被绑定的路由器函数,我们就得知道接下来的处理会返回什么类型的响应消息。这就是为什么我们在过滤器中要以一个 Response> 结束, 那样它就会可能有一个 String 类型的响应消息体。我们可以通过使用 RouterFunction.andSame() 而不是 and() 来完成这件事情。这个组合方法要求路由器函数参数是同一个类型。例如,我们可以让所有的响应消息变成小写的文本形式:
RouterFunction<String> route = route(GET("/hello-world"), handler::helloWorld) .andSame(route(GET("/the-answer"), handler::theAnswer)) .filter((request, next) -> { Response<String> response = next.handle(request); String newBody = response.body().toUpperCase(); return Response.from(response).body(fromObject(newBody)); });
使用注解的话,类似的功能可以使用 @ControllerAdvice 或者是一个 ServletFilter 来实现。
运行一个服务端
所有这些都很不错,不过仍然有一块欠缺:我们如何实际地将这些函数在一个 HTTP 服务器中跑起来呢? 答案毋庸置疑,那就是通过调用另外的一个函数。 你可以通过使用 RouterFunctions.toHttpHandler() 来将一个路由器函数转换成 HttpHandler。HttpHandler 是 Spring 5.0 M1 中引入的一个响应式抽象: 它能让你运行许多的响应式运行时: Reactor Netty, RxNetty, Servlet 3.1+, 以及 Undertow。在本示例中,我们已经展示了在 Reactor Netty 中运行一个路由会是什么样子的。对于 Tomcat 来说则是像下面这个样子:
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route); HttpServlet servlet = new ServletHttpHandlerAdapter(httpHandler); Tomcat server = new Tomcat(); Context rootContext = server.addContext("", System.getProperty("java.io.tmpdir")); Tomcat.addServlet(rootContext, "servlet", servlet); rootContext.addServletMapping("/", "servlet"); tomcatServer.start();
需要注意的意见事情就是上面的东西并不依赖于一个 Spring 应用程序上下文。就跟 JdbcTemplate 以及其它 Spring 的工具类那样, 要不要使用应用程序上下文是可以选的: 你可以将你的处理器和路由器函数在一个上下文中进行绑定,但并不是必须的。
还要注意的就是你也可以将一个路由器函数转换到一个 HandlerMapping中去,那样就它可以在一个 DispatcherHandler (可能是跟响应式的 @Controllers 并行)中运行了。
结论
이것으로 Spring의 새로운 기능적 웹 프레임워크에 대한 소개를 마칩니다. 간단히 요약하자면
처리 기능은 응답을 하여 요청을 처리합니다.
라우터 기능은 처리 기능과 연결되어 다른 라우터 기능과 공유할 수 있습니다.
라우터 기능은 필터 기능으로 필터링할 수 있으며,
라우터 기능은 반응형 네트워크 운영 메커니즘에서 실행할 수 있습니다.