1.Spring WebFlux模塊
??Spring Framework 5包含一個新 spring-webflux 模塊剧蹂。該模塊包含對反應式HTTP和WebSocket客戶端的支持以及反應式服務器Web應用程序(包括REST敬矩,HTML瀏覽器和WebSocket樣式交互)毁靶。
2.Server
spring web-flux 支持2種不同的編程模型
1.支持Spring MVC @Controller 這種注解,用法大同小異
2.函數(shù)式 Java 8 lambda 風格的路由函數(shù)處理請求
WebFlux可以在支持Servlet 3.1非阻塞IO API以及其他異步運行時(如Netty和Undertow)的Servlet容器上運行。下圖顯示了服務器端兩種技術棧蝙昙,其中包括spring-webmvc模塊左側的傳統(tǒng)基于Servlet的Spring MVC以及模塊右側的 基于響應式的spring-webflux邢笙。
1.注解式編程:雖然可以使用與Spring MVC相同的注解,但其底層核心(HandlerMapping HandlerAdapter)有所不同 ,這其中 webFlux 操作和傳遞的對象是 非阻塞式 ServletHttpRequest 和 ServletHttpResponse 而不是 傳統(tǒng) Spring MVC 所操作 阻塞式的 HttpServletRequest 與 HttpServletResponse 對象
@RestController
public class PersonController {
private final PersonRepository repository;
public PersonController(PersonRepository repository) {
this.repository = repository;
}
//從前臺接收json/xml類型數(shù)據(jù)
@PostMapping("/person")
Mono<Void> create(@RequestBody Publisher<Person> personStream) {
return this.repository.save(personStream).then();
}
//得到一個集合
@GetMapping("/person")
Flux<Person> list() {
return this.repository.findAll();
}
//RestFul 風格傳參
@GetMapping("/person/{id}")
Mono<Person> findById(@PathVariable String id) {
return this.repository.findOne(id);
}
}
2.函數(shù)式編程風格(HandlerFunctions RouterFunctions)
??2.1 HandlerFunctions http請求處理函數(shù),本質上是一個 傳入參數(shù)類型為ServerRequest
返回類型為Mono<ServerResponse>函數(shù),在Spring MVC 中與之相對應的就是被@RequestMapping 注解所修飾的方法.ServerRequest
并且ServerResponse
接口可以提供JDK-8(Lambda)對底層HTTP消息的友好訪問。對程序來說 響應流 暴露在Flux
或Mono
; 請求流以 Publisher
作為主體才沧。
下面是對Request 和 Response操作
1.取出Request body 到 Mono<String>
Mono <String> string = request.bodyToMono(String.class);
2.取出Request body 到 Flux 如果body內容是json 或者是 xml 可以將其反序列化為相應的實體類 這里的person 假設為一個實體類
Flux <Person> people = request.bodyToFlux(Person.class)
3.bodyToMono
和bodyToFlux
是 ServerRequest.body(BodyExtractor)的簡寫 上面兩段代碼相當于
Mono <String> string = request.body(BodyExtractors.toMono(String.class);
Flux <Person> people = request.body(BodyExtractors.toFlux(Person.class);
4.同樣 相應ServletResponse操作類似 該對象可以 通過其內部方法build.
Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
5.構建一個響應碼為201的 ServerResponse(自己百度201啥意思)
URI location = "http:*****";
ServerResponse.created(location).build();
- Hello World functionHandler (接收一個ServerRequest 返回一個ServerResponse )
HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().body(fromObject("Hello World"));
7.綜合示例
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;
}
//查詢出一個 people 集合 并將這個集合 序列化到響應體中
public Mono<ServerResponse> listPeople(ServerRequest request) {
Flux<Person> people = repository.allPeople();
return ServerResponse.ok().contentType(APPLICATION_JSON).body(people, Person.class);
}
//從請求體取出字符串(XML or Json 格式)反序列化為 實體對象
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class);
return ServerResponse.ok().build(repository.savePerson(person));
}
//請求參數(shù)在路徑中 rest風格
public Mono<ServerResponse> getPerson(ServerRequest request) {
//pathVariable
int personId = Integer.valueOf(request.pathVariable("id"));
// 404
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
Mono<Person> personMono = this.repository.getPerson(personId);
return personMono
.then(person -> ServerResponse.ok().contentType(APPLICATION_JSON).body(fromObject(person)))
.otherwiseIfEmpty(notFound);
}
}
??2.2 RouterFunctions 路由函數(shù)
傳入http請求轉入到 RouterFunctions 這個路由函數(shù)中, 如果請求匹配相應的路由函數(shù),這個路由函數(shù)接收一個ServerRequest
對象,并且返回一個Mono<HandlerFunction>
,如果匹配不到,則返回一個Mono<Void>
1.一般情況下我們不需要重寫一個路由函數(shù),而是使用RouterFunctions.route(RequestPredicate, HandlerFunction)
這個方法來創(chuàng)建router函數(shù),其中RequestPredicate
封裝了匹配規(guī)則,HandlerFunction 則可以理解為 匹配成功之后的回調函數(shù),匹配不到的話則返回404,一般情況下我們也不需要重寫RequestPredicate
,RequestPredicates
,這個實現(xiàn)類(多了個s)中已經封裝了常用的匹配規(guī)則,比如:基于路徑匹配,基于HTTP請求方法類型匹配(GET.PUT.POST.DELETE..),基于內容類型匹配(Content-Type)
示例1.
RouterFunction<ServerResponse> helloWorldRoute =
RouterFunctions.route(RequestPredicates.path("/hello-world"),//根據(jù)路徑匹配
request -> Response.ok().body(fromObject("Hello World")));//Handler Functions 處理函數(shù)
2.路由函數(shù)的組合 如果第一個RountHandler 匹配不成功,則進入的下一個RountHandler,匹配順序按代碼順序執(zhí)行
常用方法:
1. RouterFunction.and(RouterFunction)
2. RouterFunction.andRoute(RequestPredicate, HandlerFunction)
等價于以下的組合
2.1 RouterFunction.and()
2.2 RouterFunctions.route()
3.同路由函數(shù)的組合一樣 請求約束RequestPredicates
也可以類似的組合
//and 條件
1. RequestPredicate.and(RequestPredicate)
//or 條件
2. RequestPredicate.or(RequestPredicate)
3. RequestPredicates.GET(String) 等價于以下組合
3.1 RequestPredicates.method(HttpMethod)
3.2 RequestPredicates.path(String)
綜合示例
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);//函數(shù)引用
3.Running a Server
流程走到這就只差一步了 那就是在HTTP Server運行路由函數(shù),你可以用RouterFunctions.toHttpHandler(RouterFunction)
這個函數(shù)將路由函數(shù)轉換為一個HttpHandler
,這個HttpHandler
可以運行在很多種 具備響應式運行環(huán)境的容器,比如
Reactor Netty, RxNetty, Servlet 3.1+, Undertow
Reactor Netty 下示例:
//構建一個路由函數(shù)
RouterFunction<ServerResponse> route = ...
//轉換為 HttpHandler
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
//Reactor Netty 適配器
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
//構建一個簡單的Netty HTTP服務器,監(jiān)聽提供的地址和端口绍刮。
HttpServer server = HttpServer.create(HOST, PORT);
//
server.newHandler(adapter).block();
Tomcat 示例:啥玩意啊沒看懂 也不準備用Tomcat了
RouterFunction<ServerResponse> route = ...
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
HttpServlet servlet = new ServletHttpHandlerAdapter(httpHandler);
Tomcat server = new Tomcat();
Context rootContext = server.addContext("", System.getProperty("java.io.tmpdir"));
Tomcat.addServlet(rootContext, "servlet", servlet);
rootContext.addServletMapping("/", "servlet");
tomcatServer.start();
4.HandlerFilterFunction
顧名思義這個這函數(shù)類似于 Servlet 中的 Filter ,在路由函數(shù)進行路由之前執(zhí)行,本質上也類似與一個handler function , 過濾器執(zhí)行完可以指向 handlerFunction 或者 另一個 HandlerFilterFunction(形成過濾器鏈),示例如下
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();
}
});
5.Client Side
WebClient client = WebClient.create("http://example.com");
Mono<Account> account = client.get()
.url("/accounts/{id}", 1L)
.accept(APPLICATION_JSON)
.exchange(request)
.then(response -> response.bodyToMono(Account.class));
參考鏈接:
1.https://docs.spring.io/spring/docs/5.0.0.BUILD-SNAPSHOT/spring-framework-reference/html/web-reactive.html
2.https://coyee.com/article/12086-spring-5-reactive-web