1. 程式人生 > 實用技巧 >Spring WebFlux

Spring WebFlux

1.1 簡介

之前的spring 框架體系中的Web MVC框架的主要目的是構建關於Servlet和Servlet 容器的。在反應式技術棧中的web框架,Spring Flux 是在5.0以後加入的spring體系中的。它是完全無阻塞、支援背壓框架Reactive Streams, 執行在Netty/Undertow 和 Servlet 3.1+的容器伺服器上。

spring-webmvc和spring-webflux在spring 框架中都是都是存在的。每個都是可選的。應用可以選擇其中的一個使用,或者兩個都用,比如可以使用反應式的webclient的MVC controllers 。

1.1.1 為什麼作為新的web 框架?

部分原因是需要一個非阻塞的可以使用較少的執行緒處理高併發,需要較少的硬體資源就可以進行擴充套件的框架。Servlet 3.1 已經針對非阻塞I/O提供了API。但是,使用這個會導致Servlet API的其餘部分是同步的(比如Filter、Servlet)或者阻塞的(比如getParameter,getPart等 )。新增新的公共API的動機就是讓非阻塞橫跨整個執行時。這一點很重要,因為諸如Netty這樣的伺服器已經在非同步,非阻塞空間中很好地建立起來了。

另一部分原因是函數語言程式設計。就像java 5新增的註解功能讓我們可以通過註解實現REST controller 或者 測試用例一樣,java 8新增的lambda表示式為函式式API創造了機會。對於非阻塞應用程式和延續式API來說,這是一個福音,正如CompletableFuture和ReactiveX所推廣的那樣,它允許宣告式組合非同步邏輯。 在程式設計模型層面,Java 8使Spring WebFlux能夠在帶註解的控制器的同時提供功能性的Web端點。

1.1.2 反應式:是什麼?為什麼?

我們談到非阻塞和函數語言程式設計,為什麼要反應式的,這是什麼意思呢?

術語"reactive"(響應式)是圍繞對變化做出反應而建立的程式設計模型。比如網路元件對I/O事件做出反應,UI控制器對滑鼠事件做出反應等。從這個意義上說,非阻塞是被動的,因為不是被阻塞,而是當操作完成或資料可用時,我們現在處於對通知作出反應的模式。

還有另外一個比較重要的機制就是,spring 團隊提供的反應式是非阻塞的背壓處理。在同步情況下,命令式的程式碼中,服務端會阻塞呼叫者強制等待。 在非阻塞程式碼中,控制事件發生率非常重要,以便快速生產者不會壓跨伺服器。

Reactive Streams是一個小規格,也在Java 9中採用,它定義了背壓非同步元件之間的互動。例如,資料儲存庫(作為釋出伺服器)可以生成資料,以便作為訂閱伺服器的HTTP伺服器可以寫入響應。 Reactive Streams的主要目的是允許使用者控制釋出者產生資料的速度。

常見問題:如果釋出者不能降低速度怎麼辦?反應流的目的只是建立機制和邊界。 如果釋出商不能放慢速度,那麼它必須決定是否緩衝,丟棄或失敗。

1.1.3 反應式API

反應流在互操作性方面發揮著重要作用。 它對依賴包和基礎設施元件很有用,但作為應用程式API的用處不大,因為它太底層了。應用程式需要更高級別和更豐富的功能API來組成非同步邏輯 -,類似於Java 8 Stream API,但它不僅僅適用於集合。 這就是反應式庫發揮的作用。

Reactor是Spring WebFlux的反應性庫。 它提供了Mono和Flux API型別,通過與ReactiveX的豐富操作符來處理0..1和0..N的資料序列。 Reactor是一個Reactive Streams庫,因此它的所有操作員都支援非阻塞背壓。 Reactor強烈關注伺服器端Java。 它是與Spring密切合作開發的。

WebFlux需要Reactor作為核心依賴項,但它可以通過Reactive Streams與其他反應式庫進行互操作。作為一般規則,WebFlux API接受一個普通的Publisher作為輸入,在內部將它調整為Reactor型別,使用它們,然後返回Flux或Mono作為輸出。因此,您可以通過任何釋出作為輸入,並且可以對輸出進行操作,但您需要調整輸出以供其他反應式庫使用。只要可行,例如帶註解的控制器,WebFlux完全適應RxJava或其他反應性庫的使用。 有關更多詳細資訊,請參閱Reactive Libraries。

1.1.4 程式設計模型

Spring-Web模組包含Spring WebFlux的基礎,這些基礎包括HTTP抽象,受支援伺服器的Reactive Streams介面卡,編解碼器以及可與Servlet API相媲美但具有非阻塞協議的核心WebHandler API。

在此基礎上,Spring WebFlux提供了兩種程式設計模型供選擇:

  • Annotated Controllers 與Spring MVC一樣,並基於spring-web模組中的相同註解。 Spring MVC和WebFlux控制器都支援響應式(Reactor,RxJava)返回型別,因此很難區分它們。 一個顯著的區別是WebFlux也支援被動的@RequestBody引數。
  • Functional Endpoints 基於lambda表示式,輕量級,函數語言程式設計模型。把它想象成一個小型庫或應用程式可以用來路由和處理請求的一組實用程式。 與Annotated Controllers的最大區別在於前者應用程式負責從頭到尾的請求處理,後者通過註解宣告意圖並被回撥。

1.1.5 選擇一個web框架

你應該使用Spring MVC還是WebFlux? 我們來介紹一些不同的觀點。

如果你有個spring MVC的應用程式工作的很好,沒必要修改。指令式程式設計是最容易編寫、理解和debug的程式碼。由於歷來都是阻塞是程式設計,所以依賴包有非常多的選擇。

如果你已經開始了非阻塞的web方式,那麼spring WebFlux提供了與其他在這個領域的框架一樣的執行模式優勢,並且提供了多種伺服器選擇,如Netty/tomcat/Jetty/Undertow,Servlet3.1+容器。程式設計模式的選擇,註解控制器和函式式web端點,反應式依賴包選擇,Reactor、RxJava 或者其他的。

如果你對輕量級,使用java 8的lambdas表示式或者Kotlin的函式式web框架感興趣,那麼請使用Spring WebFlux 函式式web端點。對於較小的應用程式或微服務來說,這也可能是一個不錯的選擇,這些應用程式或複雜的需求可以從更高的透明度和控制中受益。

在微服務體系結構中,您可以將應用程式與Spring MVC或Spring WebFlux控制器或Spring WebFlux函式式端點混合使用。 在兩個框架中支援相同的基於註解的程式設計模型使得重用知識變得更加容易,同時也為正確的工作選擇正確的工具。

評估應用程式的簡單方法是檢查它的依賴關係。 如果您使用了阻塞API,例如永續性API(JPA,JDBC)或網路操作API,那麼Spring MVC至少是常見體系結構的最佳選擇。 使用Reactor和RxJava在單獨的執行緒上執行阻塞呼叫在技術上是可行的,但是您不會充分利用非阻塞的Web棧。

如果您有一個呼叫遠端服務的Spring MVC應用程式,請嘗試使用反應式WebClient。 您可以直接從Spring MVC控制器方法返回反應型別(Reactor,RxJava或其他)。 每次呼叫的延時越長,或者呼叫間的相互依賴性越強,這個作用越明顯。 Spring MVC控制器也可以呼叫其他反應式元件。

如果你有一個龐大的團隊,記住轉向非阻塞,函式式和宣告式程式設計的陡峭的學習曲線。 不用完全切換到使用反應式WebClient。 除此之外,開始小範圍使用,並衡量它的好處。 我們預計,對於廣泛的應用來說,這種轉變是不必要的。

如果您不確定有哪些好處,請先了解非阻塞I / O如何工作(例如Node.js,單執行緒與併發不矛盾)及其影響。它的標籤是“以較少硬體進行伸縮”,但不能保證這種效果,並非沒有一些網路I / O可能很慢或不可預測。 這個Netflix部落格文章是一個很好的資源。

1.1.6 選擇一個伺服器

Netty,Undertow,Tomcat,Jetty和Servlet 3.1+容器支援Spring WebFlux。 每個伺服器都適用於通用的Reactive Streams API。 Spring WebFlux程式設計模型建立在該通用API上。

常見問題:Tomcat和Jetty 怎麼在兩種方式下都能用?Tomcat和Jetty的核心是非阻塞的。 它是添加了阻塞門面的Servlet API。 從版本3.1開始,Servlet API為非阻塞I / O添加了一個選項。 然而,其使用需要注意避免其他同步和阻塞部分。 由於這個原因,Spring的反應式Web棧有一個底層的Servlet介面卡來連線到Reactive Streams,但是Servlet API不能直接使用。

Spring Boot 2在WebFlux中預設使用Netty,因為Netty在非同步,非阻塞領域中得到了更廣泛的應用,並且還提供了可共享資源的客戶端和伺服器。 通過比較,Servlet 3.1非阻塞I / O沒有太多用處,因為使用它的門檻有點高。 Spring WebFlux開啟了一條實用的應用途徑。

