From 79c4982792584bdb2dd33a021b5b1099402dd402 Mon Sep 17 00:00:00 2001 From: asahi Date: Fri, 14 Mar 2025 01:23:59 +0800 Subject: [PATCH] =?UTF-8?q?doc:=20=E9=98=85=E8=AF=BBwebflux=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spring/webflux/spring webflux.md | 156 +++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/spring/webflux/spring webflux.md b/spring/webflux/spring webflux.md index ad2c776..dd95b1d 100644 --- a/spring/webflux/spring webflux.md +++ b/spring/webflux/spring webflux.md @@ -41,6 +41,13 @@ - [Server Request](#server-request) - [ServerResponse](#serverresponse) - [Handler Class](#handler-class) + - [Validation](#validation) + - [RouterFunction](#routerfunction) + - [Predicates](#predicates) + - [Routes](#routes) + - [Nested Routes](#nested-routes) + - [Resource Redirect](#resource-redirect) + - [Filter Handler Functions](#filter-handler-functions) # Spring Webflux @@ -625,6 +632,155 @@ HandlerFunction helloWorld = request -> ServerResponse.ok().bodyValue("Hello World"); ``` +### Validation +如果想要对请求体进行校验,可以使用如下逻辑: +```java +public class PersonHandler { + + private final Validator validator = new PersonValidator(); + + // ... + + public Mono createPerson(ServerRequest request) { + Mono person = request.bodyToMono(Person.class).doOnNext(this::validate); + return ok().build(repository.savePerson(person)); + } + + private void validate(Person person) { + Errors errors = new BeanPropertyBindingResult(person, "person"); + validator.validate(person, errors); + if (errors.hasErrors()) { + throw new ServerWebInputException(errors.toString()); + } + } +} +``` +### RouterFunction +RouterFunction用于将请求路由到对应的HandlerFunction,通常情况下都通过`RouterFunctions`工具类来创建router。 + +`RouterFunctions.route()`方法提供了许多快捷创建router的方法,例如`GET(String, HandlerFunction)`等。 + +除了基于HTTP method进行映射外,router builder还提供了引入额外Predicates来进行请求路由的机制,对于每个基于http method进行路由的方法,都存在一个重载方法,用于接收predicates。 + +#### Predicates +使用者可以编写自己的RequestPredicate,但是`RequestPredicates`工具提供了常用的实现,可以基于request path、http method、content-type等进行判断。 + +在如下示例中,展示了通过`Accept` header进行判断的示例: +```java +RouterFunction route = RouterFunctions.route() + .GET("/hello-world", accept(MediaType.TEXT_PLAIN), + request -> ServerResponse.ok().bodyValue("Hello World")).build(); +``` + +如果想要对多个predicates进行逻辑运算,可以使用如下方法: +- `RequestPredicate.and(RequestPredicate)`:两者都满足 +- `RequestPredicate.or(RequestPredicate)`:两者任一满足 + +predicates中许多都是组合的,例如`equestPredicates.GET(String)`是由`RequestPredicates.method(HttpMethod)`和`RequestPredicates.path(String)`进行组合的。 + +在上述示例中,其实也使用到了两个predicates,builder内部使用了`RequestPredicates.GET`,调用方则指定了`accept`。 + +#### Routes +Router function将会按顺序被调用,如果前一个route没有匹配,将会调用下一个route。 + +在使用router function builder时,所有定义的routes都被整合在一个`RouterFunction`中,并且通过`build`方法进行返回。 + +除了使用router builder之外,还可以将多个router function整合在一起: +- 调用`RouterFunctions.route()`中的`add(RouterFunction)`方法 +- `RouterFunction.and(RouterFunction)`方法 + - and方法代表前面的方法没有匹配时,后调用后面的RouterFunction +- `RouterFunction.andRoute(RequestPredicate, HandlerFunction) `方法 + +使用示例如下所示: +```java +PersonRepository repository = ... +PersonHandler handler = new PersonHandler(repository); + +RouterFunction otherRoute = ... + +RouterFunction route = route() + .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) + .GET("/person", accept(APPLICATION_JSON), handler::listPeople) + .POST("/person", handler::createPerson) + .add(otherRoute) + .build(); +``` + +#### Nested Routes +通常情况下,很有可能许多路由都包含相同的predicate,例如共享路径。 + +如下示例展示了3个routes共用`/preson`路径的场景: +```java +RouterFunction route = route() + .path("/person", builder -> builder + .GET("/{id}", accept(APPLICATION_JSON), handler::getPerson) + .GET(accept(APPLICATION_JSON), handler::listPeople) + .POST(handler::createPerson)) + .build(); +``` +在上述示例中,route1和route2都接收json格式参数,故而还能继续嵌套,示例如下: +```java +RouterFunction route = route() + .path("/person", b1 -> b1 + .nest(accept(APPLICATION_JSON), b2 -> b2 + .GET("/{id}", handler::getPerson) + .GET(handler::listPeople)) + .POST(handler::createPerson)) + .build(); +``` + +#### Resource Redirect +RounterFunction的Builder支持对resource进行重定向,示例如下: +```java +Resource location = new FileUrlResource("public-resources/"); +RouterFunction resources = RouterFunctions.resources("/resources/**", location); +``` +### Filter Handler Functions +可以针对routing function builder添加filter,builder支持如下方法: +- before +- after +- filter + +对builder添加的filter,会应用到buidler中所有的routes,示例如下所示: +```java +RouterFunction route = route() + .path("/person", b1 -> b1 + .nest(accept(APPLICATION_JSON), b2 -> b2 + .GET("/{id}", handler::getPerson) + .GET(handler::listPeople) + .before(request -> ServerRequest.from(request) + .header("X-RequestHeader", "Value") + .build())) + .POST(handler::createPerson)) + .after((request, response) -> logResponse(response)) + .build(); +``` +上述示例中,filter的范围如下: +- `before`:before只会应用到GET请求 +- `after`:after会应用到所有请求,包括nested routes + +对于`builder.filter`方法,其接收`HandlerFilterFunction`类型的参数,使用示例如下: +```java +SecurityManager securityManager = ... + +RouterFunction route = route() + .path("/person", b1 -> b1 + .nest(accept(APPLICATION_JSON), b2 -> b2 + .GET("/{id}", handler::getPerson) + .GET(handler::listPeople)) + .POST(handler::createPerson)) + .filter((request, next) -> { + if (securityManager.allowAccessTo(request.path())) { + return next.handle(request); + } + else { + return ServerResponse.status(UNAUTHORIZED).build(); + } + }) + .build(); +``` +除了可以向builder添加filter外,还可以向已经存在的router function中添加filter,通过`RouterFunction.filter(HandlerFilterFunction)`方法。 +