SpringWebFlux
-3(5.3.12版学习笔记2021.11.10)与 Spring MVC 中的使用方式相同
您可以使用
@ModelAttribute
注解:
@RequestMapping
方法上,可以从模型创建或访问对象,并通过WebDataBinder
将其绑定到请求。@Controller
或@ControllerAdvice
类中的方法级 Comments,有助于在任何@RequestMapping
方法调用之前初始化模型。@RequestMapping
方法上将其返回值标记为模型属性。本节讨论
@ModelAttribute
方法,或前面列表中的第二项。控制器可以具有任意数量的@ModelAttribute
方法。所有此类方法均在同一控制器中的@RequestMapping
方法之前调用。@ModelAttribute
方法也可以通过@ControllerAdvice
在控制器之间共享。有关更多详细信息,请参见Controller Advice部分。
@ModelAttribute
方法具有灵活的方法签名。它们支持许多与@RequestMapping
方法相同的参数(@ModelAttribute
本身以及与请求主体相关的任何东西除外)。下面的示例使用
@ModelAttribute
方法:
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
model.addAttribute(accountRepository.findAccount(number));
// add more ...
}
// 以下示例仅添加一个属性:
@ModelAttribute
public Account addAccount(@RequestParam String number) {
return accountRepository.findAccount(number);
}
如果未明确指定名称,则根据类型选择默认名称,如Conventions的 javadoc 中所述。您始终可以使用重载的
addAttribute
方法或通过@ModelAttribute
上的 name 属性(用于返回值)来分配显式名称。与 Spring MVC 不同,Spring WebFlux 在模型中显式支持响应类型(例如
Mono
或io.reactivex.Single
)。可以在@RequestMapping
调用时将此类异步模型属性透明地解析(并更新模型)为其实际值,前提是声明了不带包装的@ModelAttribute
参数,如以下示例所示:
@ModelAttribute
public void addAccount(@RequestParam String number) {
Mono<Account> accountMono = accountRepository.findAccount(number);
model.addAttribute("account", accountMono);
}
@PostMapping("/accounts")
public String handle(@ModelAttribute Account account, BindingResult errors) {
// ...
}
此外,任何具有 Reactive 类型包装器的模型属性都将在视图渲染之前解析为其实际值(并更新了模型)。
您也可以将
@ModelAttribute
用作@RequestMapping
方法的方法级 Comments,在这种情况下@RequestMapping
方法的返回值将解释为模型属性。通常不需要这样做,因为这是 HTML 控制器中的默认行为,除非返回值是String
,否则它将被解释为视图名称。@ModelAttribute
还可以帮助自定义模型属性名称,如以下示例所示:
@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount") // 定义视图名称
public Account handle() {
// ...
return account;
}
DataBinder
(数据绑定)与 Spring MVC 中的使用相同
@Controller
或@ControllerAdvice
类可以具有@InitBinder
方法,以初始化WebDataBinder
的实例。这些依次用于:
String
的请求值(例如请求参数,路径变量,Headers,Cookie 等)转换为控制器方法参数的目标类型。String
值。
@InitBinder
个方法可以注册特定于控制器的java.bean.PropertyEditor
或 SpringConverter
和Formatter
组件。此外,您可以使用WebFlux Java 配置在全局共享的FormattingConversionService
中注册Converter
和Formatter
类型。
@InitBinder
方法支持与@RequestMapping
方法相同的许多参数,但@ModelAttribute
(命令对象)参数除外。通常,它们使用WebDataBinder
参数声明(用于注册)和void
返回值。以下示例使用@InitBinder
注解:
@Controller
public class FormController {
@InitBinder // (1 = 使用@InitBinderComments。)
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
// ...
}
// 或者,当通过共享的FormattingConversionService使用基于Formatter的设置时,可以重新使用相同的方法并注册特定于控制器的Formatter实例,如以下示例所示:
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd")); // (1 = 添加自定义格式程序(在本例中为DateFormatter)
}
与 Spring MVC 中的使用方式相同
@Controller
和@ControllerAdvice类可以具有@ExceptionHandler
个方法来处理控制器方法中的异常。下面的示例包括这样的处理程序方法:
@Controller
public class SimpleController {
// ...
@ExceptionHandler // (1 = 声明@ExceptionHandler)
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
该异常可以与正在传播的顶级异常(即直接抛出
IOException
)匹配,也可以与顶级包装程序异常中的直接原因匹配(例如,将IOException
包裹在IllegalStateException
内)。对于匹配的异常类型,最好将目标异常声明为方法参数,如前面的示例所示。或者,Comments 声明可以缩小异常类型以使其匹配。我们通常建议在参数签名中尽可能具体,并在优先级为
@ControllerAdvice
的情况下以相应的 Sequences 声明您的主根异常 Map。有关详情,请参见MVC 部分。WebFlux 中的
@ExceptionHandler
方法与@RequestMapping
方法支持相同的方法参数和返回值,但与请求正文和@ModelAttribute
相关的方法参数除外。
HandlerAdapter
for@RequestMapping
方法提供了对 Spring WebFlux 中@ExceptionHandler
方法的支持。有关更多详细信息,请参见DispatcherHandler。
与 Spring MVC 中的相同
REST 服务的常见要求是在响应正文中包含错误详细信息。 Spring 框架不会自动这样做,因为响应主体中错误详细信息的表示是特定于应用程序的。但是,
@RestController
可以使用具有ResponseEntity
返回值的@ExceptionHandler
方法来设置响应的状态和主体。也可以在@ControllerAdvice
类中声明此类方法以将其全局应用。请注意,Spring WebFlux 与 Spring MVC
ResponseEntityExceptionHandler
不具有等效项,因为 WebFlux 仅引发ResponseStatusException
(或其子类),并且不需要将其转换为 HTTP 状态代码。
与 Spring MVC 中的使用相同
通常,
@ExceptionHandler
,@InitBinder
和@ModelAttribute
方法适用于声明它们的@Controller
类(或类层次结构)。如果希望此类方法更全局地应用(跨控制器),则可以在标有@ControllerAdvice
或@RestControllerAdvice
的类中声明它们。
@ControllerAdvice
标有@Component
,这意味着可以通过component scanning将此类注册为 Spring Bean。@RestControllerAdvice
也是标有@ControllerAdvice
和@ResponseBody
的元 Comments,从本质上讲,这意味着@ExceptionHandler
方法通过消息转换(与视图分辨率或模板渲染)呈现给响应主体。启动时,
@RequestMapping
和@ExceptionHandler
方法的基础结构类将检测@ControllerAdvice
类型的 Spring bean,并在运行时应用其方法。全局@ExceptionHandler
方法(来自@ControllerAdvice
)在 本地方法(来自@Controller
)之后 应用。相比之下,全局@ModelAttribute
和@InitBinder
方法在**本地方法之前被应用。默认情况下,
@ControllerAdvice
方法适用于每个请求(即所有控制器),但是您可以通过 Comments 中的属性将其缩小到控制器的子集,如以下示例所示:
// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
前面的 selectors 会在运行时进行评估,如果广泛使用它们,可能会对性能产生负面影响。有关更多详细信息,请参见@ControllerAdvice javadoc。
Spring WebFlux 包含 WebFlux.fn,这是一个轻量级的函数编程模型,其中的函数用于路由和处理请求,而契约则是为不变性而设计的。它是基于 Comments 的编程模型的替代方案,但可以在相同的Reactive Core基础上运行。
在 WebFlux.fn 中,HTTP 请求使用
HandlerFunction
处理:该函数接受ServerRequest
并返回延迟的ServerResponse
(即Mono
)。作为请求对象的请求都具有不可变的协定,这些协定为 JDK 8 提供了对 HTTP 请求和响应的友好访问。HandlerFunction
等效于基于 Comments 的编程模型中@RequestMapping
方法的主体。传入的请求会通过
RouterFunction
路由到处理函数,该函数接受ServerRequest
并返回延迟的HandlerFunction
(即Mono
)。当 Router 功能匹配时,返回处理程序功能。否则为空 Mono。RouterFunction
等效于@RequestMapping
Comments,但主要区别在于 Router 功能不仅提供数据,还提供行为。
RouterFunctions.route()
提供了一个 Router 构建器,可简化 Router 的创建过程,如以下示例所示:
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
并通过内置的server adapters安装:
RouterFunctions.toHttpHandler(RouterFunction)
RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)
大多数应用程序都可以通过 WebFlux Java 配置运行,请参阅运行服务器。
ServerRequest
和ServerResponse
是不可变的接口,它们提供 JDK 8 友好的 HTTP 请求和响应访问。请求和响应都对体流提供Reactive Streams背压。请求主体用 ReactorFlux
或Mono
表示。响应主体由任何响应流Publisher
表示,包括Flux
和Mono
。有关更多信息,请参见Reactive Libraries。
ServerRequest
提供对 HTTP 方法,URI,Headers 和查询参数的访问,而通过body
方法提供对正文的访问。以下示例将请求正文提取到
Mono
:
Mono<String> string = request.bodyToMono(String.class);
以下示例将主体提取到
Flux
,其中Person
对象是从某种序列化形式(例如 JSON 或 XML)解码的:
Flux<Person> people = request.bodyToFlux(Person.class);
前面的示例是使用更通用的
ServerRequest.body(BodyExtractor)
的快捷方式,该ServerRequest.body(BodyExtractor)
接受BodyExtractor
功能策略界面。Util 类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 的形式访问 Multipart 数据:
Mono<MultiValueMap<String, Part> map = request.body(BodyExtractors.toMultipartData());
下面的示例演示如何以流方式一次访问多个部分:
Flux<Part> parts = request.body(BodyExtractos.toParts());
ServerResponse
提供对 HTTP 响应的访问,并且由于它是不可变的,因此可以使用build
方法来创建它。您可以使用构建器来设置响应状态,添加响应标题或提供正文。以下示例使用 JSON 内容创建 200(确定)响应:
Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
下面的示例演示如何构建带有
Location
Headers 且不包含主体的 201(已创建)响应:
URI location = ...
ServerResponse.created(location).build();
根据所使用的编解码器,可以传递提示参数来自定义正文的序列化或反序列化方式。例如,要指定Jackson JSON 视图:
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
我们可以将处理程序函数编写为 lambda,如以下示例所示:
HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().body(fromObject("Hello World"));
这很方便,但是在应用程序中我们需要多个功能,并且多个内联 lambda 可能会变得凌乱。因此,将相关的处理程序功能分组到一个处理程序类中很有用,该类在与基于 Comments 的应用程序中的作用与
@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;
}
//(1 = listPeople是一个处理函数,它以 JSON 格式返回存储库中找到的所有Person对象。)
public Mono<ServerResponse> listPeople(ServerRequest request) {
Flux<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people, Person.class);
}
// (2 = createPerson是一个处理函数,用于存储请求正文中包含的新Person。注意PersonRepository.savePerson(Person)返回Mono:当从请求中读取并存储此人时,空Mono发出完成 signal。因此,当收到完成 signal 时(即,保存Person时),我们使用build(Publisher)方法发送响应。)
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class);
return ok().build(repository.savePerson(person));
}
// (3 = getPerson是一个处理函数,它返回一个由id path 变量标识的人。我们从存储库中检索该Person,并创建一个 JSON 响应(如果找到)。如果找不到,我们使用switchIfEmpty(Mono)返回 404 Not Found 响应。)
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());
}
}
功能端点可以使用 Spring 的验证工具将验证应用于请求正文。例如,给定一个自定义 Spring Validator实现
Person
:
public class PersonHandler {
private final Validator validator = new PersonValidator(); // 1-创建Validator实例。
// ...
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate); // 2-应用验证。
return ok().build(repository.savePerson(person));
}
private void validate(Person person) {
Errors errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
throw new ServerWebInputException(errors.toString()); // 3-引发 400 响应的异常。
}
}
}
处理程序还可以通过创建和注入
Validator
基于LocalValidatorFactoryBean
. 请参阅弹簧验证。
Router 功能用于将请求路由到相应的
HandlerFunction
。通常,您不是自己编写 Router 功能,而是使用RouterFunctions
实用工具类上的方法创建一个。RouterFunctions.route()
(无参数)为您提供了流畅的生成器来创建 Router 功能,而RouterFunctions.route(RequestPredicate, HandlerFunction)
提供了直接的方式来创建 Router。通常,建议使用
route()
构建器,因为它为典型的 Map 方案提供了便捷的快捷方式,而无需发现静态导入。例如,Router 功能构建器提供了GET(String, HandlerFunction)
方法来为 GET 请求创建 Map;和POST(String, HandlerFunction)
(用于 POST)。除了基于 HTTP 方法的 Map 外,路由构建器还提供了一种在 Map 到请求时引入其他谓词的方法。对于每个 HTTP 方法,都有一个以
RequestPredicate
作为参数的重载变体,但是可以表示其他约束。
您可以编写自己的
RequestPredicate
,但是RequestPredicates
Util 类根据请求路径,HTTP 方法,Content Type 等提供常用的实现。以下示例使用请求谓词基于Accept
Headers 创建约束:
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
并将其与accept
谓词组合在一起。
Router 功能按 Sequences 评估:如果第一个路由不匹配,则评估第二个路由,依此类推。因此,在通用路由之前声明更具体的路由是有意义的。请注意,此行为不同于基于 Comments 的编程模型,在该模型中,将自动选择“最特定”的控制器方法。
使用 Router 功能生成器时,所有定义的路由都组成一个
RouterFunction
,从RouterFunction
返回。还有其他方法可以将多个 Router 功能组合在一起:
add(RouterFunction)
在RouterFunctions.route()
构建器上RouterFunction.and(RouterFunction)
RouterFunction.andRoute(RequestPredicate, HandlerFunction)
—嵌套RouterFunctions.route()
的RouterFunction.and()
的快捷方式。以下示例显示了四种 Route 的组成:
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) //(1 - 具有与 JSON 匹配的AcceptHeaders 的GET /person/{id}被路由到PersonHandler.getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople) //(2 -具有与 JSON 匹配的AcceptHeaders 的GET /person被路由到PersonHandler.listPeople)
.POST("/person", handler::createPerson) //(3 - POST /person没有其他谓词被 Map 到PersonHandler.createPerson,并且)
.add(otherRoute) // (4 - otherRoute是在其他地方创建并添加到所构建路由的 Router 功能)
.build();
一组 Router 功能通常具有共享谓词,例如共享路径。在上面的示例中,共享谓词将是与其中三个路由使用的
/person
匹配的路径谓词。使用 Comments 时,您可以通过使用 Map 到/person
的类型级别@RequestMapping
Comments 来删除此重复项。在 WebFlux.fn 中,可以通过 Router 功能构建器上的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();
请注意,
path
的第二个参数是使用 Router 构建器的使用者。尽管基于路径的嵌套是最常见的,但是您可以通过使用构建器上的
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 服务器中运行 Router 功能?一个简单的选项是使用以下方法之一将 Router 功能转换为
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
调用 Map 到请求的HandlerFunction
。ServerResponseResultHandler
:通过调用ServerResponse
的writeTo
方法来处理HandlerFunction
调用的结果。前面的组件使功能端点适合
DispatcherHandler
请求处理生命周期,并且(如果有)声明的控制器也可以(可能)与带 Comments 的控制器并排运行。这也是 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
方法来过滤处理程序函数。使用 Comments,可以通过使用@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) // (1 = 添加自定义请求 Headers 的before过滤器仅应用于两条 GET 路由。)
.header("X-RequestHeader", "Value")
.build()))
.POST("/person", handler::createPerson))
.after((request, response) -> logResponse(response)) ( // 2 = 记录响应的after过滤器应用于所有路由,包括嵌套路由。)
.build();
Router 构建器上的
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)
是可选的。当允许访问时,我们仅允许执行处理函数。除了在 Router 功能构建器上使用
filter
方法之外,还可以通过RouterFunction.filter(HandlerFilterFunction)
将过滤器应用于现有 Router 功能。通过专用的CorsWebFilter为功能端点提供 CORS 支持。
与 Spring MVC 中的使用方式相同
Spring WebFlux 使您可以处理 CORS(跨源资源共享)。本节介绍如何执行此操作。
与 Spring MVC 中的相同
出于安全原因,浏览器禁止 AJAX 调用当前来源以外的资源。例如,您可以将您的银行帐户放在一个标签中,将 evil.com 放在另一个标签中。来自 evil.com 的脚本不能使用您的凭据向您的银行 API 发出 AJAX 请求。例如,从您的帐户中提取资金!
跨域资源共享(CORS)是由most browsers实现的W3C specification,它使您可以指定授权哪种类型的跨域请求,而不是使用基于 IFRAME 或 JSONP 的安全性较低且功能较弱的变通办法。
与 Spring MVC 中的相同
CORS 规范区分飞行前,简单和实际要求。要了解 CORS 的工作原理,您可以阅读this article,或者阅读规范以获得更多详细信息。
Spring WebFlux
HandlerMapping
实现为 CORS 提供内置支持。成功将请求 Map 到处理程序后,HandlerMapping
检查给定请求和处理程序的 CORS 配置,并采取进一步的措施。飞行前请求直接处理,而简单和实际的 CORS 请求被拦截,验证并设置了所需的 CORS 响应 Headers。为了启用跨域请求(即
Origin
Headers 存在并且与请求的主机不同),您需要具有一些显式声明的 CORS 配置。如果找不到匹配的 CORS 配置,则飞行前请求将被拒绝。没有将 CORSHeaders 添加到简单和实际 CORS 请求的响应中,因此,浏览器拒绝了它们。每个
HandlerMapping
可以分别是configured,并具有基于 URL 模式的CorsConfiguration
Map。在大多数情况下,应用程序使用 WebFlux Java 配置声明此类 Map,从而导致将单个全局 Map 传递给所有HadlerMappping
实现。您可以将
HandlerMapping
级别的全局 CORS 配置与更细粒度的处理程序级别的 CORS 配置结合使用。例如,带 Comments 的控制器可以使用类或方法级别的@CrossOrigin
Comments(其他处理程序可以实现CorsConfigurationSource
)。整体配置和局部配置的组合规则通常是相加的,例如,所有全局和所有本地来源。对于只能接受单个值的那些属性(例如
allowCredentials
和maxAge
),局部变量将覆盖全局值。有关更多详细信息,请参见CorsConfiguration#combine(CorsConfiguration)。要从源中了解更多信息或进行高级自定义,请参阅:
CorsConfiguration
CorsProcessor
和DefaultCorsProcessor
AbstractHandlerMapping
与 Spring MVC 中的相同
@CrossOriginComments 启用带 Comments 的控制器方法上的跨域请求,如以下示例所示:
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin
@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}
}
默认情况下,
@CrossOrigin
允许:
默认情况下未启用
allowedCredentials
,因为它构建了一个信任级别,该级别公开了敏感的用户特定信息(例如 cookie 和 CSRF 令牌),仅在适当的地方使用。
maxAge
设置为 30 分钟。
@CrossOrigin
在类级别也受支持,并且被所有方法继承。下面的示例指定一个特定的域并将maxAge
设置为一个小时:
@CrossOrigin(origins = "http://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}
}
您可以在类和方法级别上都使用
@CrossOrigin
,如以下示例所示:
@CrossOrigin(maxAge = 3600) // ( 1=在类使用@CrossOrigin。)
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin("http://domain2.com") // (2 = 在方法级别使用@CrossOrigin)
@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}
}
与 Spring MVC 中的相同
除了细粒度的控制器方法级配置之外,您可能还想定义一些全局 CORS 配置。您可以在任何
HandlerMapping
上分别设置基于 URL 的CorsConfiguration
Map。但是,大多数应用程序都使用 WebFlux Java 配置来执行此操作。默认情况下,全局配置启用以下功能:
GET
,HEAD
和POST
方法。默认情况下未启用
allowedCredentials
,因为它构建了一个信任级别,该级别公开了敏感的用户特定信息(例如 cookie 和 CSRF 令牌),仅在适当的地方使用。
maxAge
设置为 30 分钟。要在 WebFlux Java 配置中启用 CORS,可以使用
CorsRegistry
回调,如以下示例所示:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600);
// Add more mappings...
}
}
与 Spring MVC 中的相同
您可以通过内置的CorsWebFilter来应用 CORS 支持,这很适合functional endpoints。
要配置过滤器,可以声明一个
CorsWebFilter
bean 并将CorsConfigurationSource
传递给其构造函数,如以下示例所示:
@Bean
CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// Possibly...
// config.applyPermitDefaultValues()
config.setAllowCredentials(true);
config.addAllowedOrigin("http://domain1.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
与 Spring MVC 中的相同
Spring Security项目为保护 Web 应用程序免受恶意攻击提供支持。请参阅 Spring Security 参考文档,包括:
与 Spring MVC 中的相同
Spring WebFlux 中视图技术的使用是可插入的。是否决定使用 Thymeleaf,FreeMarker 或其他某种视图技术,主要取决于配置更改。本章介绍与 Spring WebFlux 集成的视图技术。我们假设您已经熟悉View Resolution。
与 Spring MVC 中的相同
HTTP 缓存可以显着提高 Web 应用程序的性能。 HTTP 缓存围绕
Cache-Control
响应 Headers 和后续的条件请求 Headers(例如Last-Modified
和ETag
)展开。Cache-Control
建议私有(例如浏览器)和公共(例如代理)缓存如何缓存和重复使用响应。ETag
Headers 用于发出条件请求,如果内容未更改,则可能导致没有主体的 304(NOT_MODIFIED)。ETag
可以看作是Last-Modified
Headers 的更复杂的后继者。本节描述了 Spring WebFlux 中可用的 HTTP 缓存相关选项。
与 Spring MVC 中的相同
CacheControl支持配置与
Cache-Control
Headers 相关的设置,并且在许多地方都作为参数接受:
RFC 7234描述了Cache-Control
响应 Headers 的所有可能的指令,而CacheControl
类型采用面向用例的方法,该方法着重于常见方案,如以下示例所示:
// Cache for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);
// Prevent caching - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();
// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();
与 Spring MVC 中的相同
控制器可以添加对 HTTP 缓存的显式支持。我们建议这样做,因为需要先计算资源的
lastModified
或ETag
值,然后才能将其与条件请求 Headers 进行比较。控制器可以将ETag
和Cache-Control
设置添加到ResponseEntity
,如以下示例所示:
@GetMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {
Book book = findBook(id);
String version = book.getVersion();
return ResponseEntity
.ok()
.cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
.eTag(version) // lastModified is also available
.body(book);
}
如果与条件请求 Headers 的比较表明内容未更改,则前面的示例发送带有空主体的 304(NOT_MODIFIED)响应。否则,
ETag
和Cache-Control
Headers 将添加到响应中。您还可以在控制器中针对条件请求 Headers 进行检查,如以下示例所示:
@RequestMapping
public String myHandleMethod(ServerWebExchange exchange, Model model) {
long eTag = ... (1 特定于应用程序的计算)
if (exchange.checkNotModified(eTag)) {
return null; (2 响应已设置为 304(NOT_MODIFIED)。无需进一步处理)
}
model.addAttribute(...); (3 continue 进行请求处理)
return "myViewName";
}
可以使用三种变体来检查针对
eTag
值和lastModified
值或两者的条件请求。对于有条件的GET
和HEAD
请求,可以将响应设置为 304(NOT_MODIFIED)。对于条件POST
,PUT
和DELETE
,您可以改为将响应设置为 409(PRECONDITION_FAILED)以防止并发修改。
与 Spring MVC 中的相同
您应该为静态资源提供
Cache-Control
和条件响应 Headers,以实现最佳性能。请参阅有关配置Static Resources的部分。
1