Spring Boot中的預設伺服器選擇主要關於開箱即用體驗。 應用程式仍然可以選擇任何其他受支援的伺服器,這些伺服器還針對性能進行了高度優化,完全無阻塞,並適用於反應式背壓。 在Spring Boot中,切換很容易。

1.1.7 效能與可伸縮

效能有很多特點和意義。 反應式和非阻塞通常不會使應用程式執行得更快。 在某些情況下可以,例如,使用WebClient並行執行遠端呼叫。 總的來說,它需要更多的工作來完成非阻塞方式,並且可以稍微增加所需的處理時間。

反應式和非阻塞的關鍵預期好處是能夠使用少量固定數量的執行緒和較少的記憶體進行擴充套件。 這使得應用程式在負載下更具彈性,因為它們以更可預測的方式進行擴充套件。 為了觀察這些好處,您需要有一些延遲,包括緩慢和不可預知的網路I / O。 這就是反應式開始顯示其優勢的地方,差異可能非常大。

1.2 Reactive Spring web

spring-web模組提供底層基礎設施和HTTP抽象 - 客戶端和伺服器,以構建反應式Web應用程式。 所有的公共API都是以Reactor作為後臺實現來構建的。

伺服器支援分為兩層:

  • HttpHandler 和 服務介面卡 含有反應式背壓的處理HTTP請求的最基礎的、公共API
  • WebHandler API  稍高的級別,但仍然是通用伺服器Web API,具有過濾器鏈式處理。

1.2.1 HttpHandler

每個HTTP伺服器都有一些用於HTTP請求處理的API。 HttpHandler是一種處理請求和響應的簡單協議。 它是故意進行了最小化。 其主要目的是為不同伺服器上的HTTP請求處理提供基於Reactive Streams的通用API。

spring-web模組包含的對每種支援的伺服器的介面卡。下面的這張表顯示了使用了哪些伺服器API和ReactiveStreams的支援來自哪裡:

伺服器名稱APIReactiveStreams支援
Netty Netty API Reactor Netty
Undertow Undertow API spring-web: Undertow to Reactive Streams bridge
Tomcat Servlet 3.1 non-blocking I/O; Tomcat API to read and write ByteBuffers vs byte[] spring-web: Servlet 3.1 non-blocking I/O to Reactive Streams bridge
Jetty Servlet 3.1 non-blocking I/O; Jetty API to write ByteBuffers vs byte[] spring-web: Servlet 3.1 non-blocking I/O to Reactive Streams bridge
Servlet 3.1 container Servlet 3.1 non-blocking I/O spring-web: Servlet 3.1 non-blocking I/O to Reactive Streams bridge

下面是一些針對各個伺服器的必要的依賴、支援的版本和程式碼片段。

Server nameGroup idArtifact name
Reactor Netty io.projectreactor.ipc reactor-netty
Undertow io.undertow undertow-core
Tomcat org.apache.tomcat.embed tomcat-embed-core
Jetty org.eclipse.jetty jetty-server, jetty-servlet
  • Reactor Netty
HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create(host, port).newHandler(adapter).block();
  • Undertow
HttpHandler handler = ...
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();
  • Tomcat
HttpHandler handler = ...
Servlet servlet = new TomcatHttpHandlerAdapter(handler);

Tomcat server = new Tomcat();
File base = new File(System.getProperty("java.io.tmpdir"));
Context rootContext = server.addContext("", base.getAbsolutePath());
Tomcat.addServlet(rootContext, "main", servlet);
rootContext.addServletMappingDecoded("/", "main");
server.setHost(host);
server.setPort(port);
server.start();
  • Jetty
HttpHandler handler = ...
Servlet servlet = new JettyHttpHandlerAdapter(handler);

Server server = new Server();
ServletContextHandler contextHandler = new ServletContextHandler(server, "");
contextHandler.addServlet(new ServletHolder(servlet), "/");
contextHandler.start();

ServerConnector connector = new ServerConnector(server);
connector.setHost(host);
connector.setPort(port);
server.addConnector(connector);
server.start();

要將WAR部署為Servlet 3.1+容器,請使用ServletHttpHandlerAdapter包裝HttpHandler並將其註冊為Servlet。 這可以通過使用AbstractReactiveWebInitializer自動完成。

1.2.2 WebHandler API

HttpHandler是在不同的HTTP伺服器上執行的最底層的協議。 在這個基礎之上,WebHandler API提供了一個稍微高一點的,但仍然是通用的一組元件,這些元件構成了WebExceptionHandler,WebFilter和WebHandler的鏈。

所有WebHandler API元件都以ServerWebExchange作為輸入,在ServerHttpRequest和ServerHttpResponse之上,為Web應用程式提供額外的構建塊,例如請求屬性,會話屬性,對解析表單資料的訪問,檔案上傳等等。

WebHttpHandlerBuilder用於組裝請求處理鏈。 您可以使它的方法來手動新增元件,或者更有可能通過Spring ApplicationContext獲取它們,並通過伺服器介面卡執行HttpHandler準備好的結果。

ApplicationContext context = ...
HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context).build()

1.2.2.1 特殊的bean型別

下表列出了一些WebHttpHandlerBuilder可以從容器中獲取的元件:

Bean nameBean typeCountDescription
any WebExceptionHandler 0..N Exception handlers to apply after all WebFilter's and the target WebHandler.
any WebFilter 0..N Filters to invoke before and after the target WebHandler.
"webHandler" WebHandler 1 The handler for the request.
"webSessionManager" WebSessionManager 0..1 The manager for WebSession's exposed through a method on ServerWebExchange.DefaultWebSessionManager by default.
"serverCodecConfigurer" ServerCodecConfigurer 0..1 For access to HttpMessageReader's for parsing form data and multipart data that’s then exposed through methods on ServerWebExchange.ServerCodecConfigurer.create() by default.
"localeContextResolver" LocaleContextResolver 0..1 The resolver for LocaleContextexposed through a method on ServerWebExchange.AcceptHeaderLocaleContextResolverby default.

1.2.2.2 表單資料

ServerWebExchange 暴露了下面的方法,可以獲取表單資料

Mono<MultiValueMap<String, String>> getFormData();

DefaultServerWebExchange使用配置的HttpMessageReader將表單資料(“application / x-www-form-urlencoded”)解析為MultiValueMap。 預設情況下,FormHttpMessageReader被配置為通過ServerCodecConfigurer bean使用(請參閱Web Handler API)。

1.2.2.3 檔案上傳

ServerWebExchange 暴露了下面的方法可以獲取上傳檔案的資料:

Mono<MultiValueMap<String, Part>> getMultipartData();

DefaultServerWebExchange使用配置的HttpMessageReader <MultiValueMap <String,Part>將“multipart / form-data”內容解析為MultiValueMap。 目前Synchronoss NIO Multipart是唯一支援的第三方庫,也是我們唯一知道的用於非阻塞解析多部分請求的庫。 它通過ServerCodecConfigurer bean啟用(請參閱Web處理程式API)。

要以流方式解析多部分資料,請使用從HttpMessageReader 返回的Flux 。 例如,在註解控制器中,使用@RequestPart意味著按名稱對各個部分進行類似Map的訪問,因此需要完整地解析多部分資料。 相比之下,@RequestBody可用於將內容解碼到Flux ,而不會收集到MultiValueMap。

1.2.3 HTTP訊息編解碼

spring-web模組定義HttpMessageReader和HttpMessageWriter協議,通過Rective Streams Publisher's對HTTP請求和響應的主體進行編碼和解碼。 這些行為在客戶端使用,例如, 在WebClient中,在伺服器端,例如 在註解的控制器和功能端點中。

Spring-core模組定義了獨立於HTTP的Encoder和Decoder,並依賴於(如Netty ByteBuf和java.nio.ByteBuffer(請參閱資料緩衝區和編解碼器))的DataBuffer協議。 Encoder可以用EncoderHttpMessageWriter包裝以用作HttpMessageWriter,而Decoder可以用DecoderHttpMessageReader包裝以用作HttpMessageReader。

Spring-core模組包含byte [],ByteBuffer,DataBuffer,Resource和String的基本編碼器和解碼器實現。 Spring-Web模組為Jackson JSON,Jackson Smile和JAXB2增加了Encoder和Decoder。 Spring-Web模組還包含一些針對伺服器傳送事件,表單資料和多部分請求的特定於Web的readers 和writers 。

要配置或自定義readers 和writers ,通常會使用ClientCodecConfigurer或ServerCodecConfigurer。

1.2.3.1 Jackson

decoder依靠Jackson的非阻塞位元組陣列解析器將位元組塊流解析為TokenBuffer流,然後可以將其轉換為Jackson的ObjectMapper物件。 目前支援JSON和Smile(二進位制JSON)資料格式。

編碼Publisher<?> 的流程如下:

  • 如果Publisher是Mono(即單個值),則該值在可用時被編碼。
  • 如果media 型別是application/stream+json或者application/stream+x-jackson-smile則每個值都單獨編碼(中間使用新行)
  • 否則將使用Flux#collectToList()收集Publisher中的所有專案,並將生成的編碼集合轉為陣列。

