[Day 24] Reactive Programming - Spring WebFlux(Router)

前言

接续上一篇RestController转成Reactive,可以看到逻辑的部分已经被抽到Handler内了,剩下就是路径转导就是交由Router来处理。

Router

首先看RouterFunctions.java,大部分s结尾的都是工具类别,像是CollectionsArraysRouterFunctions内有提供route,传入predicate&handlerFunction,很直觉得理解就是符合predicate条件则交由handlerFunction处理,这边predicateRequestPredicate,传入的参数就是ServerRequest

public static <T extends ServerResponse> RouterFunction<T> route(
  RequestPredicate predicate, HandlerFunction<T> handlerFunction) {

 return new DefaultRouterFunction<>(predicate, handlerFunction);
}

很直觉得转换後,将原本的annotation-based的写法转换如下,包含了路径与HttpMethodServerRequest本身有提供取得路径(path(),与method()可以取得HttpMethod

RouterFunctions.route(request ->
    request.path().equals("/router/greeting") && HttpMethod.GET.equals(request.method()), greetingHandler::allGreeting);

接下来有一个好用的工具类别RequestPredicates.java里面可以看到很多方便的function,上面的条件判断都可以被简化,像是比对HttpMethodmethod(),比对路径的path(),就像是整合後的@GetMapping()一样,同样有一个GET()整合了pathget,对於header也都有支援,contentTypeaccept,这些条件(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,最後Postaccept无关所以并不在第二层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的方式,相信大部分的情境应该都可以在介绍的工具类别里面找到方法处理。

资料来源

<<:  [Day23] 如何让 Scrum Master 的经历为履历加分

>>:  Day24 [实作] 一对一视讯通话(4): 加入通话及挂断机制

[Day 6]中秋时在做什麽,有没有空,可以帮想标题吗(前端篇)

今天要先来看Native Camp的流程,首先是点击注册後的页面, 有信箱和密码,还有SSO的选项,...

[day5] Python发送Request接收Response与永丰API串接参数

Python实作 Request发送 如果你的Python环境没有requests模组 pip in...

Python - Python Selenium 套件使用参考笔记

Python - Python Selenium 套件使用参考笔记 参考资料 Selenium-Py...

[Day3] MacOS - 操作上手2

今天继续记录一些 Mac的功能。 四角设定 非常好用的功能,只要将滑鼠移动到画面4个角落,就可以触发...

Day 28: Divide and Conquer

这是什麽 分而治之,分治法! 分治法的步骤是: 将一个问题拆解成多个可以处理的小问题後 处理、击破每...