1.5 Functional Endpoints

Spring WebFlux包含WebFlux.fn,这是一个轻量级的函数编程模型,其中的函数用于路由和处理请求,而契约则是为不变性而设计的。它是基于注解的编程模型的替代方案,但可以在相同的Reactive Core基础上运行。

1.5.1 总览

在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并通过内置服务器适配器之一进行安装:

  • RouterFunctions.toHttpHandler(RouterFunction)

  • RouterFunctions.toHttpHandler(RouterFunction,HandlerStrategies)

大多数应用程序都可以通过WebFlux Java配置运行,请参阅运行服务器。

1.5.2 HandlerFunction

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)。

1.5.3 RouterFunction

路由器功能用于将请求路由到相应的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")));

您可以使用以下命令组合多个请求谓词:

  • RequestPredicate.and(RequestPredicate)-两者都必须匹配。

  • RequestPredicate.or(RequestPredicate)-两者都可以匹配。

RequestPredicates中的许多谓词都是组成的。例如,RequestPredicates.GET(String)由RequestPredicates.method(HttpMethod)和RequestPredicates.path(String)组成。上面显示的示例还使用了两个请求谓词,因为构建器在内部使用RequestPredicates.GET并将其与接受谓词组合在一起。

Routes

路由器功能按顺序评估:如果第一个路由不匹配,则评估第二个路由,依此类推。因此,在通用路由之前声明更具体的路由是有意义的。请注意,此行为不同于基于注解的编程模型,在该模型中,将自动选择“最特定”的控制器方法。

使用路由器功能生成器时,所有定义的路由都组成一个RouterFunction,从build()返回。还有其他方法可以将多个路由器功能组合在一起:

  • 在RouterFunctions.route()构建器上添加(RouterFunction)

  • RouterFunction.and(RouterFunction)

  • RouterFunction.andRoute(RequestPredicate,HandlerFunction)-带有嵌套RouterFunctions.route()的RouterFunction.and()的快捷方式。

以下示例显示了四种路线的组成:

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();

1.5.4 Running a Server

如何在HTTP服务器中运行路由器功能?一个简单的选项是使用以下方法之一将路由器功能转换为HttpHandler:

  • RouterFunctions.toHttpHandler(RouterFunction)

  • RouterFunctions.toHttpHandler(RouterFunction,HandlerStrategies)

然后,可以通过遵循HttpHandler来获取特定于服务器的指令,将返回的HttpHandler与许多服务器适配器一起使用。

Spring Boot还使用了一个更典型的选项,即通过WebFlux Config使用基于DispatcherHandler的设置来运行,该配置使用Spring配置声明处理请求所需的组件。 WebFlux Java配置声明以下基础结构组件以支持功能端点:

  • RouterFunctionMapping:在Spring配置中检测一个或多个RouterFunction <?> bean,通过RouterFunction.andOther组合它们,并将请求路由到生成的组成RouterFunction。

  • HandlerFunctionAdapter:简单的适配器,它使DispatcherHandler调用映射到请求的HandlerFunction。

  • ServerResponseResultHandler:通过调用ServerResponse的writeTo方法来处理HandlerFunction调用的结果。

前面的组件使功能端点适合于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...
    }
}

1.5.5 Filtering Handler Functions

您可以使用路由功能构建器上的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支持。

最后更新于