作為上述規則的一個特例,ServerSentEventHttpMessageWriter將Publisher發出的項單獨作為Mono <?>提供給Jackson2JsonEncoder。

請注意,Jackson JSON編碼器和解碼器都明確不渲染String型別的元素。 相反,String被視為低階內容(即序列化的JSON),並由CharSequenceEncoder按原樣呈現。 如果你想把一個Flux 渲染成JSON陣列,你必須使用Flux#collectToList()並提供一個Mono <List >。

1.3 DispatcherHandler

像Spring MVC一樣,Spring WebFlux圍繞前端控制器模式進行設計,中央WebHandler(DispatcherHandler)為請求處理提供共享演算法,而實際工作由可配置的委託元件執行。 該模型非常靈活,支援多種工作流程。

DispatcherHandler通過Spring配置發現它需要的委託元件。 它也被設計成一個Spring bean,並實現ApplicationContextAware來訪問它所執行的上下文。如果DispatcherHandler是用bean名稱“webHandler”宣告的,它會被WebHttpHandlerBuilder發現,它將會像WebHandler API描述的那樣與請求處理鏈放在一起 。

WebFlux應用程式中的Spring配置通常包含:

  • 一個名稱為webHandler的DispatcherHandler型別的bean
  • WebFilter 和WebExceptionHandler beans
  • DispatcherHandler 特殊beans
  • 其他

WebHttpHandlerBuilder 將那個配置資訊進行構建處理鏈:

ApplicationContext context = ...
HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context);

至此HttpHandler已經準備好供伺服器介面卡使用。

1.3.1 特殊bean型別

DispatcherHandler委託特殊的bean來處理請求並呈現相應的響應。 “特殊bean”是指實現WebFlux框架協議的Spring管理的物件例項。 這些通常是內建的,但您可以自定義其屬性,然後進行擴充套件或替換。

下表是DispatcherHandler可以獲取到的特殊beans,注意,也有一些其他在底層可以獲取的bean。

Bean型別解釋說明
HandlerMapping 將請求對映到處理器。 該對映基於一些標準,其細節因HandlerMapping實現而異 - 註解的控制器,簡單的URL模式對映等。HandlerMapping的主要實現是用於@RequestMapping註解方法的RequestMappingHandlerMapping,用於功能性端點路由的RouterFunctionMapping以及用於顯式註冊URI路徑模式和WebHandler's的SimpleUrlHandlerMapping。
HandlerAdapter 幫助DispatcherHandler呼叫對映到請求的處理器,而不管實際如何呼叫處理程式。 例如呼叫一個帶註解的控制器需要解析註解。 HandlerAdapter的主要目的是遮蔽DispatcherHandler的細節。
HandlerResultHandler 處理 處理器的執行結果並且進行響應

1.3.2 WebFlux配置

應用程式可以宣告處理請求所需的Web Handler API 和DispatcherHandler下列出的基礎架構Bean。 但是在大多數情況下,WebFlux配置是最好的起點。 它宣告所需的bean並提供更高級別的配置回撥API來定製它。Spring Boot依靠WebFlux配置來配置Spring WebFlux,並且還提供了許多額外的方便選項。

1.3.3 Processing

DispatcherHandler 處理請求的流程如下:

  • 要求每個HandlerMapping 找到一個匹配的handler,並且使用第一個匹配的
  • 如果能找到一個handler,就通過相應的HandlerAdapter進行執行並且返回HandlerResult
  • 返回的HandlerResult被髮送給對應的HandlerResultHandler去完成處理過程,直接寫到一個用檢視選擇的響應裡。

1.3.4 Result Handling

當DispatcherHandler需要處理來自handler的返回值時,它會找到支援它並呼叫它的HandlerResultHandler。 下面列出了它們的預設順序(全部在WebFlux配置中宣告):

  • ResponseEntityResultHandler  處理通常從註解控制器返回的ResponseEntity返回值。 由於按型別安全地匹配返回值,因此該順序設定為0。
  • ServerResponseResultHandler  處理通常從函式式端點返回的ServerResponse返回值。 由於按型別安全地匹配返回值,因此該順序設定為0。
  • ResponseBodyResultHandler  處理通常從使用@RestController 註解的類的@ResponseBody 方法返回值。順序設定為100,即在結果處理器後面進行校驗特殊型別
  • ViewResolutionResultHandler  為HTML模板渲染執行檢視匹配演算法。 由於它支援多種特定型別,例如,String, Map, Rendering等,所以優先順序設定最低Ordered.LOWEST_PRECEDENCE,但也會將其他物件視為模型屬性。

1.3.5 View Resolution

View Resolution能夠使用HTML模板和資料呈現給瀏覽器,而無需將您繫結到特定的檢視技術。 在Spring WebFlux中,通過專用的HandlerResultHandler來支援檢視解析,HandlerResultHandler使用ViewResolver將表示邏輯檢視名稱的String對映到View例項。 該檢視然後用於呈現響應。

1.3.5.1 Handling

傳遞給ViewResolutionResultHandler的HandlerResult包含來自處理程式的返回值以及包含在請求處理過程中新增的屬性的模型。返回值將被處理成下面的一種:

  • String, CharSequence 一個邏輯檢視名,在ViewResolver配置的其中一個
  • void  根據請求路徑去掉前導斜槓和尾部斜槓選擇預設檢視名稱,並將其解析為檢視。 當沒有提供檢視名稱時也是如此,例如 模型屬性被返回,或者非同步返回值,例如 空的Mono。
  • Rendering 檢視解析API介面型別; 使用程式碼完成功能探索IDE中的選項。
  • Model, Map  新增額外的模型屬性到請求的模型中。
  • 任何其他情況 任何其他返回值(BeanUtils#isSimpleProperty確定的簡單型別除外)都被視為要新增到模型的模型屬性。 除非處理方法存在@ModelAttribute註解,否則屬性名稱是使用約定從Class名稱派生的。

該模型可以包含非同步,反應式型別(例如來自Reactor,RxJava)。 在渲染之前,AbstractView將這些模型屬性解析為具體的值並更新模型。 單值反應型別被分解為單個值,或者在多值反應型別時被解析為無值(如果為空), Flux 被收集並解析為List 。

配置一個view resolution 就是新增一個ViewResolutionResultHandler型別的bean到spring 配置當中那麼簡單。WebFlux Config 為配置view resolution提供了一個專門的配置API。

1.3.5.2 Redirecting

檢視名稱關鍵字redirect:字首允許執行重定向。 UrlBasedViewResolver(和子類)將此識別為需要重定向的指令。 檢視名稱的其餘部分是重定向URL。 使用控制器返回RedirectView或Rendering.redirectTo(“abc”).build()()一樣的效果,但現在控制器內部可以簡單地按邏輯檢視名稱操作。 檢視名稱(如redirect:/ some / resource)是當前應用程式內部,而檢視名稱redirect:http://example.com/arbitrary/path將重定向到絕對URL。

1.3.5.3 內容協商

ViewResolutionResultHandler 支援內容協商。它會比較請求和選擇的View支援的媒體型別,會使用第一個支援的請求media型別。 為了支援JSON和XML等媒體型別,Spring WebFlux提供了HttpMessageWriterView,它是一個通過HttpMessageWriter呈現的特殊檢視。 通常您可以通過WebFlux配置將這些配置為預設檢視。 如果預設檢視與請求的媒體型別匹配,則始終選擇並使用它們。

1.4 註解Controller

Spring WebFlux提供了一種基於註解的程式設計模型,其中@Controller和@RestController元件使用註解來表示請求對映,請求輸入,異常處理等。 帶註解的控制器具有靈活的方法簽名,不必擴充套件基類,也不需要實現特定的介面。

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String handle() {
        return "Hello WebFlux";
    }
}

在上面的示例中,這個方法返回的字串將會被寫入響應體中。

1.4.1 @Controller

你可以用一個標準的Spring bean作為controller。 @Controller允許自動檢測,與Spring其他支援一致,用於檢測類路徑中的@Component類,併為它們自動註冊bean定義,讓它作為Web元件。

為了開啟掃描@Controller,你可以用Java程式碼新增元件掃描註解:

@Configuration
@ComponentScan("org.example.web")
public class WebConfig {

    // ...
}

@RestController是一個組合註解,它自己被@Controller和@ResponseBody進行了標註,使用它標註的類的每個方法都會繼承@ResponseBody註解。所以直接把返回值寫入響應體,不在進行檢視解析渲染。

1.4.2 Request Mapping

@RequestMapping註解是用來把請求對映到controller方法上。它有很多屬性用來匹配URL、HTTP請求方法,請求引數、請求頭和媒體型別。可以用在Class上表示共用的前置路徑,方法級上就是詳細 路徑。

還有@RequestMapping的HTTP方法特定的快捷方式:

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping 以上是提供的開箱即用的自定義註解,因為可以說大多數控制器方法應該對映到特定的HTTP方法,而不是使用預設情況下與所有HTTP方法相匹配的@RequestMapping。 同樣,在類級別仍需要@RequestMapping來表示共用對映。

以下是型別和方法級別對映的示例:

@RestController
@RequestMapping("/persons")
class PersonController {

