SpringWebflux是SpringFramework5.0添加的新功能,WebFlux本身追隨當下最火的Reactive Programming而誕生的框架,那么本篇就來簡述一下這個框架到底是做什么的
一、關於WebFlux
我們知道傳統的Web框架,比如說:struts2,springmvc等都是基於Servlet API與Servlet容器基礎之上運行的,在Servlet3.1之后才有了異步非阻塞的支持。而WebFlux是一個典型非阻塞異步的框架,它的核心是基於Reactor的相關API實現的。相對於傳統的web框架來說,它可以運行在諸如Netty,Undertow及支持Servlet3.1的容器上,因此它的運行環境的可選擇行要比傳統web框架多的多。
根據官方的說法,webflux主要在如下兩方面體現出獨有的優勢:
1)非阻塞式
其實在servlet3.1提供了非阻塞的API,WebFlux提供了一種比其更完美的解決方案。使用非阻塞的方式可以利用較小的線程或硬件資源來處理並發進而提高其可伸縮性
2) 函數式編程端點
老生常談的編程方式了,Spring5必須讓你使用java8,那么函數式編程就是java8重要的特點之一,而WebFlux支持函數式編程來定義路由端點處理請求。
二、SpringMVC與SpringWebFlux
我們先來看官網的一張圖:
它們都可以用注解式編程模型,都可以運行在tomcat,jetty,undertow等servlet容器當中。但是SpringMVC采用命令式編程方式,代碼一句一句的執行,這樣更有利於理解與調試,而WebFlux則是基於異步響應式編程,對於初次接觸的碼農們來說會不習慣。對於這兩種框架官方給出的建議是:
1)如果原先使用用SpringMVC好好的話,則沒必要遷移。因為命令式編程是編寫、理解和調試代碼的最簡單方法。因為老項目的類庫與代碼都是基於阻塞式的。
2)如果你的團隊打算使用非阻塞式web框架,WebFlux確實是一個可考慮的技術路線,而且它支持類似於SpringMvc的Annotation的方式實現編程模式,也可以在微服務架構中讓WebMvc與WebFlux共用Controller,切換使用的成本相當小
3)在SpringMVC項目里如果需要調用遠程服務的話,你不妨考慮一下使用WebClient,而且方法的返回值可以考慮使用Reactive Type類型的,當每個調用的延遲時間越長,或者調用之間的相互依賴程度越高,其好處就越大
我個人意見是:官網明確指出,SpringWebFlux並不是讓你的程序運行的更快(相對於SpringMVC來說),而是在有限的資源下提高系統的伸縮性,因此當你對響應式編程非常熟練的情況下並將其應用於新的系統中,還是值得考慮的,否則還是老老實實的使用WebMVC吧
三、Reactive Spring Web
在這里定義了最基本的服務端接口:HttpHandler和WebHandler
HttpHandler
HttpHandler定義了最基本的處理Http請求行為,這個接口主要作用是處理Http請求並將結果做出響應,下面這個表格是說明了Server API的使用方式及何種方式進行響應式流支持的:
Server name | Server API used | Reactive Streams support |
---|---|---|
Netty |
Netty API |
|
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 |
WebHandler
WebHandler定義了Web請求必要一些處理行為,大家不妨想想看:WebFlux已經脫離了Servlet API,那么使用WebFlux時遇到會話機制怎么辦,想要對請求過濾處理怎么辦或者想要處理靜態資源怎么辦等等,那么WebHandler就是做這個事情的。其實在HttpHandler的基本實現類通過適配器模式及裝飾模式也間接的實現了WebHandler接口:
WebHandler常見的實現類,我在這里列舉一下:
WebHandlerDecorator:WebHandler的裝飾器,利用裝飾模式實現相關功能的擴展
HttpWebHandlerAdapter: 進行Http請求處理,同時也是HttpHandler的實現類
FilteringWebHandler:通過WebFilter進行過濾處理的類,類似於Servlet中的Filter
ExceptionHandlingWebHandler: 針對於異常的處理類
ResourceWebHandler:用於靜態資源請求的處理類
DispatcherHandler:請求的總控制器,類似於WebMVC中的DispatcherServlet
四、實現WebFlux示例
建立SpringBoot項目,注意SpringBoot版本必須為2.0.0+,在build.gradle配置文件里寫:
compile('org.springframework.boot:spring-boot-starter-webflux')
基於Annotated Controller方式實現
WebFluxConfig配置:
package com.hzgj.framework.study.springwebflux.web.reactive; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.DispatcherHandler; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.config.WebFluxConfigurer; @Configuration @ComponentScan @EnableWebFlux public class WebFluxConfig implements WebFluxConfigurer { @Bean public WebHandler webHandler(ApplicationContext applicationContext) { DispatcherHandler dispatcherHandler = new DispatcherHandler(applicationContext); return dispatcherHandler; } }
在這里我們創建一個WebHandler並使用@EnableWebFlux打開相關功能。我們可以發現與SpringMVC的WebConfig配置真的好像
Controller:
package com.hzgj.framework.study.springwebflux.web.reactive.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class IndexController { @GetMapping("/index") public String index() { return "index"; } }
在這里與SpringMVC定義的Controller無異
Main方法:
package com.hzgj.framework.study.springwebflux.web.reactive; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import reactor.ipc.netty.http.server.HttpServer; import java.io.IOException; /** * 基於Reactor Netty實現WebFlux服務 * @author chen.nie * @date 2018/7/13 **/ public class SpringWebfluxApplication { public static void main(String[] args) throws IOException { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(WebFluxConfig.class); //通過ApplicationContext創建HttpHandler HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(applicationContext).build(); ReactorHttpHandlerAdapter httpHandlerAdapter = new ReactorHttpHandlerAdapter(httpHandler); HttpServer.create("localhost",8080).newHandler(httpHandlerAdapter).block(); System.in.read(); } }
程序啟動成功后即可通過http://localhost:8080/index拿到對應結果
函數式編程方式
使用這種方式請先了解Java8提供的函數式編程特性。那么我們先編寫一個簡單的Handler
package com.hzgj.framework.study.springwebflux.web.reactive.handler; import org.springframework.beans.BeanUtils; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; import static org.springframework.http.MediaType.*; import static org.springframework.web.reactive.function.BodyInserters.fromObject; import static org.springframework.web.reactive.function.server.ServerResponse.ok; /** * 類似於Controller,處理用戶請求的真實邏輯 */ public class StudentHandler { public static Mono<ServerResponse> selectStudent(ServerRequest request) { Student studentBody = new Student(); request.bodyToMono(Student.class).subscribe(student -> BeanUtils.copyProperties(student, studentBody)); return ok().contentType(APPLICATION_JSON_UTF8).body(fromObject(studentBody)); } public static Mono<ServerResponse> insertStudent(ServerRequest request){ return ok().contentType(TEXT_PLAIN).body(fromObject("success")); } private static class Student { private Integer id; private String name; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } }
這個Handler類似於Controller的作用,在這里的返回值均為Mono類型,其中ServerRequest和ServerResponse,大家可以先理解為WebFlux替代ServletRequest與ServletResponse對象的,而且這些類能夠支持異步。
Main方法里我們創建的HttpHandler的方式需要進行改變一下,同樣用函數式方式進行編寫:
package com.hzgj.framework.study.springwebflux.web.reactive; import com.hzgj.framework.study.springwebflux.web.reactive.handler.StudentHandler; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter; import reactor.ipc.netty.http.server.HttpServer; import java.io.IOException; import static org.springframework.web.reactive.function.server.RequestPredicates.*; import static org.springframework.web.reactive.function.server.RouterFunctions.route; import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler; public class FunctionRouteApplication { public static void main(String[] args) throws IOException { HttpHandler httpHandler = toHttpHandler( route(POST("/selectStudent").and(accept(MediaType.APPLICATION_JSON_UTF8)), StudentHandler::selectStudent). and(route(GET("/saveStudent"), StudentHandler::insertStudent))); ReactorHttpHandlerAdapter httpHandlerAdapter = new ReactorHttpHandlerAdapter(httpHandler); HttpServer.create("localhost", 8080).newHandler(httpHandlerAdapter).block(); System.in.read(); } }
啟動成功后,我們用postman測試一下
此時我們可以看到使用函數式編程創建路由端點,也可以實現同樣的功能。