功能性端点

Spring WebFlux提供了一个轻量级的函数式编程模型,其中函数用于路由和处理请求,而契约的设计是为不变性设计的。

它是基于注解的编程模型的另一种选择,但是它运行在相同的响应式SpringWeb基础中

HandlerFunction

HandlerFunction处理传入的HTTP请求,本质上是一个函数,专门接受一个ServerRequest 请求和返回一个Mono<ServerResponse>响应。HandlerFunction对应的函数注解是@RequestMapping方法

ServerRequest和ServerResponse都是不可变的接口,它提供了对底层HTTP消息的jdk - 8友好访问,这些消息具有非阻塞的响应式背压。

想该请求的body暴露为Reactor Flux 或者 Mono 类型。响应接受任何的响应式流作为Publisher

ServerRequest提供对各种HTTP请求元素的访问: 方法、URI、查询参数甚至单独的ServerRequest.Headers接口,要访问body是通过body的方法提供的。例如:如何将请求体提取到Mono

Mono<String> string = request.bodyToMono(String.class);

如何将请求体提取到Flux中,Person是可以被反序列化的类

Flux<Person> people = request.bodyToFlux(Person.class);

bodyToMono 和 bodyToFlux 是通用的便利方法。

ServerRequest.body(BodyExtractor)方法,BodyExtractor是功能性策略接口,允许编写自己的提取逻辑,所以以上的可以代替为

Mono<String> string = request.body(BodyExtractors.toMono(String.class);
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class);

同样的,ServerResponse提供对HTTP响应的访问。由于它是不可变的,所以您需要创建一个与生成器的服务器响应,允许您设置响应状态,添加响应标头,并提供一个响应体。例如:下面如何创建一个200 OK状态的响应、一个JSON内容类型主体

Mono<Person> person = ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);

如何构建一个201、Location头和一个空响应

URI location = ServerResponse.created(location).build();

简单HelloWorld:返回状态码为200的字符串

HandlerFunction<ServerResponse> helloWorld =  request -> ServerResponse.ok().body(fromObject("Hello World"));

像上面这么做,处理函数是很方便的。但是可能缺乏可读性,在处理多个函数的情况下,可能不是那么好维护的。因此,建议将相关的处理程序函数分组到处理程序或控制器类中。例如:下面的类暴露了PersonRespository类

import static org.springframework.http.MediaType.APPLICATION_JSON;
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 ServerResponse.ok().contentType(APPLICATION_JSON).body(people, Person.class);
    }

    public Mono<ServerResponse> createPerson(ServerRequest request) { 
        Mono<Person> person = request.bodyToMono(Person.class);
        return ServerResponse.ok().build(repository.savePerson(person));
    }

    public Mono<ServerResponse> getPerson(ServerRequest request) { 
        int personId = Integer.valueOf(request.pathVariable("id"));
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();
        Mono<Person> personMono = this.repository.getPerson(personId);
        return personMono
                .flatMap(person -> ServerResponse.ok().contentType(APPLICATION_JSON).body(fromObject(person)))
                .switchIfEmpty(notFound);
    }
}

① listPeople将所有person对象作为json字符串返回

createPerson是一个处理函数,将一个新的对象包含在请求体中,让PersonRepository.savePerson(Person) 返回 Mono<Void>,,当Mono发出完成信号时,对象已经持久化。所以,我们使用一个Publisher<Void>发送信息,此时,新的Person对象已经存储完毕。

③ getPerson 是一个处理函数通过id返回的单个对象,我们检索数据,如果找到该Person并创建一个JSON响应。如果未找到数据,我们通过switchIfEmpty返回一个404状态

RouterFunction

传入的请求被路由到处理函数生成一个RouterFunction,该函数需要一个ServerRequest入参并且返回Mono<HandlerFunction>,如果请求匹配到了特定的路由,则返回处理程序函数;否则它将返回一个空的Mono。RouterFunction 具有@RequestMapping和@Controller的特性

通常,你无须编写路由器,而是通过RouterFunctions.route(RequestPredicate, HandlerFunction)创建一个路由。如果是一个predicate请求,则请求被路由到给定的处理程序函数中。否则不执行该路由,返回一个404结果。当然,你也可以编写自己的请求路由,但是没必要这么做:RequestPredicates提供常用predicates、基于路径的匹配、HTTP方法。下面是使用Router返回的HelloWorld

    RouterFunction<ServerResponse> helloWorldRoute =
    RouterFunctions.route(RequestPredicates.path("/hello-world"),
    request -> Response.ok().body(fromObject("Hello World")));

两个路由器函数可以被组合成一个新的路由器函数,可以路由到任意一处理器函数:如果第一个路径的predicate不匹配,则计算第二个路径,组合的路由器函数按顺序进行评估,因此将特定的函数放置于泛型的函数之前是有一定道理的。你可以调用路由函数RouterFunction.and(RouterFunction)或者RouterFunction.andRoute(RequestPredicate, HandlerFunction)组成一个方便的组合。

鉴于上例PersonHandler,我们可以定义一个路由器函数来路由到各自的处理函数,使用方法引用来处理程序函数。

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> personRoute =
    route(GET("/person/{id}").and(accept(APPLICATION_JSON)), handler::getPerson)
        .andRoute(GET("/person").and(accept(APPLICATION_JSON)), handler::listPeople)
        .andRoute(POST("/person").and(contentType(APPLICATION_JSON)), handler::createPerson);

除了路由器函数之外,您还可以通过调用RequestPredicate.and(RequestPredicate)或者 RequestPredicate.or(RequestPredicate)。符合预期:因为如果给定的谓词匹配,由此产生的谓词匹配;或任一谓词执行匹配,则匹配;在RequestPredicates中发现大多数的谓词都是组合。例如:RequestPredicates.GET(String) 是RequestPredicates.method(HttpMethod.GET) and RequestPredicates.path(String)的结合

运行

如何在HTTP服务器中运行路由器函数?一个简单的方法是将路由器函数转换为HttpHandler#RouterFunctions.toHttpHandler(RouterFunction),然后,HttpHandler可以与许多服务器适配器一起使用。查看HttpHandler的特殊指令

还可以将一个DispatcherHandler设置与注解控制器一起运行。最简单的方法就是通过WebFlux Java Config创建必要的配置与路由器处理请求和处理程序函数

HandlerFilterFunction

通过调用RouterFunction.filter(HandlerFilterFunction),可以通过RouterFunction.filter(HandlerFilterFunction)筛选路由映射的路由,其中HandlerFilterFunction本质上是一个接受ServerRequest和HandlerFunction的函数,并返回一个ServerResponse

handler函数参数表示链中的下一个元素:这通常是被路由到的handlerfunction函数,但如果应用多个过滤器,也可以是另一个FilterFunction,类似于注解中的@ControllerAdvice 或者 ServletFilter实现其功能。让我们在我们的路由中添加一个简单的安全过滤器,假设我们有一个SecurityManager,它可以确定是否允许特定的路径

import static org.springframework.http.HttpStatus.UNAUTHORIZED;

SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = ...

RouterFunction<ServerResponse> filteredRoute =
    route.filter(request, next) -> {
        if (securityManager.allowAccessTo(request.path())) {
            return next.handle(request);
        }
        else {
            return ServerResponse.status(UNAUTHORIZED).build();
        }
  });

看到上述实例next.handle(ServerRequest)是可选的:我们只允许在允许访问时执行处理程序函数

CORS 跨域的实现由一个专门的CorsWebFilter提供

results matching ""

    No results matching ""