SpringWebFlux-3(请求数据获取学习笔记2021.11.10)

SpringWebFlux -3(5.3.12版学习笔记2021.11.10)

1.4.4 Model (模型)

与 Spring MVC 中的使用方式相同

您可以使用@ModelAttribute注解:

  • 在method argument in @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 在模型中显式支持响应类型(例如Monoio.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;
}

1.4.5 DataBinder (数据绑定)

与 Spring MVC 中的使用相同

@Controller@ControllerAdvice类可以具有@InitBinder方法,以初始化WebDataBinder的实例。这些依次用于:

  • 将请求参数(即表单数据或查询)绑定到模型对象。
  • 将基于String的请求值(例如请求参数,路径变量,Headers,Cookie 等)转换为控制器方法参数的目标类型。
  • 呈现 HTML 表单时,将模型对象的值格式化为String值。

@InitBinder个方法可以注册特定于控制器的java.bean.PropertyEditor或 Spring ConverterFormatter组件。此外,您可以使用WebFlux Java 配置在全局共享的FormattingConversionService中注册ConverterFormatter类型。

@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)
}

1.4.6 Management (管理异常)

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

REST API 异常

与 Spring MVC 中的相同

REST 服务的常见要求是在响应正文中包含错误详细信息。 Spring 框架不会自动这样做,因为响应主体中错误详细信息的表示是特定于应用程序的。但是,@RestController可以使用具有ResponseEntity返回值的@ExceptionHandler方法来设置响应的状态和主体。也可以在@ControllerAdvice类中声明此类方法以将其全局应用。

请注意,Spring WebFlux 与 Spring MVC ResponseEntityExceptionHandler不具有等效项,因为 WebFlux 仅引发ResponseStatusException(或其子类),并且不需要将其转换为 HTTP 状态代码。

1.4.7 Controller Advice(控制器通知)

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

1.5. Functional Endpoints (功能端点)

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

1.5.1. Overview

在 WebFlux.fn 中,HTTP 请求使用HandlerFunction处理:该函数接受ServerRequest并返回延迟的ServerResponse(即Mono)。作为请求对象的请求都具有不可变的协定,这些协定为 JDK 8 提供了对 HTTP 请求和响应的友好访问。 HandlerFunction等效于基于 Comments 的编程模型中@RequestMapping方法的主体。

传入的请求会通过RouterFunction路由到处理函数,该函数接受ServerRequest并返回延迟的HandlerFunction(即Mono)。当 Router 功能匹配时,返回处理程序功能。否则为空 Mono。 RouterFunction等效于@RequestMappingComments,但主要区别在于 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 配置运行,请参阅运行服务器。

1.5.2 HandlerFunction (处理函数)

ServerRequestServerResponse是不可变的接口,它们提供 JDK 8 友好的 HTTP 请求和响应访问。请求和响应都对体流提供Reactive Streams背压。请求主体用 Reactor FluxMono表示。响应主体由任何响应流Publisher表示,包括FluxMono。有关更多信息,请参见Reactive Libraries。

ServerRequest

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

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

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

下面的示例演示如何构建带有LocationHeaders 且不包含主体的 201(已创建)响应:

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

根据所使用的编解码器,可以传递提示参数来自定义正文的序列化或反序列化方式。例如,要指定Jackson JSON 视图:

ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);

Handler Classes

我们可以将处理程序函数编写为 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());
    }
}

Validation

功能端点可以使用 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. 请参阅弹簧验证。

1.5.3 RouterFunction

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作为参数的重载变体,但是可以表示其他约束。

Predicates

您可以编写自己的RequestPredicate,但是RequestPredicatesUtil 类根据请求路径,HTTP 方法,Content Type 等提供常用的实现。以下示例使用请求谓词基于AcceptHeaders 创建约束:

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谓词组合在一起。

Routes

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

Nested Routes

一组 Router 功能通常具有共享谓词,例如共享路径。在上面的示例中,共享谓词将是与其中三个路由使用的/person匹配的路径谓词。使用 Comments 时,您可以通过使用 Map 到/person的类型级别@RequestMappingComments 来删除此重复项。在 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 谓词形式的重复项。通过结合使用nestaccept可以进一步改进:

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 服务器中运行 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:通过调用ServerResponsewriteTo方法来处理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...
    }
}

1.5.5 Filtering Handler Function (过滤处理程序功能)

您可以使用路由功能构建器上的beforeafterfilter方法来过滤处理程序函数。使用 Comments,可以通过使用@ControllerAdviceServletFilter或同时使用两者来实现类似的功能。该过滤器将应用于构建器构建的所有路由。这意味着在嵌套路由中定义的过滤器不适用于“顶级”路由。例如,考虑以下示例:

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:此函数采用ServerRequestHandlerFunction并返回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 支持。

1.6. URI 组件 (可用于准备 URI 的各种选项看官网)

1.7 CORS (跨域)

与 Spring MVC 中的使用方式相同

Spring WebFlux 使您可以处理 CORS(跨源资源共享)。本节介绍如何执行此操作。

1.7.1. 介绍

与 Spring MVC 中的相同

出于安全原因,浏览器禁止 AJAX 调用当前来源以外的资源。例如,您可以将您的银行帐户放在一个标签中,将 evil.com 放在另一个标签中。来自 evil.com 的脚本不能使用您的凭据向您的银行 API 发出 AJAX 请求。例如,从您的帐户中提取资金!