    @GetMapping("/{id}")
    public Person getPerson(@PathVariable Long id) {
        // ...
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void add(@RequestBody Person person) {
        // ...
    }
}

1.4.2.1 URI匹配規則

你可以使用glob模式和萬用字元類對映請求:

  • ?匹配單個字元
  • *在單個目錄上匹配0個或多個字元
  • **可以匹配多個目錄上的0個或者多個字元

你也可以生命URI變數,並且通過@PathVariable訪問他們:

@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
    // ...
}

URI變數可以在類和方法級別定義:

@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {

    @GetMapping("/pets/{petId}")
    public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
        // ...
    }
}

URI變數會自動轉換為恰當的型別,或者丟擲TypeMismatchException。預設支援int/long/Date等型別,你可以註冊任何其他型別。可以檢視 Type Conversion 和 Binder Methods.

URI變數可以明確指定名稱,例如@PathVariable("customId"),如果你的程式碼編譯時包含了debugging 資訊或者在java 8 指定了-parameters引數,可以省略名稱。

{*varName}聲明瞭一個URI變數,匹配0個或多個字元,例如/resources/{*path}匹配所有的/resources/下的路徑,並且變數path捕獲了相對路徑。{varName:regex}聲明瞭一個正則表示式變數,例如給定路徑/spring-web-3.0.5 .jar,下面的方法或提取出名稱、版本和副檔名:

@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String ext) {
    // ...
}

URI匹配模式也可以包含佔位符${...},PropertyPlaceHolderConfigurer從本地、系統變數、環境變數和其他配置檔案中解析。這個可以用來引數化一些外部配置。

Spring WebFlux使用PathPattern和PathPatternParser進行URI路徑匹配支援,這兩者都位於spring-web中,並且明確設計用於在執行時匹配大量URI路徑模式的Web應用程式中的HTTP URL路徑。

Spring WebFlux不支援字尾模式匹配 - 不同於Spring MVC,像/ person這樣的對映也與/person.*匹配。 對於基於URL的內容協商,如果需要,我們建議使用查詢引數,該引數更簡單,更明確,並且不易受基於URL路徑的攻擊的影響。

1.4.2.2 模式比較

如果有多個模式匹配一個URL,必須找出一個最佳匹配。PathPattern.SPECIFICITY_COMPARATOR 查詢最佳匹配。

對於每種模式,根據URI變數和萬用字元的數量來計算得分。URI變數得分低於萬用字元的, 總分較低的模式獲勝。 如果兩種模式具有相同的分數,則選擇較長的模式。

全部匹配的模式,例如**,{*varName},不進行計算得分,並且始終放在最後。如果兩個全部匹配模式都匹配,那麼選擇較長的模式。

1.4.2.3 Consumable Media Types

你可以通過Content-Type限定請求:

@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
    // ...
}

consumes屬性也可以使用取反表示式,例如!text/plain表示出了此型別之外的型別。

你可以在Class上定義一個公用的consumes. 不像其他的請求對映屬性。但是如果方法級別指定了consumes,將會覆蓋Class級別上的。

MediaType為常用的媒體型別提供常量 - 例如 APPLICATION_JSON_VALUE,APPLICATION_JSON_UTF8_VALUE

1.4.2.4 Producible Media Types

你可以使用請求頭Accept來限定請求:

@GetMapping(path = "/pets/{petId}", produces = "application/json;charset=UTF-8")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
    // ...
}

produces 屬性也可以使用取反表示式,例如!text/plain表示出了此型別之外的型別。

你可以在Class上定義一個公用的produces . 不像其他的請求對映屬性。但是如果方法級別指定了produces ,將會覆蓋Class級別上的。MediaType為常用的媒體型別提供常量 - 例如 APPLICATION_JSON_VALUE,APPLICATION_JSON_UTF8_VALUE

1.4.2.5 Parameters and Headers

你可以用請求引數來限定請求,可以指定存在查詢引數myParam或不存在!myParam,或等於某個指定值myParam=myValue:

@GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public void findPet(@PathVariable String petId) {
    // ...
}

你也可以使用請求頭來作為條件:

@GetMapping(path = "/pets", headers = "myHeader=myValue")
public void findPet(@PathVariable String petId) {
    // ...
}

1.4.2.6 HTTP HEAD, OPTIONS

@GetMapping - 還有@RequestMapping(method = HttpMethod.GET),為了請求對映的目的,透明地支援HTTP HEAD。 Controller方法不需要改變。 在HttpHandler伺服器介面卡中應用的響應包裝可確保將“Content-Length”標頭設定為寫入的位元組數,而無需實際寫入響應。

預設情況下,通過將“Allow”響應頭設定為所有具有匹配URL模式的@RequestMapping方法中列出的HTTP方法列表來處理HTTP OPTIONS。

對於沒有HTTP方法宣告的@RequestMapping,“Allow”頭設定為“GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS”。 控制器方法應該總是宣告支援的HTTP方法,例如通過使用特定於HTTP方法的變體 - @GetMapping,@ PostMapping等。

@RequestMapping方法可以顯式對映到HTTP HEAD和HTTP OPTIONS,但在常見情況下這不是必需的。

1.4.2.7 Custom Annotations

Spring WebFlux支援使用組合註解進行請求對映。 這些註解本身是用@RequestMapping進行元註解的,並且用更限定,更具體的目的重新宣告@RequestMapping屬性的子集(或全部)。

@GetMapping,@PostMapping,@PutMapping,@DeleteMapping和@PatchMapping是組合註解的示例。 它們是開箱即用的,因為大多數控制器方法應該對映到特定的HTTP方法,而不是使用預設情況下與所有HTTP方法相匹配的@RequestMapping。 如果您需要組合註解的示例,請檢視如何宣告這些註解。

Spring WebFlux還支援自定義請求對映屬性和自定義請求匹配邏輯。 這是一個更高階的選項,需要繼承RequestMappingHandlerMapping並重寫getCustomMethodCondition方法,您可以在其中檢查自定義屬性並返回自己的RequestCondition。

1.4.3 Handler methods

@RequestMapping處理程式方法具有靈活的簽名,可以從一系列支援的控制器方法引數和返回值中進行選擇。

1.4.3.1 Method arguments

下表顯示支援的控制器方法引數。

反應式型別(Reactor,RxJava或其他)在需要阻塞I / O的引數上受支援待解決,例如 讀取請求正文。 這在描述欄中進行了標記。 在不需要阻塞的引數上不需要反應型別。

支援JDK 1.8的java.util.Optional作為方法引數,相當於required屬性- 例如 @RequestParam,@RequestHeader等,並相當於required = false。

引數描述
ServerWebExchange Access to the full ServerWebExchange — container for the HTTP request and response, request and session attributes, checkNotModified methods, and others.
ServerHttpRequest, ServerHttpResponse Access to the HTTP request or response.
WebSession Access to the session; this does not force the start of a new session unless attributes are added. Supports reactive types.
java.security.Principal Currently authenticated user; possibly a specific Principal implementation class if known. Supports reactive types
org.springframework.http.HttpMethod The HTTP method of the request.
java.util.Locale The current request locale, determined by the most specific LocaleResolver available, in effect, the configured LocaleResolver/LocaleContextResolver.
Java 6+: java.util.TimeZone,Java 8+: java.time.ZoneId The time zone associated with the current request, as determined by a LocaleContextResolver.
@PathVariable For access to URI template variables. See URI Patterns.
@MatrixVariable For access to name-value pairs in URI path segments. See Matrix variables.
@RequestParam For access to Servlet request parameters. Parameter values are converted to the declared method argument type. See @RequestParam.Note that use of @RequestParam is optional, e.g. to set its attributes. See "Any other argument" further below in this table.
@RequestHeader For access to request headers. Header values are converted to the declared method argument type. See @RequestHeader.
@CookieValue For access to cookies. Cookies values are converted to the declared method argument type. See @CookieValue.
@RequestBody For access to the HTTP request body. Body content is converted to the declared method argument type using HttpMessageReader's. Supports reactive types. @RequestBody.
HttpEntity For access to request headers and body. The body is converted with HttpMessageReader's. Supports reactive types. See HttpEntity.
@RequestPart For access to a part in a "multipart/form-data" request. Supports reactive types. See Multipart and Multipart data.
java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap For access to the model that is used in HTML controllers and exposed to templates as part of view rendering.
@ModelAttribute For access to an existing attribute in the model (instantiated if not present) with data binding and validation applied. See @ModelAttribute as well as Model Methodsand Binder Methods.Note that use of @ModelAttribute is optional, e.g. to set its attributes. See "Any other argument" further below in this table.
Errors, BindingResult For access to errors from validation and data binding for a command object (i.e. @ModelAttribute argument), or errors from the validation of an @RequestBody or@RequestPart arguments; an Errors, or BindingResult argument must be declared immediately after the validated method argument.
SessionStatus + class-level @SessionAttributes For marking form processing complete which triggers cleanup of session attributes declared through a class-level @SessionAttributes annotation. See@SessionAttributes for more details.
UriComponentsBuilder For preparing a URL relative to the current request’s host, port, scheme, context path, and the literal part of the servlet mapping also taking into account Forwarded and X-Forwarded-* headers.
@SessionAttribute For access to any session attribute; in contrast to model attributes stored in the session as a result of a class-level @SessionAttributes declaration. See@SessionAttribute for more details.
@RequestAttribute For access to request attributes. See @RequestAttribute for more details.
Any other argument If a method argument is not matched to any of the above, by default it is resolved as an @RequestParam if it is a simple type, as determined by BeanUtils#isSimpleProperty, or as an @ModelAttribute otherwise.

