Functional Endpoints
Spring Web MVC 包含 WebMvc.fn,其中函数用于路由和处理请求,且协定设计为不可变,是一种轻量级函数式编程模型。它是基于注解的编程模型的替代方案,但在其他方面它在相同的 DispatcherServlet 上运行。
Spring Web MVC includes WebMvc.fn, a lightweight functional programming model in which functions are used to route and handle requests and contracts are designed for immutability. It is an alternative to the annotation-based programming model but otherwise runs on the same DispatcherServlet.
Overview
在 WebMvc.fn 中,使用 HandlerFunction
处理一个 HTTP 请求,这是个接受 ServerRequest
并返回 ServerResponse
的函数。请求和响应对象都具有不可变契约,使开发者能够以 JDK 8 友好的方式访问 HTTP 请求和响应。HandlerFunction
等于基于注解的编程模型中 @RequestMapping
方法的主体。
In WebMvc.fn, an HTTP request is handled with a HandlerFunction
: a function that takes
ServerRequest
and returns a ServerResponse
.
Both the request and the response object have immutable contracts that offer JDK 8-friendly
access to the HTTP request and response.
HandlerFunction
is the equivalent of the body of a @RequestMapping
method in the
annotation-based programming model.
传入请求会通过一个 RouterFunction
路由到一个处理函数,该函数接受 ServerRequest
并返回一个可选的 HandlerFunction
(即 Optional<HandlerFunction>
)。当路由函数匹配成功时,会返回一个处理函数;否则返回一个空 Optional。RouterFunction
等于 @RequestMapping
注解,但存在一个主要差异,即路由函数不仅提供数据,还提供行为。
Incoming requests are routed to a handler function with a RouterFunction
: a function that
takes ServerRequest
and returns an optional HandlerFunction
(i.e. Optional<HandlerFunction>
).
When the router function matches, a handler function is returned; otherwise an empty Optional.
RouterFunction
is the equivalent of a @RequestMapping
annotation, but with the major
difference that router functions provide not just data, but also behavior.
RouterFunctions.route()
提供了一个路由构建器,它使用户能够轻松创建路由,如下图例所示:
RouterFunctions.route()
provides a router builder that facilitates the creation of routers,
as the following example shows:
- Java
-
import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.servlet.function.RequestPredicates.*; import static org.springframework.web.servlet.function.RouterFunctions.route; PersonRepository repository = ... PersonHandler handler = new PersonHandler(repository); RouterFunction<ServerResponse> route = route() (1) .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) .GET("/person", accept(APPLICATION_JSON), handler::listPeople) .POST("/person", handler::createPerson) .build(); public class PersonHandler { // ... public ServerResponse listPeople(ServerRequest request) { // ... } public ServerResponse createPerson(ServerRequest request) { // ... } public ServerResponse getPerson(ServerRequest request) { // ... } }
1 | Create router using route() .
|
2 | Create router using the router DSL. |
例如,如果您将 RouterFunction
注册为一个 bean,通过在 @Configuration
类中展示它,它将被 servlet 自动检测到,如 Running a Server 中所述。
If you register the RouterFunction
as a bean, for instance by exposing it in a
@Configuration
class, it will be auto-detected by the servlet, as explained in Running a Server.
HandlerFunction
ServerRequest
和 ServerResponse
是不可变接口,使开发者能够以 JDK 8 友好的方式访问 HTTP 请求和响应,包括标头、主体、方法和状态代码。
ServerRequest
and ServerResponse
are immutable interfaces that offer JDK 8-friendly
access to the HTTP request and response, including headers, body, method, and status code.
ServerRequest
ServerRequest
提供对 HTTP 方法、URI、标头和查询参数的访问,而对主体的访问是通过 body
方法提供的。
ServerRequest
provides access to the HTTP method, URI, headers, and query parameters,
while access to the body is provided through the body
methods.
下例提取请求主体为一个 String
:
The following example extracts the request body to a String
:
-
Java
-
Kotlin
String string = request.body(String.class);
val string = request.body<String>()
下例将主体提取为 List<Person>
,其中 Person
对象从序列化形式(例如 JSON 或 XML)解码而来:
The following example extracts the body to a List<Person>
,
where Person
objects are decoded from a serialized form, such as JSON or XML:
-
Java
-
Kotlin
List<Person> people = request.body(new ParameterizedTypeReference<List<Person>>() {});
val people = request.body<Person>()
下例展示了如何访问参数:
The following example shows how to access parameters:
-
Java
-
Kotlin
MultiValueMap<String, String> params = request.params();
val map = request.params()
ServerResponse
ServerResponse
提供对 HTTP 响应的访问,而且因为它不可变,所以你可以使用 build
方法来创建它。你可以使用该构建器设置响应状态、添加响应标头或提供一个主体。下例创建了一个 200 (OK) 响应(带有 JSON 内容):
ServerResponse
provides access to the HTTP response and, since it is immutable, you can use
a build
method to create it. You can use the builder to set the response status, to add response
headers, or to provide a body. The following example creates a 200 (OK) response with JSON
content:
-
Java
-
Kotlin
Person person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
val person: Person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person)
下例展示了如何构建一个 201 (CREATED) 响应(带有 Location
标头和空主体):
The following example shows how to build a 201 (CREATED) response with a Location
header and no body:
-
Java
-
Kotlin
URI location = ...
ServerResponse.created(location).build();
val location: URI = ...
ServerResponse.created(location).build()
你也可以使用一个异步结果作为一个主体,它可以是 CompletableFuture
、Publisher
或任何其他受 ReactiveAdapterRegistry
支持的类型。例如:
You can also use an asynchronous result as the body, in the form of a CompletableFuture
,
Publisher
, or any other type supported by the ReactiveAdapterRegistry
. For instance:
-
Java
-
Kotlin
Mono<Person> person = webClient.get().retrieve().bodyToMono(Person.class);
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
val person = webClient.get().retrieve().awaitBody<Person>()
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person)
如果不仅主体,而且状态或标头也基于一个异步类型,则可以在 ServerResponse
上使用静态 async
方法,该方法接受 CompletableFuture<ServerResponse>
、Publisher<ServerResponse>
或任何其他受 ReactiveAdapterRegistry
支持的异步类型。例如:
If not just the body, but also the status or headers are based on an asynchronous type,
you can use the static async
method on ServerResponse
, which
accepts CompletableFuture<ServerResponse>
, Publisher<ServerResponse>
, or
any other asynchronous type supported by the ReactiveAdapterRegistry
. For instance:
-
Java
Mono<ServerResponse> asyncResponse = webClient.get().retrieve().bodyToMono(Person.class)
.map(p -> ServerResponse.ok().header("Name", p.name()).body(p));
ServerResponse.async(asyncResponse);
Server-Sent Events 可通过 ServerResponse
上的 sse
静态方法提供。该方法提供的构建器允许您发送字符串或其他对象作为 JSON。例如:
Server-Sent Events can be provided via the
static sse
method on ServerResponse
. The builder provided by that method
allows you to send Strings, or other objects as JSON. For example:
-
Java
-
Kotlin
public RouterFunction<ServerResponse> sse() {
return route(GET("/sse"), request -> ServerResponse.sse(sseBuilder -> {
// Save the sseBuilder object somewhere..
}));
}
// In some other thread, sending a String
sseBuilder.send("Hello world");
// Or an object, which will be transformed into JSON
Person person = ...
sseBuilder.send(person);
// Customize the event by using the other methods
sseBuilder.id("42")
.event("sse event")
.data(person);
// and done at some point
sseBuilder.complete();
fun sse(): RouterFunction<ServerResponse> = router {
GET("/sse") { request -> ServerResponse.sse { sseBuilder ->
// Save the sseBuilder object somewhere..
}
}
// In some other thread, sending a String
sseBuilder.send("Hello world")
// Or an object, which will be transformed into JSON
val person = ...
sseBuilder.send(person)
// Customize the event by using the other methods
sseBuilder.id("42")
.event("sse event")
.data(person)
// and done at some point
sseBuilder.complete()
Handler Classes
我们可以将一个处理函数编写为一个 lambda,如下图例所示:
We can write a handler function as a lambda, as the following example shows:
-
Java
-
Kotlin
HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().body("Hello World");
val helloWorld: (ServerRequest) -> ServerResponse =
{ ServerResponse.ok().body("Hello World") }
这样做很方便,但在应用程序中,我们需要多个函数,并且多个内联 lambda 可能很混乱。因此,将相关的处理函数分组在一个处理函数类中很有用,这个类在基于注解的应用程序中所扮演的角色与 @Controller
类似。例如,下例公开了一个响应式的 Person
存储库:
That is convenient, but in an application we need multiple functions, and multiple inline
lambda’s can get messy.
Therefore, it is useful to group related handler functions together into a handler class, which
has a similar role as @Controller
in an annotation-based application.
For example, the following class exposes a reactive Person
repository:
- Java
-
import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.server.ServerResponse.ok; public class PersonHandler { private final PersonRepository repository; public PersonHandler(PersonRepository repository) { this.repository = repository; } public ServerResponse listPeople(ServerRequest request) { (1) List<Person> people = repository.allPeople(); return ok().contentType(APPLICATION_JSON).body(people); } public ServerResponse createPerson(ServerRequest request) throws Exception { (2) Person person = request.body(Person.class); repository.savePerson(person); return ok().build(); } public ServerResponse getPerson(ServerRequest request) { (3) int personId = Integer.parseInt(request.pathVariable("id")); Person person = repository.getPerson(personId); if (person != null) { return ok().contentType(APPLICATION_JSON).body(person); } else { return ServerResponse.notFound().build(); } } }
1 | listPeople is a handler function that returns all Person objects found in the repository as
JSON. |
2 | createPerson is a handler function that stores a new Person contained in the request body. |
3 | getPerson is a handler function that returns a single person, identified by the id path
variable. We retrieve that Person from the repository and create a JSON response, if it is
found. If it is not found, we return a 404 Not Found response.
|
4 | listPeople is a handler function that returns all Person objects found in the repository as
JSON. |
5 | createPerson is a handler function that stores a new Person contained in the request body. |
6 | getPerson is a handler function that returns a single person, identified by the id path
variable. We retrieve that Person from the repository and create a JSON response, if it is
found. If it is not found, we return a 404 Not Found response. |
Validation
函数式端点可以使用 Spring 的 validation facilities 对请求正文进行验证。例如,给定 Person
的自定义 Spring Validator 实现:
A functional endpoint can use Spring’s validation facilities to
apply validation to the request body. For example, given a custom Spring
Validator implementation for a Person
:
- Java
-
public class PersonHandler { private final Validator validator = new PersonValidator(); (1) // ... public ServerResponse createPerson(ServerRequest request) { Person person = request.body(Person.class); validate(person); (2) repository.savePerson(person); return ok().build(); } private void validate(Person person) { Errors errors = new BeanPropertyBindingResult(person, "person"); validator.validate(person, errors); if (errors.hasErrors()) { throw new ServerWebInputException(errors.toString()); (3) } } }
1 | Create Validator instance. |
2 | Apply validation. |
3 | Raise exception for a 400 response.
|
4 | Create Validator instance. |
5 | Apply validation. |
6 | Raise exception for a 400 response. |
处理程序还可以创建一个全局`Validator`实例并将其注入,进而使用标准 Bean 验证 API (JSR-303),该实例基于`LocalValidatorFactoryBean`。请参见Spring Validation。
Handlers can also use the standard bean validation API (JSR-303) by creating and injecting
a global Validator
instance based on LocalValidatorFactoryBean
.
See Spring Validation.
RouterFunction
路由函数用于将请求路由到对应的 HandlerFunction
。通常,您不会自己编写路由函数,而是使用 RouterFunctions
实用类上的一个方法来创建一个。RouterFunctions.route()
(无参数)为一个流畅的构建器,用于创建一个路由函数,而 RouterFunctions.route(RequestPredicate, HandlerFunction)
提供了一个创建一个路由的直接方式。
Router functions are used to route the requests to the corresponding HandlerFunction
.
Typically, you do not write router functions yourself, but rather use a method on the
RouterFunctions
utility class to create one.
RouterFunctions.route()
(no parameters) provides you with a fluent builder for creating a router
function, whereas RouterFunctions.route(RequestPredicate, HandlerFunction)
offers a direct way
to create a router.
通常建议使用 route()
构建器,因为它为典型的映射场景提供方便的快捷方式,而无需使用难以发现的静态导入。例如,路由函数构建器提供 GET(String, HandlerFunction)
方法来为 GET 请求创建一个映射;POST(String, HandlerFunction)
为 POST 请求创建一个映射。
Generally, it is recommended to use the route()
builder, as it provides
convenient short-cuts for typical mapping scenarios without requiring hard-to-discover
static imports.
For instance, the router function builder offers the method GET(String, HandlerFunction)
to create a mapping for GET requests; and POST(String, HandlerFunction)
for POSTs.
除了基于 HTTP 方法的映射之外,路由构建器还提供了一种在映射到请求时引入其他谓词的方法。对于每种 HTTP 方法,都有一个重载的变体,将 RequestPredicate
作为参数,通过它可以表示附加约束。
Besides HTTP method-based mapping, the route builder offers a way to introduce additional
predicates when mapping to requests.
For each HTTP method there is an overloaded variant that takes a RequestPredicate
as a
parameter, through which additional constraints can be expressed.
Predicates
您可以编写自己的 RequestPredicate
,但 RequestPredicates
实用类提供基于请求路径、HTTP 方法、内容类型等的常用实现。以下示例使用请求谓词基于 Accept
头创建约束:
You can write your own RequestPredicate
, but the RequestPredicates
utility class
offers commonly used implementations, based on the request path, HTTP method, content-type,
and so on.
The following example uses a request predicate to create a constraint based on the Accept
header:
-
Java
-
Kotlin
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().body("Hello World")).build();
import org.springframework.web.servlet.function.router
val route = router {
GET("/hello-world", accept(TEXT_PLAIN)) {
ServerResponse.ok().body("Hello World")
}
}
您可以通过使用以下方法组合多个请求谓词:
You can compose multiple request predicates together by using:
-
RequestPredicate.and(RequestPredicate)
— both must match. -
RequestPredicate.or(RequestPredicate)
— either can match.
RequestPredicates
中的许多谓词是组合的。例如,RequestPredicates.GET(String)
由 RequestPredicates.method(HttpMethod)
和 RequestPredicates.path(String)
组合而成。上面所示的示例还使用两个请求谓词,因为构建器在内部使用 RequestPredicates.GET
,并将其与 accept
谓词组合。
Many of the predicates from RequestPredicates
are composed.
For example, RequestPredicates.GET(String)
is composed from RequestPredicates.method(HttpMethod)
and RequestPredicates.path(String)
.
The example shown above also uses two request predicates, as the builder uses
RequestPredicates.GET
internally, and composes that with the accept
predicate.
Routes
路由函数按顺序评估:如果第一个路由不匹配,则评估第二个路由,依此类推。因此,在通用路由之前声明更具体的路由是有意义的。在将路由函数注册为 Spring bean 时,这一点也很重要,如下所述。请注意,此行为不同于基于注释的编程模型,其中“最具体”的控制器方法是自动选择的。
Router functions are evaluated in order: if the first route does not match, the second is evaluated, and so on. Therefore, it makes sense to declare more specific routes before general ones. This is also important when registering router functions as Spring beans, as will be described later. Note that this behavior is different from the annotation-based programming model, where the "most specific" controller method is picked automatically.
使用路由函数构建器时,所有已定义的路由都组合成一个 RouterFunction
,该 RouterFunction
从 build()
返回。还有其他方法可以将多个路由函数组合在一起:
When using the router function builder, all defined routes are composed into one
RouterFunction
that is returned from build()
.
There are also other ways to compose multiple router functions together:
-
add(RouterFunction)
on theRouterFunctions.route()
builder -
RouterFunction.and(RouterFunction)
-
RouterFunction.andRoute(RequestPredicate, HandlerFunction)
— shortcut forRouterFunction.and()
with nestedRouterFunctions.route()
.
以下示例显示了四个路由的组合:
The following example shows the composition of four routes:
- Java
-
import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.servlet.function.RequestPredicates.*; PersonRepository repository = ... PersonHandler handler = new PersonHandler(repository); RouterFunction<ServerResponse> otherRoute = ... RouterFunction<ServerResponse> route = route() .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) (1) .GET("/person", accept(APPLICATION_JSON), handler::listPeople) (2) .POST("/person", handler::createPerson) (3) .add(otherRoute) (4) .build();
1 | GET /person/{id} with an Accept header that matches JSON is routed to
PersonHandler.getPerson |
2 | GET /person with an Accept header that matches JSON is routed to
PersonHandler.listPeople |
3 | POST /person with no additional predicates is mapped to
PersonHandler.createPerson , and |
4 | otherRoute is a router function that is created elsewhere, and added to the route built.
|
5 | GET /person/{id} with an Accept header that matches JSON is routed to
PersonHandler.getPerson |
6 | GET /person with an Accept header that matches JSON is routed to
PersonHandler.listPeople |
7 | POST /person with no additional predicates is mapped to
PersonHandler.createPerson , and |
8 | otherRoute is a router function that is created elsewhere, and added to the route built. |
Nested Routes
对于一组路由函数来说,具有一个共享谓词(例如共享路径)是很常见的。在上面的示例中,共享谓词将是一个与 /person
匹配的路径谓词,由三个路由使用。在使用注释时,您可以通过使用映射到 /person
的类型级 @RequestMapping
注释来消除这种重复。在 WebMvc.fn 中,可以通过路由函数构建器上的 path
方法共享路径谓词。例如,通过使用嵌套路由,可以按如下方式改进上述示例的最后几行:
It is common for a group of router functions to have a shared predicate, for instance a shared
path.
In the example above, the shared predicate would be a path predicate that matches /person
,
used by three of the routes.
When using annotations, you would remove this duplication by using a type-level @RequestMapping
annotation that maps to /person
.
In WebMvc.fn, path predicates can be shared through the path
method on the router function builder.
For instance, the last few lines of the example above can be improved in the following way by using nested routes:
- Java
-
RouterFunction<ServerResponse> route = route() .path("/person", builder -> builder (1) .GET("/{id}", accept(APPLICATION_JSON), handler::getPerson) .GET(accept(APPLICATION_JSON), handler::listPeople) .POST(handler::createPerson)) .build();
1 | Note that second parameter of path is a consumer that takes the router builder.
|
2 | Using nest DSL. |
虽然基于路径的嵌套是最常见的,但是您可以通过使用构建器上的 nest
方法嵌套在任何类型的谓词上。上面仍然包含一些重复,形式为共享的 Accept
头谓词。我们可以通过将 nest
方法与 accept
一起使用来进一步改进:
Though path-based nesting is the most common, you can nest on any kind of predicate by using
the nest
method on the builder.
The above still contains some duplication in the form of the shared Accept
-header predicate.
We can further improve by using the nest
method together with accept
:
-
Java
-
Kotlin
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST(handler::createPerson))
.build();
import org.springframework.web.servlet.function.router
val route = router {
"/person".nest {
accept(APPLICATION_JSON).nest {
GET("/{id}", handler::getPerson)
GET("", handler::listPeople)
POST(handler::createPerson)
}
}
}
Serving Resources
WebMvc.fn 提供了用于提供资源的内置支持。
WebMvc.fn provides built-in support for serving resources.
除了下面描述的功能外,还可以通过 [|RouterFunctions.resources()|](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/function/RouterFunctions.html#resources(java.util.function.Function)) 实现更为灵活的资源处理。 |
In addition to the capabilities described below, it is possible to implement even more flexible resource handling thanks to
|
Redirecting to a resource
可以将与指定谓词匹配的请求重定向到资源。例如,这对于处理单页面应用程序中的重定向非常有用。
It is possible to redirect requests matching a specified predicate to a resource. This can be useful, for example, for handling redirects in Single Page Applications.
-
Java
-
Kotlin
ClassPathResource index = new ClassPathResource("static/index.html");
List<String> extensions = List.of("js", "css", "ico", "png", "jpg", "gif");
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).or(pathExtension(extensions::contains)).negate();
RouterFunction<ServerResponse> redirectToIndex = route()
.resource(spaPredicate, index)
.build();
val redirectToIndex = router {
val index = ClassPathResource("static/index.html")
val extensions = listOf("js", "css", "ico", "png", "jpg", "gif")
val spaPredicate = !(path("/api/**") or path("/error") or
pathExtension(extensions::contains))
resource(spaPredicate, index)
}
Serving resources from a root location
还可以将与给定模式匹配的请求路由到相对于给定根位置的资源。
It is also possible to route requests that match a given pattern to resources relative to a given root location.
-
Java
-
Kotlin
Resource location = new FileSystemResource("public-resources/");
RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
val location = FileSystemResource("public-resources/")
val resources = router { resources("/resources/**", location) }
Running a Server
您通常会通过MVC Config在基于DispatcherHandler
的设置中运行路由器函数,该设置使用 Spring 配置来声明处理请求所需的组件。MVC Java 配置声明以下基础设施组件以支持函数 end-point:
You typically run router functions in a DispatcherHandler
-based setup through the
MVC Config, which uses Spring configuration to declare the
components required to process requests. The MVC Java configuration declares the following
infrastructure components to support functional endpoints:
-
RouterFunctionMapping
: Detects one or moreRouterFunction<?>
beans in the Spring configuration, orders them, combines them throughRouterFunction.andOther
, and routes requests to the resulting composedRouterFunction
. -
HandlerFunctionAdapter
: Simple adapter that letsDispatcherHandler
invoke aHandlerFunction
that was mapped to a request.
上述元件允许功能端点适用于 DispatcherServlet
请求处理生命周期,而且还可以(可能是)与已声明的标注控制器并排运行。这也是 Spring Boot Webstarter 启用功能端点的实现方法。
The preceding components let functional endpoints fit within the DispatcherServlet
request
processing lifecycle and also (potentially) run side by side with annotated controllers, if
any are declared. It is also how functional endpoints are enabled by the Spring Boot Web
starter.
下面的示例展示了 WebFlux Java 配置:
The following example shows a WebFlux Java configuration:
-
Java
-
Kotlin
@Configuration
@EnableMvc
public class WebConfig implements WebMvcConfigurer {
@Bean
public RouterFunction<?> routerFunctionA() {
// ...
}
@Bean
public RouterFunction<?> routerFunctionB() {
// ...
}
// ...
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// configure message conversion...
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// configure CORS...
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// configure view resolution for HTML rendering...
}
}
@Configuration
@EnableMvc
class WebConfig : WebMvcConfigurer {
@Bean
fun routerFunctionA(): RouterFunction<*> {
// ...
}
@Bean
fun routerFunctionB(): RouterFunction<*> {
// ...
}
// ...
override fun configureMessageConverters(converters: List<HttpMessageConverter<*>>) {
// configure message conversion...
}
override fun addCorsMappings(registry: CorsRegistry) {
// configure CORS...
}
override fun configureViewResolvers(registry: ViewResolverRegistry) {
// configure view resolution for HTML rendering...
}
}
Filtering Handler Functions
可使用路由函数生成器中的 before
、after
或 filter
方法过滤处理程序函数。借助标注,可通过使用 @ControllerAdvice
、ServletFilter
或同时使用两者,实现类似功能。过滤器将应用到生成器构建的所有路由。这意味着在嵌套路由中定义的过滤器不会应用到“顶级”路由。例如,考虑以下示例:
You can filter handler functions by using the before
, after
, or filter
methods on the routing
function builder.
With annotations, you can achieve similar functionality by using @ControllerAdvice
, a ServletFilter
, or both.
The filter will apply to all routes that are built by the builder.
This means that filters defined in nested routes do not apply to "top-level" routes.
For instance, consider the following example:
- 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) (1) .header("X-RequestHeader", "Value") .build())) .POST(handler::createPerson)) .after((request, response) -> logResponse(response)) (2) .build();
1 | The before filter that adds a custom request header is only applied to the two GET routes. |
2 | The after filter that logs the response is applied to all routes, including the nested ones.
|
3 | The before filter that adds a custom request header is only applied to the two GET routes. |
4 | The after filter that logs the response is applied to all routes, including the nested ones. |
路由生成器中的 filter
方法接收一个 HandlerFilterFunction
:一个接收 ServerRequest
和 HandlerFunction
并返回 ServerResponse
的函数。处理程序函数参数代表链中的下一个元素。这通常是指被路由到的处理程序,但如果应用了多个过滤器,它也可以是另一个过滤器。
The filter
method on the router builder takes a HandlerFilterFunction
: a
function that takes a ServerRequest
and HandlerFunction
and returns a ServerResponse
.
The handler function parameter represents the next element in the chain.
This is typically the handler that is routed to, but it can also be another
filter if multiple are applied.
现在,假设我们有一个可以确定特定路径是否被允许的 SecurityManager
,我们可以向路由中添加一个简单的安全性过滤器。以下示例展示了如何执行此操作:
Now we can add a simple security filter to our route, assuming that we have a SecurityManager
that
can determine whether a particular path is allowed.
The following example shows how to do so:
-
Java
-
Kotlin
SecurityManager securityManager = ...
RouterFunction<ServerResponse> 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();
import org.springframework.web.servlet.function.router
val securityManager: SecurityManager = ...
val route = router {
("/person" and accept(APPLICATION_JSON)).nest {
GET("/{id}", handler::getPerson)
GET("", handler::listPeople)
POST(handler::createPerson)
filter { request, next ->
if (securityManager.allowAccessTo(request.path())) {
next(request)
}
else {
status(UNAUTHORIZED).build();
}
}
}
}
上述示例表明调用 next.handle(ServerRequest)
是可选的。我们只有在允许访问时才运行处理程序函数。
The preceding example demonstrates that invoking the next.handle(ServerRequest)
is optional.
We only let the handler function be run when access is allowed.
除了在路由函数生成器中使用 filter
方法之外,还可通过 RouterFunction.filter(HandlerFilterFunction)
将过滤器应用到现有路由函数。
Besides using the filter
method on the router function builder, it is possible to apply a
filter to an existing router function via RouterFunction.filter(HandlerFilterFunction)
.
通过专用 |
CORS support for functional endpoints is provided through a dedicated
|