跨域资源共享(CORS)是由most browsers实现的W3C specification,它使您可以指定授权哪种类型的跨域请求,而不是使用基于 IFRAME 或 JSONP 的安全性较低且功能较弱的变通办法。

1.7.2. Processing (处理)

与 Spring MVC 中的相同

CORS 规范区分飞行前,简单和实际要求。要了解 CORS 的工作原理,您可以阅读this article,或者阅读规范以获得更多详细信息。

Spring WebFlux HandlerMapping实现为 CORS 提供内置支持。成功将请求 Map 到处理程序后,HandlerMapping检查给定请求和处理程序的 CORS 配置,并采取进一步的措施。飞行前请求直接处理,而简单和实际的 CORS 请求被拦截,验证并设置了所需的 CORS 响应 Headers。

为了启用跨域请求(即OriginHeaders 存在并且与请求的主机不同),您需要具有一些显式声明的 CORS 配置。如果找不到匹配的 CORS 配置,则飞行前请求将被拒绝。没有将 CORSHeaders 添加到简单和实际 CORS 请求的响应中,因此,浏览器拒绝了它们。

每个HandlerMapping可以分别是configured,并具有基于 URL 模式的CorsConfigurationMap。在大多数情况下,应用程序使用 WebFlux Java 配置声明此类 Map,从而导致将单个全局 Map 传递给所有HadlerMappping实现。

您可以将HandlerMapping级别的全局 CORS 配置与更细粒度的处理程序级别的 CORS 配置结合使用。例如,带 Comments 的控制器可以使用类或方法级别的@CrossOriginComments(其他处理程序可以实现CorsConfigurationSource)。

整体配置和局部配置的组合规则通常是相加的,例如,所有全局和所有本地来源。对于只能接受单个值的那些属性(例如allowCredentialsmaxAge),局部变量将覆盖全局值。有关更多详细信息,请参见CorsConfiguration#combine(CorsConfiguration)。

要从源中了解更多信息或进行高级自定义,请参阅:

  • CorsConfiguration
  • CorsProcessorDefaultCorsProcessor
  • AbstractHandlerMapping

1.7.3. @CrossOrigin

与 Spring MVC 中的相同

@CrossOriginComments 启用带 Comments 的控制器方法上的跨域请求,如以下示例所示:

@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin
    @GetMapping("/{id}")
    public Mono<Account> retrieve(@PathVariable Long id) {
        // ...
    }
}

默认情况下,@CrossOrigin允许:

  • All origins.
  • All headers.
  • 控制器方法 Map 到的所有 HTTP 方法。

默认情况下未启用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) {
        // ...
    }
}

1.7.4. 全局配置

与 Spring MVC 中的相同

除了细粒度的控制器方法级配置之外,您可能还想定义一些全局 CORS 配置。您可以在任何HandlerMapping上分别设置基于 URL 的CorsConfigurationMap。但是,大多数应用程序都使用 WebFlux Java 配置来执行此操作。

默认情况下,全局配置启用以下功能:

  • All origins.
  • All headers.
  • GETHEADPOST方法。

默认情况下未启用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...
    }
}

1.7.5. CORS WebFilter

与 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);
}

1.8. 网络安全

与 Spring MVC 中的相同

Spring Security项目为保护 Web 应用程序免受恶意攻击提供支持。请参阅 Spring Security 参考文档,包括:

  • WebFlux Security
  • WebFlux 测试支持
  • CSRF Protection
  • 安全响应 Headers

1.9. 视图技术 (官网)

与 Spring MVC 中的相同

Spring WebFlux 中视图技术的使用是可插入的。是否决定使用 Thymeleaf,FreeMarker 或其他某种视图技术,主要取决于配置更改。本章介绍与 Spring WebFlux 集成的视图技术。我们假设您已经熟悉View Resolution。

1.10. HTTP 缓存

与 Spring MVC 中的相同

HTTP 缓存可以显着提高 Web 应用程序的性能。 HTTP 缓存围绕Cache-Control响应 Headers 和后续的条件请求 Headers(例如Last-ModifiedETag)展开。 Cache-Control建议私有(例如浏览器)和公共(例如代理)缓存如何缓存和重复使用响应。 ETagHeaders 用于发出条件请求,如果内容未更改,则可能导致没有主体的 304(NOT_MODIFIED)。 ETag可以看作是Last-ModifiedHeaders 的更复杂的后继者。

本节描述了 Spring WebFlux 中可用的 HTTP 缓存相关选项。

1.10.1. CacheControl

与 Spring MVC 中的相同

CacheControl支持配置与Cache-ControlHeaders 相关的设置,并且在许多地方都作为参数接受:

  • Controllers
  • Static Resources

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

1.10.2. Controllers

与 Spring MVC 中的相同

控制器可以添加对 HTTP 缓存的显式支持。我们建议这样做,因为需要先计算资源的lastModifiedETag值,然后才能将其与条件请求 Headers 进行比较。控制器可以将ETagCache-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)响应。否则,ETagCache-ControlHeaders 将添加到响应中。

您还可以在控制器中针对条件请求 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值或两者的条件请求。对于有条件的GETHEAD请求,可以将响应设置为 304(NOT_MODIFIED)。对于条件POSTPUTDELETE,您可以改为将响应设置为 409(PRECONDITION_FAILED)以防止并发修改。

1.10.3. 静态资源

与 Spring MVC 中的相同

您应该为静态资源提供Cache-Control和条件响应 Headers,以实现最佳性能。请参阅有关配置Static Resources的部分。

1

你可能感兴趣的:(响应式框架,mvc,spring,java)