1.4.3.2 返回值

下表顯示支援的控制器方法返回值。 請注意,對於所有返回值,通常都支援來自庫(如Reactor,RxJava或其他)的反應式型別。

返回值描述
@ResponseBody The return value is encoded through HttpMessageWriter's and written to the response. See @ResponseBody.
HttpEntity, ResponseEntity The return value specifies the full response including HTTP headers and body be encoded through HttpMessageWriter's and written to the response. See ResponseEntity.
HttpHeaders For returning a response with headers and no body.
String A view name to be resolved with ViewResolver's and used together with the implicit model — determined through command objects and @ModelAttribute methods. The handler method may also programmatically enrich the model by declaring a Model argument (see above).
View A View instance to use for rendering together with the implicit model — determined through command objects and @ModelAttribute methods. The handler method may also programmatically enrich the model by declaring a Model argument (see above).
java.util.Map, org.springframework.ui.Model Attributes to be added to the implicit model with the view name implicitly determined based on the request path.
@ModelAttribute An attribute to be added to the model with the view name implicitly determined based on the request path.Note that @ModelAttribute is optional. See "Any other return value" further below in this table.
Rendering An API for model and view rendering scenarios.
void A method with a void, possibly async (e.g. Mono), return type (or a null return value) is considered to have fully handled the response if it also has a ServerHttpResponse, or a ServerWebExchange argument, or an @ResponseStatus annotation. The same is true also if the controller has made a positive ETag or lastModified timestamp check.If none of the above is true, a void return type may also indicate "no response body" for REST controllers, or default view name selection for HTML controllers.
Flux, Observable, or other reactive type Emit server-sent events; the SeverSentEventwrapper can be omitted when only data needs to be written (however text/event-stream must be requested or declared in the mapping through the produces attribute).
Any other return value If a return value is not matched to any of the above, by default it is treated as a view name, if it is String or void (default view name selection applies); or as a model attribute to be added to the model, unless it is a simple type, as determined byBeanUtils#isSimpleProperty in which case it remains unresolved.

1.4.3.3 型別轉換

如果引數宣告為String以外的其他引數,一些基於字串的請求輸入的方法引數 - 例如 @RequestParam,@RequestHeader,@ PathVariable,@MatrixVariable和@CookieValue可能需要進行型別轉換。 對於這種情況,基於配置的轉換器自動應用型別轉換。 預設情況下,支援int,long,Date等簡單型別。 可以通過WebDataBinder定製型別轉換,參見[mvc-ann-initbinder],或者使用FormattingConversionService註冊Formatter,請參閱Spring Field Formatting。

1.4.3.4 Matrix variables(矩陣變數)

RFC 3986討論了路徑段中的name-value對。 在Spring WebFlux中,我們將那些稱為“矩陣變數”,但它們也可以稱為URI路徑引數。

Matrix variables可出現在任何路徑段中,每個變數用分號分隔,多個值用逗號分隔,例如/cars;color=red,green;year=2012。 也可以通過重複的變數名稱來指定多個值,例如color=red;color=green;color=blue

與Spring MVC不同的是,在WebFlux中,URL中Matrix variables的存在與否不會影響請求對映。 換句話說,您不需要使用URI變數來隱藏變數內容。 也就是說,如果要從控制器方法訪問矩陣變數,則需要將URI變數新增到需要矩陣變數的路徑段。 下面是一個例子:

// GET /pets/42;q=11;r=22

@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {

    // petId == 42
    // q == 11
}

有可能所有的路徑目錄都包含矩陣變數,有時候你需要指出這個矩陣變數希望在哪裡,例如:

// GET /owners/42;q=11/pets/21;q=22

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable(name="q", pathVar="ownerId") int q1,
        @MatrixVariable(name="q", pathVar="petId") int q2) {

    // q1 == 11
    // q2 == 22
}

一個矩陣變數也可以定義為可選的,指定一個預設值:

// GET /pets/42

@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {

    // q == 1
}

想要獲取所有的矩陣變數,可以使用MultiValueMap:

// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable MultiValueMap<String, String> matrixVars,
        @MatrixVariable(pathVar="petId"") MultiValueMap<String, String> petMatrixVars) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 22, "s" : 23]
}

1.4.3.5 @RequestParam

使用@RequestParam註解將查詢引數繫結到控制器中的方法引數。 以下程式碼片段顯示了用法:

@Controller
@RequestMapping("/pets")
public class EditPetForm {

    // ...

    @GetMapping
    public String setupForm(@RequestParam("petId") int petId, Model model) {
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...

}

與將查詢引數,表單資料和上傳檔案合併為一個的Servlet API“請求引數”概念不同,在WebFlux中,每個需要通過ServerWebExchange單獨訪問。@RequestParam僅與查詢引數繫結,但是可以使用資料繫結將查詢引數,表單資料和檔案上傳資料繫結到物件上。

使用@RequestParam註解的引數預設是必須的,但是可以設定required=false,或者用java.util.Optional包裝一下類。

如果引數型別不是String,預設會進行引數轉換。

如果@RequestParam註解的引數是Map<String, String> 或 MultiValueMap<String, String>就會包所有的查詢引數新增進來。

注意,@RequestParam是可選的,預設情況下簡單型別(BeanUtils#isSimpleProperty判斷)或者其他的型別轉換器都不匹配,就預設認為是@RequestParam。

1.4.3.6 @RequestHeader

繫結一個請求頭中的值。 有一個請求頭是這樣:

Host                              localhost:8080
Accept                           text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language         fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding          gzip,deflate
Accept-Charset             ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive                    300

下面的程式碼可以獲取到請求頭Accept-EncodingKeep-Alive的值:

@GetMapping("/demo")
public void handle(
        @RequestHeader("Accept-Encoding") String encoding,
        @RequestHeader("Keep-Alive") long keepAlive) {
    //...
}

如果引數不是String型別,預設也會進行型別轉換。

如果@RequestHeader註解的引數是Map<String, String>, MultiValueMap<String, String>, 或 HttpHeaders 就會獲取到所有的請求頭資訊。

預設會把逗號分隔的字串轉換為字串或者其他已知的可轉換型別的陣列或者集合。例如:一個引數使用了@RequestHeader("Accept")標註有可能是String、String[]或者List型別

1.4.3.7 @CookieValue

繫結cookie值。 有這樣一個請求cookie:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

下面的程式碼很簡單的就可以獲取到cookie值:

@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
    //...
}

如果引數型別不是String,預設就會進行引數轉換。

1.4.3.8 @ModelAttribute

在方法引數上使用@ModelAttribute註解來訪問模型中的屬性,或者如果不存在,則將其例項化。 模型屬性也覆蓋了查詢引數和表單欄位的名稱與欄位名稱匹配的值。 這被稱為資料繫結,它不必處理解析和轉換單個查詢引數和表單欄位。 例如:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }

上面的Pet例項解析如下:

  • 已經添加了就通過 Model Methods 從model中獲取
  • 通過@SessionAttributes 從HTTP session 中獲取
  • 從預設構造方法中
  • 從呼叫具有匹配查詢引數或表單欄位的引數的“主建構函式” 引數名稱通過JavaBeans @ConstructorProperties或通過位元組碼中的執行時保留引數名稱確定。

在獲得例項之後,應用資料繫結。 WebExchangeDataBinder類將查詢引數和表單欄位的名稱與目標物件上的欄位名稱進行匹配。 必要時應用型別轉換後填充匹配欄位。 有關資料繫結(和驗證)的更多資訊,請參閱 Validation.。 有關自定義資料繫結的更多資訊,請參閱Binder Methods.。

資料繫結可能會導致錯誤。 預設情況下會引發WebExchangeBindException,但要在控制器方法中檢查此類錯誤,請立即在@ModelAttribute旁邊新增BindingResult引數,如下所示:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}

通過新增javax.validation.Valid註解或Spring的@Validated註解(另請參閱Bean驗證和Spring驗證),可以在資料繫結後自動應用驗證。 例如:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}

與Spring MVC不同,Spring WebFlux支援模型中的反應型別,例如, Mono或io.reactivex.Single 。 @ModelAttribute引數可以使用或不使用反應式型別的包裝來宣告,並且如果需要,它將被相應地解析為實際值。 但是請注意,為了使用BindingResult引數,您必須在它之前宣告@ModelAttribute引數,並且不使用反應型包裝器,如前所示。 或者可以通過反應型來處理任何錯誤:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public Mono<String> processSubmit(@Valid @ModelAttribute("pet") Mono<Pet> petMono) {
    return petMono
        .flatMap(pet -> {
            // ...
        })
        .onErrorResume(ex -> {
            // ...
        });
}

