Spring MVC函數式接口Router Function你用過嗎?
環境:Springboot2.4.12
概述
Spring Web MVC包括WebMvc.fn,這是一種輕量級函數式編程模型,其中函數用于路由和處理請求,參數及返回值設計為不可變。它是基于注釋的編程模型的替代方案,但在其他方面還是運行在同一DispatcherServlet上。
在WebMvc.fn中,HTTP請求由HandlerFunction處理:該函數接受ServerRequest并返回ServerResponse。請求和響應對象都有不可變,提供對HTTP請求和響應的JDK 8友好訪問。HandlerFunction相當于基于注釋的編程模型中@RequestMapping方法的主體。
傳入的請求通過RouterFunction路由到處理程序函數:一個接受ServerRequest并返回可選HandlerFunction(即可選的<HandlerFunction>)的函數。當路由器函數匹配時,返回處理函數;否則為空可選。RouterFunction相當于@RequestMapping注釋,但主要區別在于路由器函數不僅提供數據,還提供行為。
示例:
@Configuration
public class PersonHandlerConfiguration {
@Bean
public RouterFunction<ServerResponse> person() {
return route().GET("/person", accept(MediaType.APPLICATION_JSON), request -> {
return ServerResponse.status(HttpStatus.OK).body("Hello World") ;
}).build() ;
}
}
我們需要在一個@Configuration配置類中將RouterFunction暴露為Bean對象即可。
GET方法的3個參數:
第一個:請求的接口地址。
第二個:謂詞也就是限定哪些調用能夠匹配上,這個類似注解接口@RequestMapping參數中的consumer,params等屬性一樣。
第三個:HandlerFunction,這就是處理器對象了,實際的業務方法的處理對比@RequestMapping的方法體了。
HandlerFunction對象
ServerRequest和ServerResponse是不可變的接口,提供對HTTP請求和響應的JDK8友好訪問,包括頭、正文、方法和狀態代碼。
- ServerRequest
ServerRequest提供對HTTP方法、URI、頭和查詢參數的訪問,而對主體的訪問是通過主體方法提供的。如下示例:
@Bean
public RouterFunction<ServerResponse> student() {
return route().GET("/student/{id}", accept(MediaType.APPLICATION_JSON), request -> {
return ServerResponse.ok().body("name = " + request.param("name").get() + ", id = " + request.pathVariable("id")) ;
})
.POST("/student", accept(MediaType.APPLICATION_JSON), request -> {
return ServerResponse.ok().body(request.body(Student.class)) ;
})
.build() ;
}
GET接口獲取查詢參數和路徑上的參數。
POST接口獲取body主體內容。
- ServerResponse
ServerResponse提供對HTTP響應的訪問,因為它是不可變的,所以可以使用構建方法來創建它。可以使用生成器設置響應狀態、添加響應標題或提供正文。
在上面的示例中已經看到了如何使用,這里就不再給出示例了。
- Handler Classes處理器類
將處理器類單獨定義到一個文件中進行相應的處理,這就與傳統的@RestController注解類似了將很多的接口方法都定義在一個Controller類中。
示例:
@Configuration
public class PersonHandlerConfiguration {
@Resource
private PersonHandler ph ;
@Bean
public RouterFunction<ServerResponse> person() {
return route()
.GET("/person/{id}", accept(MediaType.APPLICATION_JSON), ph::queryPerson)
.POST("/person", accept(MediaType.APPLICATION_JSON), ph::save)
.build() ;
}
}
處理器類(在該類中可以注入DAO類進行相關的數據庫操作)
@Component
public class PersonHandler {
public ServerResponse save(ServerRequest request) throws Exception {
return ok().body(request.body(Person.class)) ;
}
public ServerResponse queryPerson(ServerRequest request) throws Exception {
return ok().body(new Person(Integer.valueOf(request.pathVariable("id")), "中國")) ;
}
}
- Validation驗證
可以使用Spring的驗證工具將驗證應用于請求主體。例如,給定一個人的自定義Spring驗證器實現。示例:
@Component
public class PersonHandler {
@Resource
private Validator validator ;
public ServerResponse save(ServerRequest request) throws Exception {
Person person = request.body(Person.class) ;
Errors errors = validate(person) ;
if (errors == null) {
return ok().body(person) ;
}
return ok().body(errors.toString()) ;
}
private Errors validate(Person person) {
Errors errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
return errors ;
}
return null ;
}
}
需要引入依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
關于參數驗證《Springboot項目中你的參數都在如何驗證?這個starter你知道嗎?》
RouterFunction
路由器功能用于將請求路由到相應的HandlerFunction。通常,你不會自己編寫路由器函數,而是使用RouterFunctions類上的方法來創建路由器函數。RouterFunctions.route()(無參數)為你提供了創建路由器函數的流暢生成器,而RouterFunctions.route(RequestPredicate,HandlerFunction)則提供了創建路由器的直接方法。
通常,建議使用route() 構建,因為它為典型映射場景提供了方便的捷徑,而不需要難以發現的靜態導入。例如,router function builder提供方法GET(String,HandlerFunction)為GET請求創建映射;和POST(字符串、HandlerFunction)用于POST。
除了基于HTTP方法的映射之外,route builder還提供了一種在映射到請求時引入額外謂詞的方法。對于每個HTTP方法,都有一個重載變量,該變量將RequestPredicate作為參數,通過它可以表示額外的約束。
在上面的示例中已經看到了在做相應GET,POST方法時傳的第二個參數。
- 謂詞Predicate
我們可以編寫自己的RequestPredicate,但是RequestPredicates類提供了基于請求路徑、HTTP方法、內容類型等的常用實現。如下示例通過Accept來限定了能夠接收的數據類型。
import static org.springframework.web.servlet.function.RequestPredicates.accept;
@Bean
public RouterFunction<ServerResponse> hello() {
return route().GET("/hello", accept(MediaType.APPLICATION_JSON), request -> {
return ServerResponse.status(HttpStatus.OK).body("Hello World") ;
}).build() ;
}
還可以通過and 或者 or 來添加多個謂詞
accept(...).and() || or()
- 嵌套路由
在傳統的Controller定義時,可以在類上加@RequestMapping("/person")注解的方式來統一請求接口的前綴。在函數式接口中我們可以通過如下的方式設定:
@Bean
public RouterFunction<ServerResponse> nestPerson() {
return route()
.path("/persons", builder -> builder
.GET("/{id}", accept(MediaType.APPLICATION_JSON), ph::queryPerson)
.POST("/save", ph::save))
.build();
}
通過path定義了路由的前綴。也可以通過如下方式:
@Bean
public RouterFunction<ServerResponse> nestPerson2() {
return route()
.path("/persons2", b1 -> b1
.nest(accept(MediaType.APPLICATION_JSON), b2 -> b2
.GET("/{id}", accept(MediaType.APPLICATION_JSON), ph::queryPerson))
.POST("/save", ph::save))
.build();
}
HandlerMapping
這種函數式接口既然底層還是使用的DispatcherServlet ,那么它就會有對應的HandlerMapping和Adapter
RouterFunctionMapping:檢測Spring配置中的一個或多個RouterFunction<?>bean,對它們進行排序,通過RouterFunction.andOther組合它們,并將請求路由到生成的組合RouterFunction。
HandlerFunctionAdapter:讓DispatcherHandler調用映射到請求的HandlerFunction的簡單適配器。
過濾器
可以使用路由函數生成器上的before、after或filter方法篩選處理程序函數。對于接口注釋的方式,我們可以通過使用@ControllerAdvice、ServletFilter或兩者來實現類似的功能。過濾器將應用于生成器生成的所有路由。這意味著嵌套路由中定義的過濾器不適用于“頂層”路由。示例:
@Bean
public RouterFunction<ServerResponse> nestPerson2() {
return route()
.path("/persons2", b1 -> b1
.nest(accept(MediaType.APPLICATION_JSON), b2 -> b2
.GET("/{id}", accept(MediaType.APPLICATION_JSON), ph::queryPerson)
.before(request -> ServerRequest.from(request).header("x-pack", "123123").build()))
.POST("/save", ph::save))
.after((request, response) -> {
System.out.println("after execution..." + response.statusCode());
return response ;
})
.filter((request, next) -> {
if (request.pathVariable("id").equals("100")) {
return ServerResponse.ok().body("參數錯誤") ;
} else {
return next.handle(request) ;
}
})
.build();
}
public ServerResponse queryPerson(ServerRequest request) throws Exception {
System.out.println(request.headers().header("x-pack")) ;
return ok().body(new Person(Integer.valueOf(request.pathVariable("id")), "中國")) ;
}
before:添加了自定義header信息,然后我們可以在queryPerson中獲取到,并且該before只能應用于當前這個嵌套的路由中。
after:能夠應用所有的路由中
filter:filter方法采用HandlerFilterFunction:采用ServerRequest和HandlerFunction并返回ServerResponse的函數。handler函數參數表示鏈中的下一個元素。這通常是路由到的處理程序,但如果應用了多個過濾器,它也可以是另一個過濾器。
Swagger
此時的Swagger沒有用了,所以你的慎用啊。
完畢!!!