接續上一篇RestController轉成Reactive,可以看到邏輯的部分已經被抽到Handler內了,剩下就是路徑轉導就是交由Router來處理。
首先看RouterFunctions.java,大部分s結尾的都是工具類別,像是Collections、Arrays,RouterFunctions內有提供route,傳入predicate&handlerFunction,很直覺得理解就是符合predicate條件則交由handlerFunction處理,這邊predicate是RequestPredicate,傳入的參數就是ServerRequest。
public static <T extends ServerResponse> RouterFunction<T> route(
RequestPredicate predicate, HandlerFunction<T> handlerFunction) {
return new DefaultRouterFunction<>(predicate, handlerFunction);
}
很直覺得轉換後,將原本的annotation-based的寫法轉換如下,包含了路徑與HttpMethod,ServerRequest本身有提供取得路徑(path(),與method()可以取得HttpMethod。
RouterFunctions.route(request ->
request.path().equals("/router/greeting") && HttpMethod.GET.equals(request.method()), greetingHandler::allGreeting);
接下來有一個好用的工具類別RequestPredicates.java裡面可以看到很多方便的function,上面的條件判斷都可以被簡化,像是比對HttpMethod的method(),比對路徑的path(),就像是整合後的@GetMapping()一樣,同樣有一個GET()整合了path跟get,對於header也都有支援,contentType、accept,這些條件(RequestPredicate)可以透過RequestPredicate.java內的default function (and())來串接,剩餘其他好用的方法,之後有遇到再來介紹。
public abstract class RequestPredicates {
...
public static RequestPredicate method(HttpMethod httpMethod) {
return new HttpMethodPredicate(httpMethod);
}
public static RequestPredicate path(String pattern) {
Assert.notNull(pattern, "'pattern' must not be null");
if (!pattern.isEmpty() && !pattern.startsWith("/")) {
pattern = "/" + pattern;
}
return pathPredicates(PathPatternParser.defaultInstance).apply(pattern);
}
public static RequestPredicate GET(String pattern) {
return method(HttpMethod.GET).and(path(pattern));
}
public static RequestPredicate contentType(MediaType... mediaTypes) {
Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty");
return new ContentTypePredicate(mediaTypes);
}
public static RequestPredicate accept(MediaType... mediaTypes) {
Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty");
return new AcceptPredicate(mediaTypes);
}
...
}
經過整理後,再將另外兩個也一併改寫,再透過RouterFunction.java內的default function (and())來串聯(是不是跟上面RequestPredicate有異曲同工之妙)。
RouterFunctions
.route(GET("/router/greeting/{id}"), greetingHandler::getGreeting);
RouterFunctions
.route(POST("/router/greeting").and(contentType(APPLICATION_JSON)), greetingHandler::saveGreeting);
RouterFunctions
.route(GET("/router/greeting"), greetingHandler::allGreeting);
////////////////////////////////
RouterFunctions.route(GET("/router/greeting/{id}"), greetingHandler::getGreeting)
.and(
RouterFunctions.route(
POST("/router/greeting").and(contentType(APPLICATION_JSON)),
greetingHandler::saveGreeting))
.and(RouterFunctions.route(GET("/router/greeting"), greetingHandler::allGreeting));
再來可以很明顯可以看出and之後都是接route,而且沒有一個像是掛在class層的@RequestMapping()可以把相同的路徑往上抽,所以還差最後的一步驟整合
RouterFunction.java 幫你整合好的andRoute
default RouterFunction<T> andRoute(RequestPredicate predicate, HandlerFunction<T> handlerFunction) {
return and(RouterFunctions.route(predicate, handlerFunction));
}
RouterFunctions.java nest(),他可以把整個共同的部分統一在第一個參數(predicate),後面就放相同類型的routerFunction
public static <T extends ServerResponse> RouterFunction<T> nest(
RequestPredicate predicate, RouterFunction<T> routerFunction) {
return new DefaultNestedRouterFunction<>(predicate, routerFunction);
}
成果如下,將相同的/router/greeting往上抽到第一層nest,兩個get都需要有accept,抽到第二層的nest,最後Post與accept無關所以並不在第二層nest範圍內。
GreetingRouter.java
public class GreetingRouter {
@Bean
public RouterFunction<ServerResponse> routerFunction(GreetingHandler handler) {
return nest(
path("/router/greeting"),
nest(
accept(APPLICATION_JSON),
route(GET("/{id}"), handler::getGreeting)
.andRoute(method(HttpMethod.GET), handler::allGreeting))
.andRoute(POST("/").and(contentType(APPLICATION_JSON)), handler::saveGreeting));
}
}
經過這兩天的學習,大致上能掌握將controller轉換成reactive的方式,相信大部分的情境應該都可以在介紹的工具類別裡面找到方法處理。