注意,@ModelAttribute是可選的,預設情況下所有的非簡單型別(BeanUtils#isSimpleProperty判斷)並且不能被其他型別轉換,就會認為使用@ModelAttribute註解。

1.4.3.9 @SessionAttributes

@SessionAttributes用於在請求之間的WebSession中儲存模型屬性。 它是一個宣告特定控制器使用的會話屬性的型別級註釋。 這通常會列出模型屬性的名稱或模型屬性的型別,這些屬性應該透明地儲存在會話中供隨後的訪問請求使用。例如:

@Controller
@SessionAttributes("pet")
public class EditPetForm {
    // ...
}

在第一個請求中,當名稱為“pet”的模型屬性新增到模型中時,它會自動儲存在WebSession中。 它會一直存在,直到另一個控制器方法使用SessionStatus方法引數清除為止:

@Controller
@SessionAttributes("pet")
public class EditPetForm {

    // ...

    @PostMapping("/pets/{id}")
    public String handle(Pet pet, BindingResult errors, SessionStatus status) {
        if (errors.hasErrors) {
            // ...
        }
            status.setComplete();
            // ...
        }
    }
}

1.4.3.10 @SessionAttribute

如果您需要訪問全域性(即在控制器之外)管理的預先存在的會話屬性,並且可能存在也可能不存在,請在方法引數上使用@SessionAttribute註釋:

@GetMapping("/")
public String handle(@SessionAttribute User user) {
    // ...
}

對於需要新增或刪除會話屬性的用例,考慮將WebSession注入控制器方法。

為了將會話中的模型屬性臨時儲存為控制器工作流的一部分,請考慮使用@SessionAttributes中所述的SessionAttributes。

1.4.3.11 @RequestAttribute

類似於@SessionAttribute,可以使用@RequestAttribute註釋來訪問先前建立的請求屬性,例如, 通過WebFilter新增的:

@GetMapping("/")
public String handle(@RequestAttribute Client client) {
    // ...
}

1.4.3.12 Multipart

正如Multipart data中所解釋的,ServerWebExchange提供對Multipart 內容的訪問。 在控制器中處理檔案上傳表單(例如從瀏覽器)的最佳方式是通過資料繫結到物件:

class MyForm {

    private String name;

    private MultipartFile file;

    // ...

}

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(MyForm form, BindingResult errors) {
        // ...
    }

}

Multipart 請求也可以在非RESTful服務場景中從非瀏覽器客戶端提交。 例如,JSON形式伴隨一個檔案:

POST /someUrl
Content-Type: multipart/mixed

--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="meta-data"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit

{
    "name": "value"
}
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="file-data"; filename="file.properties"
Content-Type: text/xml
Content-Transfer-Encoding: 8bit
... File Data ...

你可以使用@RequestPart獲取到從JSON格式反序列化(這是在HTTP Message Codecs配置的)回來的"meta-data"部分:

@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata,
        @RequestPart("file-data") FilePart file) {
    // ...
}

要以流方式順序訪問Multipart 資料,請使用帶Flux 的@RequestBody。 例如:

@PostMapping("/")
public String handle(@RequestBody Flux<Part> parts) {
    // ...
}

@RequestPart可以與javax.validation.Valid或Spring的@Validated註釋組合使用,這會應用標準Bean驗證。 預設情況下,驗證錯誤會導致變為400(BAD_REQUEST)響應的WebExchangeBindException。 或者,驗證錯誤可以通過Errors或BindingResult引數在控制器內本地處理:

@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") MetaData metadata,
        BindingResult result) {
    // ...
}

1.4.3.13 @RequestBody

使用@RequestBody註釋讓請求體通過HttpMessageReader讀取並反序列化成Object。 下面是一個帶有@RequestBody引數的例子:

@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
    // ...
}

與Spring MVC不同,在WebFlux中,@RequestBody方法引數支援反應型別和完全非阻塞式讀取和(客戶端到伺服器)流:

@PostMapping("/accounts")
public void handle(@RequestBody Mono<Account> account) {
    // ...
}

您可以使用WebFlux Config的HTTP message codecs選項來配置或自定義訊息讀取器。 @RequestBody可以與javax.validation.Valid或Spring的@Validated註解組合使用,這會應用標準Bean驗證。 預設情況下,驗證錯誤會導致變為400(BAD_REQUEST)響應的WebExchangeBindException。 或者,驗證錯誤可以通過Errors或BindingResult引數在控制器內本地處理:

@PostMapping("/accounts")
public void handle(@Valid @RequestBody Account account, BindingResult result) {
    // ...
}

1.4.3.14 HttpEntity

HttpEntity或多或少與使用@RequestBody相同,但包含了請求標頭和主體的容器物件。 下面是一個例子:

@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
    // ...
}

1.4.3.15 @ResponseBody

在一個方法上使用@ResponseBody註解來通過HttpMessageWriter將返回序列化到響應主體。 例如:

@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
    // ...
}

@ResponseBody也支援類級別,在這種情況下,它被所有控制器方法繼承。 這是@RestController的作用,它只不過是用@Controller和@ResponseBody標記的組合註解。

@ResponseBody支援反應型別,這意味著您可以返回Reactor或RxJava型別,並將它們生成的非同步值呈現給響應。 有關JSON渲染的更多詳細資訊,請參閱[webflux-codecs-jackson-json]。

@ResponseBody方法可以與JSON序列化檢視結合使用。 有關詳細資訊,請參閱[mvc-ann-jackson]。

您可以使用WebFlux Config的HTTP message codecs選項來配置或定製訊息寫入。

1.4.3.16 ResponseEntity

ResponseEntity或多或少與使用@ResponseBody相同,但是指定了請求標頭和主體的容器物件。 下面是一個例子:

@PostMapping("/something")
public ResponseEntity<String> handle() {
    // ...
    URI location = ...
    return new ResponseEntity.created(location).build();
}

1.4.3.17 Jackson JSON

Jackson 序列化檢視 Spring WebFlux為Jackson 的序列化檢視提供了內建的支援,它允許只呈現部分物件屬性。 要將其與@ResponseBody或ResponseEntity控制器方法一起使用,請使用Jackson的@JsonView註釋來啟用序列化檢視類:

@RestController
public class UserController {

    @GetMapping("/user")
    @JsonView(User.WithoutPasswordView.class)
    public User getUser() {
        return new User("eric", "7!jd#h23");
    }
}

public class User {

    public interface WithoutPasswordView {};
    public interface WithPasswordView extends WithoutPasswordView {};

    private String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @JsonView(WithoutPasswordView.class)
    public String getUsername() {
        return this.username;
    }

    @JsonView(WithPasswordView.class)
    public String getPassword() {
        return this.password;
    }
}

@JsonView的值允許指定一個檢視類的陣列,但每個控制器方法只能使用一個註解。 如果您需要啟用多個檢視,請使用複合檢視。

1.4.4 Model Methods

可以在@RequestMapping方法引數上使用@ModelAttribute註釋來建立或訪問模型中的Object並將其繫結到請求。 @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);
}

如果未明確指定名稱,則會根據Javadoc for Conventions中所述的物件型別選擇預設名稱。 您始終可以使用過載的addAttribute方法或通過@ModelAttribute (name)上的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) {
    // ...
}

此外,任何具有反應型包裝的模型屬性都會在檢視呈現之前解析為其實際值(並更新模型)。

@ModelAttribute也可以用作@RequestMapping方法的方法級別註釋,在這種情況下,@RequestMapping方法的返回值被解釋為模型屬性。 這通常不是必需的,因為它是HTML控制器中的預設行為,除非返回值是一個字串。 @ModelAttribute也可以幫助模型屬性名稱:

@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
    // ...
    return account;
}

1.4.5 Binder Methods

@Controller或@ControllerAdvice類中的@InitBinder方法可用於自定義基於字串的請求值(例如請求引數,路徑變數,請求頭,cookie等)的方法引數的型別轉換。 在將請求引數繫結到@ModelAttribute引數上時,也有型別轉換。

@InitBinder方法可以註冊特定控制器的java.bean.PropertyEditor或Spring Converter和Formatter元件。 另外,WebFlux Java配置可用於在全域性共享的FormattingConversionService中註冊Converter和Formatter型別。

@InitBinder方法支援許多與@RequestMapping方法相同的引數,除了@ModelAttribute引數。 通常註冊時,宣告WebDataBinder引數,返回void。 下面是一個例子:

@Controller
public class FormController {

    @InitBinder
    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:

@Controller
public class FormController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
    }

    // ...
}

1.4.6 Controller Advice

通常,@ExceptionHandler,@InitBinder和@ModelAttribute方法適用於宣告它們的@Controller類(或類層次結構)中。如果希望這些方法跨控制器在全域性範圍內應用,則可以在標有@ControllerAdvice或@RestControllerAdvice的類中宣告。

@ControllerAdvice使用了@Component,這意味著這些類可以通過元件掃描註冊為Spring bean。 @RestControllerAdvice也是一個用@ControllerAdvice和@ResponseBody標記的元註釋,它意味著@ExceptionHandler方法通過訊息轉換(就像檢視解析/模板渲染)呈現給響應主體。

