Spring WebFlux包含WebFlux.fn,这是一个轻量级的函数编程模型,其中的函数用于路由和处理请求,而契约则是为不变性而设计的。它是基于注解的编程模型的替代方案,但可以在相同的Reactive Core基础上运行。
在WebFlux.fn中,HTTP请求由HandlerFunction处理:该函数接受ServerRequest并返回延迟的ServerResponse(即Mono <ServerResponse>)。作为请求对象的请求都具有不可变的协定,这些协定为JDK 8提供了对HTTP请求和响应的友好访问。 HandlerFunction等效于基于注解的编程模型中@RequestMapping方法的主体。
传入的请求通过RouterFunction路由到处理程序函数:该函数接受ServerRequest并返回延迟的HandlerFunction(即Mono <HandlerFunction>)。当路由器功能匹配时,返回处理程序功能。否则为空Mono。 RouterFunction等效于@RequestMapping批注,但主要区别在于路由器功能不仅提供数据,还提供行为。
RouterFunctions.route()提供了一个路由器构建器,可简化路由器的创建过程,如以下示例所示:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson)
.build();
public class PersonHandler {
// ...
public Mono<ServerResponse> listPeople(ServerRequest request) {
// ...
}
public Mono<ServerResponse> createPerson(ServerRequest request) {
// ...
}
public Mono<ServerResponse> getPerson(ServerRequest request) {
// ...
}
}
运行RouterFunction的一种方法是将其转换为HttpHandler并通过内置服务器适配器之一进行安装:
大多数应用程序都可以通过WebFlux Java配置运行,请参阅运行服务器。
ServerRequest和ServerResponse是不可变的接口,它们提供JDK 8友好的HTTP请求和响应访问。 请求和响应都为反应流提供了对体流的反压力。 请求主体用Reactor Flux或Mono表示。 响应主体由任何Reactive Streams Publisher组成,包括Flux和Mono。 有关更多信息,请参见反应式库。
ServerRequest
ServerRequest提供对HTTP方法,URI,标头和查询参数的访问,而通过body方法提供对主体的访问。
下面的示例将请求正文提取到Mono <String>:
Mono<String> string = request.bodyToMono(String.class);
下面的示例将主体提取到Flux <Person>,其中Person对象从某种序列化形式(例如JSON或XML)解码:
Flux<Person> people = request.bodyToFlux(Person.class);
前面的示例是使用更通用的ServerRequest.body(BodyExtractor)的快捷方式,该请求接受BodyExtractor功能策略接口。 实用程序类BodyExtractors提供对许多实例的访问。 例如,前面的示例也可以编写如下:
Mono<String> string = request.body(BodyExtractors.toMono(String.class));
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));
下面的示例显示如何访问表单数据:
Mono<MultiValueMap<String, String> map = request.body(BodyExtractors.toFormData());
以下示例显示了如何以Map形式访问多部分数据:
Mono<MultiValueMap<String, Part> map = request.body(BodyExtractors.toMultipartData());
下面的示例演示如何以流方式一次访问多个部分:
Flux<Part> parts = request.body(BodyExtractors.toParts());
ServerResponse
ServerResponse提供对HTTP响应的访问,并且由于它是不可变的,因此可以使用一种build方法来创建它。您可以使用构建器来设置响应状态,添加响应标题或提供正文。以下示例使用JSON内容创建200(确定)响应:
Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
下面的示例演示如何构建Location不带标头的201(已创建)响应:
URI location = ...
ServerResponse.created(location).build();
Handler Classes
我们可以将处理程序函数编写为lambda,如以下示例所示:
HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().body(fromObject("Hello World"));
这很方便,但是在应用程序中我们需要多个功能,并且多个内联lambda可能会变得凌乱。 因此,将相关的处理程序功能分组到一个处理程序类中很有用,该类的作用与基于注解的应用程序中的@Controller相似。 例如,以下类公开了反应型Person存储库:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.ServerResponse.ok;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
public class PersonHandler {
private final PersonRepository repository;
public PersonHandler(PersonRepository repository) {
this.repository = repository;
}
public Mono<ServerResponse> listPeople(ServerRequest request) {
Flux<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people, Person.class);
}
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class);
return ok().build(repository.savePerson(person));
}
public Mono<ServerResponse> getPerson(ServerRequest request) {
int personId = Integer.valueOf(request.pathVariable("id"));
return repository.getPerson(personId)
.flatMap(person -> ok().contentType(APPLICATION_JSON).body(fromObject(person)))
.switchIfEmpty(ServerResponse.notFound().build());
}
}
Validation
功能端点可以使用Spring的验证工具将验证应用于请求正文。 例如,给定Person的自定义Spring Validator实现:
public class PersonHandler {
private final Validator validator = new PersonValidator();
// ...
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate);
return ok().build(repository.savePerson(person));
}
private void validate(Person person) {
Errors errors = new BeanPropertyBindingResult(body, "person");
validator.validate(body, errors);
if (errors.hasErrors) {
throw new ServerWebInputException(errors.toString());
}
}
处理程序还可以通过创建和注入基于LocalValidatorFactoryBean的全局Validator实例来使用标准的bean验证API(JSR-303)。
路由器功能用于将请求路由到相应的HandlerFunction。通常,您不是自己编写路由器功能,而是使用RouterFunctions实用工具类上的方法来创建一个。 RouterFunctions.route()(无参数)为您提供了流畅的生成器,用于创建路由器功能,而RouterFunctions.route(RequestPredicate,HandlerFunction)提供了直接创建路由器的方法。
通常,建议使用route()构建器,因为它为典型的映射方案提供了便捷的快捷方式,而无需发现静态导入。例如,路由器功能构建器提供了GET(String,HandlerFunction)方法来为GET请求创建映射。和POST(String,HandlerFunction)进行POST。
除了基于HTTP方法的映射外,路由构建器还提供了一种在映射到请求时引入其他谓词的方法。对于每个HTTP方法,都有一个重载的变体,它以RequestPredicate作为参数,尽管可以表达其他约束。
Predicates
您可以编写自己的RequestPredicate,但是RequestPredicates实用程序类根据请求路径,HTTP方法,内容类型等提供常用的实现。以下示例使用请求谓词基于Accept头创建约束:
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> Response.ok().body(fromObject("Hello World")));
您可以使用以下命令组合多个请求谓词:
RequestPredicates中的许多谓词都是组成的。例如,RequestPredicates.GET(String)由RequestPredicates.method(HttpMethod)和RequestPredicates.path(String)组成。上面显示的示例还使用了两个请求谓词,因为构建器在内部使用RequestPredicates.GET并将其与接受谓词组合在一起。
Routes
路由器功能按顺序评估:如果第一个路由不匹配,则评估第二个路由,依此类推。因此,在通用路由之前声明更具体的路由是有意义的。请注意,此行为不同于基于注解的编程模型,在该模型中,将自动选择“最特定”的控制器方法。
使用路由器功能生成器时,所有定义的路由都组成一个RouterFunction,从build()返回。还有其他方法可以将多个路由器功能组合在一起:
以下示例显示了四种路线的组成:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> otherRoute = ...
RouterFunction<ServerResponse> 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
一组路由器功能通常具有共享谓词,例如共享路径。 在上面的示例中,共享谓词将是与/ person匹配的路径谓词,由三个路由使用。 使用注解时,您可以通过使用映射到/ person的类型级别@RequestMapping注解来删除此重复项。 在WebFlux.fn中,可以通过路由器功能构建器上的path方法共享路径谓词。 例如,以上示例的最后几行可以通过使用嵌套路由以以下方式进行改进:
RouterFunction<ServerResponse> route = route()
.path("/person", builder -> builder
.GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson))
.build();
请注意,路径的第二个参数是使用路由器构建器的使用者。
尽管基于路径的嵌套是最常见的,但是您可以通过使用构建器上的nest方法来嵌套在任何种类的谓词上。 上面的内容仍然包含一些以共享的Accept-header谓词形式出现的重复。 通过将nest方法与accept一起使用,我们可以进一步改进:
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET("", handler::listPeople))
.POST("/person", handler::createPerson))
.build();
如何在HTTP服务器中运行路由器功能?一个简单的选项是使用以下方法之一将路由器功能转换为HttpHandler:
然后,可以通过遵循HttpHandler来获取特定于服务器的指令,将返回的HttpHandler与许多服务器适配器一起使用。
Spring Boot还使用了一个更典型的选项,即通过WebFlux Config使用基于DispatcherHandler的设置来运行,该配置使用Spring配置声明处理请求所需的组件。 WebFlux Java配置声明以下基础结构组件以支持功能端点:
前面的组件使功能端点适合于DispatcherHandler请求处理生命周期,并且(如果有)声明的控制器也可以(可能)与带注解的控制器并排运行。这也是Spring Boot WebFlux启动器启用功能端点的方式。
以下示例显示了WebFlux Java配置(有关如何运行它,请参见DispatcherHandler):
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Bean
public RouterFunction<?> routerFunctionA() {
// ...
}
@Bean
public RouterFunction<?> routerFunctionB() {
// ...
}
// ...
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
// configure message conversion...
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// configure CORS...
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// configure view resolution for HTML rendering...
}
}
您可以使用路由功能构建器上的before,after或filter方法来过滤处理程序函数。 使用注解,可以通过使用@ ControllerAdvice,ServletFilter或同时使用两者来实现类似的功能。 该过滤器将应用于构建器构建的所有路由。 这意味着在嵌套路由中定义的过滤器不适用于“顶级”路由。 例如,考虑以下示例:
RouterFunction<ServerResponse> 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("/person", handler::createPerson))
.after((request, response) -> logResponse(response))
.build();
路由器构建器上的filter方法采用HandlerFilterFunction:该函数采用ServerRequest和HandlerFunction并返回ServerResponse。 handler函数参数代表链中的下一个元素。 这通常是路由到的处理程序,但是如果应用了多个,它也可以是另一个过滤器。
现在,我们可以在路由中添加一个简单的安全过滤器,假设我们拥有一个可以确定是否允许特定路径的SecurityManager。 以下示例显示了如何执行此操作:
SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET("", handler::listPeople))
.POST("/person", handler::createPerson))
.filter((request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request);
}
else {
return ServerResponse.status(UNAUTHORIZED).build();
}
})
.build();
前面的示例演示了调用next.handle(ServerRequest)是可选的。 当允许访问时,我们仅允许执行处理函数。
除了在路由器功能构建器上使用filter方法之外,还可以通过RouterFunction.filter(HandlerFilterFunction)将过滤器应用于现有路由器功能。
通过专用的CorsWebFilter提供对功能端点的CORS支持。