# 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（）提供了一个路由器构建器，可简化路由器的创建过程，如以下示例所示：

```java
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>：

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

下面的示例将主体提取到Flux \<Person>，其中Person对象从某种序列化形式（例如JSON或XML）解码：

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

前面的示例是使用更通用的ServerRequest.body（BodyExtractor）的快捷方式，该请求接受BodyExtractor功能策略接口。 实用程序类BodyExtractors提供对许多实例的访问。 例如，前面的示例也可以编写如下：

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

下面的示例显示如何访问表单数据：

```java
Mono<MultiValueMap<String, String> map = request.body(BodyExtractors.toFormData());
```

以下示例显示了如何以Map形式访问多部分数据：

```java
Mono<MultiValueMap<String, Part> map = request.body(BodyExtractors.toMultipartData());
```

下面的示例演示如何以流方式一次访问多个部分：

```java
Flux<Part> parts = request.body(BodyExtractors.toParts());
```

**ServerResponse**

ServerResponse提供对HTTP响应的访问，并且由于它是不可变的，因此可以使用一种build方法来创建它。您可以使用构建器来设置响应状态，添加响应标题或提供正文。以下示例使用JSON内容创建200（确定）响应：

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

下面的示例演示如何构建Location不带标头的201（已创建）响应：

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

**Handler Classes**

我们可以将处理程序函数编写为lambda，如以下示例所示：

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

这很方便，但是在应用程序中我们需要多个功能，并且多个内联lambda可能会变得凌乱。 因此，将相关的处理程序功能分组到一个处理程序类中很有用，该类的作用与基于注解的应用程序中的@Controller相似。 例如，以下类公开了反应型Person存储库：

```java
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实现：

```java
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头创建约束：

```java
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（）的快捷方式。

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

```java
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方法共享路径谓词。 例如，以上示例的最后几行可以通过使用嵌套路由以以下方式进行改进：

```java
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一起使用，我们可以进一步改进：

```java
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）：

```java
@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或同时使用两者来实现类似的功能。 该过滤器将应用于构建器构建的所有路由。 这意味着在嵌套路由中定义的过滤器不适用于“顶级”路由。 例如，考虑以下示例：

```java
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。 以下示例显示了如何执行此操作：

```java
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支持。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flydean.com/spring-framework-documentation5/webreactive/1.spring-webflux/1.5functional-endpoints.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