啟動時,基礎設施類檢測在@ControllerAdvice的Spring bean中宣告@RequestMapping和@ExceptionHandler的方法,然後在執行時應用它們。來自@ControllerAdvice的全域性@ExceptionHandler方法在來自@Controller的之後執行。相比之下,全域性@ModelAttribute和@InitBinder方法在本地之前執行。

預設情況下,@ControllerAdvice方法適用於每個請求,即所有控制器,但您可以通過註釋上的屬性限定需要執行的控制器:

// 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 {}

請記住,上述選擇器在執行時執行,如果廣泛使用,可能會對效能產生負面影響。 有關更多詳細資訊,請參閱@ControllerAdvice Javadoc。

1.5 URI Links

本節介紹Spring框架中可用於準備URI的各種選項。

1.5.1 UriComponents

UriComponents與java.net.URI差不多。 但是它帶有一個專用的UriComponentsBuilder並支援URI模板變數:

String uriTemplate = "http://example.com/hotels/{hotel}";

UriComponents uriComponents = UriComponentsBuilder.fromUriString(uriTemplate)  ①
        .queryParam("q", "{q}")  ②
        .build(); ③

URI uri = uriComponents.expand("Westin", "123").encode().toUri();  ④

① 一個建立 URI的靜態工廠方法 ②新增或者替換引數 ③構建 ④擴充套件變數、編碼並且獲取URI

上面的寫法可以用鏈式或者快捷方式:

String uriTemplate = "http://example.com/hotels/{hotel}";

URI uri = UriComponentsBuilder.fromUriString(uriTemplate)
        .queryParam("q", "{q}")
        .buildAndExpand("Westin", "123")
        .encode()
        .toUri();

1.5.2 UriBuilder

UriComponentsBuilder是UriBuilder的一個實現。 UriBuilderFactory和UriBuilder一起提供了可從URI模板構建URI的可插入機制,以及共享公共屬性(如基本URI,編碼策略等)的方法。

RestTemplate和WebClient都可以使用UriBuilderFactory進行配置,以便自定義URI模板建立URI的方式。 預設實現在內部依賴於UriComponentsBuilder,並提供了配置通用基本URI,替代編碼模式策略等的選項。

配置RestTemplate的一個例子:

String baseUrl = "http://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);

RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);

配置WebClient的一個例子:

String baseUrl = "http://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);

// Configure the UriBuilderFactory..
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();

// Or use shortcut on builder..
WebClient client = WebClient.builder().baseUrl(baseUrl).build();

// Or use create shortcut...
WebClient client = WebClient.create(baseUrl);

您也可以直接使用DefaultUriBuilderFactory,就像您使用UriComponentsBuilder一樣。 主要區別在於,DefaultUriBuilderFactory是有狀態的,可以重新用於準備許多URL,共享例如基本URL等通用配置,而UriComponentsBuilder是無狀態的並且是單URI。

使用DefaultUriBuilderFactory的一個例子:

String baseUrl = "http://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);

URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
        .queryParam("q", "{q}")
        .build("Westin", "123"); // encoding strategy applied..

1.5.3 URI Encoding

在UriComponents中編碼URI的預設方式如下所示:

  • URI變數被擴充套件。
  • 每個URI元件(路徑,查詢等)都是單獨編碼的。

編碼規則如下:在URI元件中,按照RFC 3986中的定義,對所有非法字元(包括非US-ASCII字元)以及URI元件中非法的所有其他字元應用編碼。

UriComponents中的編碼與java.net.URI的多引數建構函式一樣,就是類級別Javadoc的“Escaped octets,quotation,encoding,and decoding”部分中所述。

上述預設編碼策略不會對所有具有保留含義的字元進行編碼,而只會對給定URI元件中的非法字元進行編碼。 如果這不符合您的期望,您可以使用下面介紹的替代策略。

當使用DefaultUriBuilderFactory - 嵌入WebClient,RestTemplate或直接使用時,可以切換到另一種編碼策略,如下所示:

String baseUrl = "http://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.VALUES_ONLY);

// ...

這種編碼策略在擴充套件之前對每個URI變數值應用UriUtils.encode(String,Charset),有效編碼所有非US-ASCII字元以及在URI中具有保留含義的所有字元,這確保擴充套件的URI變數 對URI的結構或含義沒有任何影響。

1.6 Functional Endpoints

Spring WebFlux包含一個輕量級的函數語言程式設計模型,其中函式用於路由和處理請求,並且契約是為不可變性而設計的。 它是基於註釋的程式設計模型的一種替代方案,但是同樣可以在Reactive Spring Web基礎上執行.

1.6.1 HandlerFunction

傳入的HTTP請求由HandlerFunction處理,它本質上是一個接受ServerRequest並返回Mono 的函式。 如果您熟悉基於註釋的程式設計模型,則HandlerFunction相當於@RequestMapping方法。

ServerRequest和ServerResponse是不可變的介面,它提供了JDK-8友好的訪問底層HTTP訊息的能力,以及反應式非阻塞背壓。 該請求將主體暴露為Reactor Flux或Mono型別; 響應接受任何Reactive Streams Publisher作為正文。 Reactive Libraries解釋了這一點的合理性。

ServerRequest允許訪問各種HTTP請求元素:方法,URI,查詢引數和頭部資訊(通過一個單獨的ServerRequest.Headers介面,通過body方法提供對主體的訪問,例如,如何提取請求主體 成Mono:

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

這裡是如何響應體轉換到Flux中,其中Person是一個可以從報文內容反序列化的類(即,使用Jackson反序列化JSON,使用JAXB反序列化XML):

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

上面使用的bodyToMono和bodyToFlux實際上是使用通用ServerRequest.body(BodyExtractor)方法的便捷方法。 BodyExtractor是一個功能性策略介面,可讓您編寫自己的提取邏輯,但可在BodyExtractor工具類中找到常見的BodyExtractor例項。 所以,上面的例子也可以寫成如下:

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

同樣,ServerResponse提供對HTTP響應的訪問。 由於它是不可變的,因此您可以使用構建器建立一個ServerResponse。 構建器允許您設定響應狀態,新增響應頭並提供正文。 例如,這是如何建立200 OK狀態,JSON內容型別和正文的響應:

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

這裡是如何建立一個201 CREATED狀態,一個“Location”頭和空白主體的響應:

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

把它們放在一起可以讓我們建立一個HandlerFunction。 例如,下面是一個簡單的“Hello World”處理程式lambda的示例,它返回一個具有200狀態和基於String的主體的響應:

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

正如我們上面所做的那樣,編寫處理函式的lambda函式是很方便的,但是在處理多個函式時可能缺乏可讀性並且變得不易維護。 因此,建議將相關處理函式分組到處理程式或控制器類中。 例如,下面是一個反應式Person儲存庫的類:

import static org.springframework.http.MediaType.APPLICATION_JSON;
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 ServerResponse.ok().contentType(APPLICATION_JSON).body(people, Person.class);
    }

    public Mono<ServerResponse> createPerson(ServerRequest request) { ②
        Mono<Person> person = request.bodyToMono(Person.class);
        return ServerResponse.ok().build(repository.savePerson(person));
    }

    public Mono<ServerResponse> getPerson(ServerRequest request) { ③
        int personId = Integer.valueOf(request.pathVariable("id"));
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();
        Mono<Person> personMono = repository.getPerson(personId);
        return personMono
                .flatMap(person -> ServerResponse.ok().contentType(APPLICATION_JSON).body(fromObject(person)))
                .switchIfEmpty(notFound);
    }
}

① listPeople 把儲存的所有Person用JSON格式返回。 ②createPerson 把請求體中的Person儲存起來。注意,PersonRepository.savePerson(Person)返回Mono:這是一個空的Mono並在從請求體中讀取完資料儲存後發出完成訊號。所以我們收到完成訊號(即儲存完成後)後使用build(Publisher)方法去傳送一個響應。 ③ getPerson 返回使用路徑變數id標識出的單個Person。如果我們從儲存中成功獲取就建立一個JSON響應,如果沒有找到就使用switchIfEmpty(Mono)返回一個404未發現響應。

1.6.2 RouterFunction

傳入的請求通過一個RouterFunction被路由到處理函式,這是一個接受ServerRequest的函式,並返回一個Mono 。 如果請求匹配特定的路由,則返回一個處理函式,否則返回一個空的Mono。 RouterFunction與基於註解的程式設計模型中的@RequestMapping註釋具有相似的用途。

通常,您不要自己編寫路由器功能,而是使用RouterFunctions.route(RequestPredicate,HandlerFunction)使用請求斷言和處理函式建立一個路由器函式。 如果斷言適用,則將請求路由到給定的處理函式; 否則不執行路由,返回404 Not Found響應。 雖然您可以編寫自己的RequestPredicate,但您不必:RequestPredicates工具類提供常用的斷言,例如基於路徑,HTTP方法,內容型別等的匹配。使用路由,我們可以路由到我們的“Hello World” 處理函式:

RouterFunction<ServerResponse> helloWorldRoute =
    RouterFunctions.route(RequestPredicates.path("/hello-world"),
    request -> Response.ok().body(fromObject("Hello World")));

兩個路由器功能可以組成一個新的路由器功能,該路由器可以路由到任一處理器:如果第一個路由的斷言不匹配,則第二個路由器的斷言將被執行判斷。 組合路由器功能按順序進行匹配,因此將特定功能放在通用功能之前。 您可以通過呼叫RouterFunction.and(RouterFunction)或通過呼叫RouterFunction.andRoute(RequestPredicate,HandlerFunction)來組合兩個路由器功能,這是RouterFunction.and()與RouterFunctions.route()的簡便用法。

鑑於我們上面展示的PersonHandler,我們現在可以定義路由到相應處理器的路由器功能。 我們使用method-references 來關聯處理函器:

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> personRoute =
    route(GET("/person/{id}").and(accept(APPLICATION_JSON)), handler::getPerson)
        .andRoute(GET("/person").and(accept(APPLICATION_JSON)), handler::listPeople)
        .andRoute(POST("/person").and(contentType(APPLICATION_JSON)), handler::createPerson);

除了路由器功能外,您還可以通過呼叫RequestPredicate.and(RequestPredicate)或RequestPredicate.or(RequestPredicate)來組合請求斷言。 這些按預期工作:and表示兩個斷言都要匹配; or表示其中一個匹配就可以。 RequestPredicates中的大多數斷言都是組合。 例如,RequestPredicates.GET(String)是RequestPredicates.method(HttpMethod)和RequestPredicates.path(String)的組合。

1.6.3 Running a server

你如何在HTTP伺服器上執行路由器功能? 一個簡單的選擇是使用以下方法之一將路由器功能轉換為HttpHandler:

  • RouterFunctions.toHttpHandler(RouterFunction)

  • RouterFunctions.toHttpHandler(RouterFunction,HandlerStrategies)

然後,通過遵循HttpHandler獲取特定伺服器的用法,可以將返回的HttpHandler與多個伺服器介面卡一起使用。

更高階的做法是通過WebFlux配置基於DispatcherHandler的伺服器,該配置使用Spring配置宣告需要處理請求的元件。 WebFlux Java配置宣告以下基礎結構元件以支援功能端點:

  • RouterFunctionMapping  在Spring配置中獲取一個或多個RouterFunction <?> bean,通過RouterFunction.andOther組合它們,並將請求路由到最終組成的RouterFunction。
  • HandlerFunctionAdapter  一個簡單的介面卡,它允許DispatcherHandler呼叫對映到請求的HandlerFunction。
  • ServerResponseResultHandler  通過呼叫ServerResponse的writeTo方法來處理呼叫HandlerFunction的結果。

上述元件允許功能端點符合DispatcherHandler請求處理生命週期,並且還可能與註釋的控制器並行執行(如果宣告的話)。 這也是功能端點啟用Spring Boot WebFlux的啟動器。

下面是用java程式碼配置WebFlux的一個示例(檢視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
    default void addCorsMappings(CorsRegistry registry) {
        // configure CORS...
    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        // configure view resolution for HTML rendering...
    }
}

1.6.4 HandlerFilterFunction

由路由器功能對映的路由可以通過呼叫RouterFunction.filter(HandlerFilterFunction)進行過濾,其中HandlerFilterFunction本質上是一個接受ServerRequest和HandlerFunction的函式,並返回ServerResponse。 HandlerFunction引數表示鏈中的下一個元素:這通常是路由到的HandlerFunction,但如果應用多個過濾器,則也可以是另一個FilterFunction。 使用註解,可以使用@ControllerAdvice和/或ServletFilter實現類似的功能。 讓我們在我們的路由中新增一個簡單的安全過濾器,假設我們有一個可以確定是否允許特定路徑的SecurityManager:

import static org.springframework.http.HttpStatus.UNAUTHORIZED;

SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = ...

RouterFunction<ServerResponse> filteredRoute =
    route.filter((request, next) -> {
        if (securityManager.allowAccessTo(request.path())) {
            return next.handle(request);
        }
        else {
            return ServerResponse.status(UNAUTHORIZED).build();
        }
  });

你可以在這個例子中看到呼叫next.handle(ServerRequest)是可選的:我們只允許在允許訪問時執行處理函式。

1.7 CORS

通過專用的CorsWebFilter提供對功能端點的CORS支援。

1.7.1 介紹

出於安全原因,瀏覽器禁止對當前域以外的資源進行AJAX呼叫。 例如,您可以在一個標籤中使用銀行帳戶,在另一個標籤中使用evil.com。 來自evil.com的指令碼不應該使用您的憑證向您的銀行API傳送AJAX請求,例如 從您的帳戶中提取錢!

跨源資源共享(CORS)是大多數瀏覽器實現的W3C規範,允許您指定哪種型別的跨域請求被授權,而不是使用基於IFRAME或JSONP的不太安全和功能較弱的解決方法。

1.7.2 Processing

CORS規範區分預檢,簡單和實際請求。 要了解CORS如何工作,可以閱讀本文以及其他許多內容,或參閱規範以獲取更多詳細資訊。

Spring WebFlux HandlerMapping提供了對CORS的內建支援。 在成功將請求對映到處理程式後,HandlerMapping會檢查給定請求和處理程式的CORS配置並採取進一步的操作。 預檢請求被直接處理,而簡單和實際的CORS請求被攔截,驗證並且需要設定CORS響應頭。

為了實現跨域請求(即Origin頭部存在並且與請求的主機不同),你需要有一些明確宣告的CORS配置。 如果找不到匹配的CORS配置,則會拒絕預檢請求。 沒有將CORS頭新增到簡單和實際的CORS請求的響應,因此瀏覽器拒絕它們。

每個HandlerMapping可以單獨配置基於URL模式的CorsConfiguration對映。 在大多數情況下,應用程式將使用WebFlux Java配置來宣告這種對映,這會把單個全域性對映傳遞給所有HadlerMappping。

HandlerMapping級別的全域性CORS配置可以與更細粒度的處理器級CORS配置相結合。 例如,帶註釋的控制器可以使用類或方法級的@CrossOrigin註釋(其他處理器可以實現CorsConfigurationSource)。

全域性和本地配置相結合的規則通常是相加的 - 例如, 所有的全域性和所有本地的源。 對於那些只能接受單個值的屬性,如allowCredentials和maxAge,本地將覆蓋全域性值。 有關更多詳細資訊,請參閱CorsConfiguration#combine(CorsConfiguration)。

從原始碼中學習或者使用更高階的自定義,請檢視:

  • CorsConfiguration
  • CorsProcessor, DefaultCorsProcessor
  • AbstractHandlerMapping

1.7.3 @CrossOrigin

@CrossOrigin 註解可以在控制器方法上開啟跨域:

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

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

    @DeleteMapping("/{id}")
    public Mono<Void> remove(@PathVariable Long id) {
        // ...
    }
}

預設情況下@CrossOrigin允許:

  • 所有域
  • 所有請求頭
  • 所有這個請求對映的HTTP方法
  • allowedCredentials allowedCredentials預設情況下未啟用,因為它建立了一個信任級別,用於公開敏感的使用者特定資訊,如Cookie和CSRF令牌,並且只能在適當的情況下使用。
  • maxAge 預設30分鐘

@CrossOrigin 也可以在類級別使用,並且對所有的方法都生效:

@CrossOrigin(origins = "http://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

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

    @DeleteMapping("/{id}")
    public Mono<Void> remove(@PathVariable Long id) {
        // ...
    }
}

@CrossOrigin 也可以同時在類級別和方法上使用:

@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

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

    @DeleteMapping("/{id}")
    public Mono<Void> remove(@PathVariable Long id) {
        // ...
    }
}

1.7.4 Global Config

除了細粒度的控制器方法級配置之外,您還可能需要定義一些全域性CORS配置。 您可以在任何HandlerMapping上分別設定基於URL的CorsConfiguration對映。 然而,大多數應用程式將使用WebFlux Java配置來實現這一點。

預設情況下全域性配置開啟下面的配置:

  • 所有域
  • 所有請求頭
  • GET、HEAD和POST方法
  • allowedCredentials 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

您可以通過內建的CorsWebFilter來應用CORS支援,這非常適合功能端點。

要配置過濾器,您可以宣告一個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 Web Security

專案為保護Web應用程式免受惡意攻擊提供支援。 檢視Spring Security參考文件,其中包括:

1.9 檢視技術

待更新。。。 這裡是檢視技術的一些說明,包括Thymeleaf、freeMarker、HTML、JSON等。https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-view

1.10 WebFlux配置

待更新。。。 WebFlux的相關配置

1.10.1 HTTP/2

需要Servlet 4容器來支援HTTP / 2,並且Spring Framework 5與Servlet API 4相容。從程式設計模型的角度來看,沒有什麼具體的應用程式需要做。 但是有一些與伺服器配置相關的考慮事項 有關更多詳細資訊,請檢視HTTP / 2 wiki頁面。

目前Spring WebFlux不支援Netty的HTTP / 2。 也不支援以程式設計方式將資源推送到客戶端。