上一篇,
IF-ELSE
清除计划之管道风云
,诸位老哥看了我一顿乱扯,一锅乱炖,就像我上篇总结的一样,我其实是描述了 IF-ELSE
清除计划的前置条件,具有管道的意识,然后我们继续执行清除计划,这篇我们着重谈谈怎么通过 Optional
和 Function
实现管道的短路和流转
异常向北(上),流转向南(下)!如上篇所述,管道模式是有 Valve
( 阀门
)和 Status
( 状态
)的,这两个组件主要是控制代码的流转,也就是真实用来清理 IF-ELSE
的实现,但是我们 CRUD-BOYS
使用的是极简模式的管道,并没有加入这些组件,那我们处理 IF-ELSE
其实是通过以下几种方式
Java8
的新特性终于让我们可以使用函数式编程了,同时也提供了很多非常优秀的工具,比如我们今天的主角 Optional
,他是用于进行控制判断的工具,很多文章都是这么说的。。。但是如果你仔细看看它的 api
,你会发现它其实一点也不简单,让我们看看今天会使用的几个方法
//用于过滤,可以有效清理`IF-ELSE` public Optional<T> filter(Predicate<? super T> predicate) { Objects.requireNonNull(predicate); if (!isPresent()) return this; else return predicate.test(value) ? this : empty(); } //用于装载极简管道,注意其实可以使用andThen多级连接 public<U> Optional<U> map(Function<? super T, ? extends U> mapper) { Objects.requireNonNull(mapper); if (!isPresent()) return empty(); else { //这里返回的还是Optional return Optional.ofNullable(mapper.apply(value)); } } 复制代码
//嘿嘿,炒一份冷饭,就是极简管道的核心方法 default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } 复制代码
为了方便大家理解,我们使用一个稍微复杂场景来进行举例,同时也是为了方便大家能够在实际的业务中使用,减少 知行合一
的成本,更好的方便实践,这个场景就是使用 JWT
进行权限校验, Token
存放在请求头中,然后校验 Token
的有效性,获取用户的信息,接着获取用户的权限,校验当前 Url
是否在用户的权限中,我们试试校验Token有效性
public static void main(String[] args) { HttpServletRequest request = new HttpServletRequestWrapper(null); //如果不拼接极简管道,会有大量的`IF-ELSE`出现 //而且这边的逻辑本质就是校验Token是否有效 String headerToken = request.getHeader(HttpHeaders.AUTHORIZATION); //流转逻辑一直->向南 String userId = Optional.ofNullable(headerToken) //分支管道,干掉IF-ELSE .filter(StringUtils::isNoneBlank) //极简管道拼接 .map(parseUserIdFromToken().andThen(gainCacheFromRedis())) //这个比较其实是防止Token泄露等异常情况 .filter(cacheToken -> StringUtils.equals(headerToken, cacheToken)) //校验完成后,再获取一次UserId //其实可以在上一个Map直接封装一层,这边只做演示,不做复杂拓展 .map(parseUserIdFromToken()) //所有的逻辑都由该异常->向北 这是异常管道 .orElseThrow(() -> new RuntimeException("Token异常!")); //todo 继续通过UserId获取用户权限 } /** * 模拟JWT解析 * * @return 从Token中解析UserId */ private static Function<String, String> parseUserIdFromToken() { return token -> "JWT解析UserId"; } /** * 模拟Redis获取缓存(分布式缓存简单实现) * * @return 从Redis中获取UserId对应的Token */ private static Function<String, String> gainCacheFromRedis() { return userId -> "通过Redis获取到的Token"; } 复制代码
当我把上篇文章发到某个神秘的技术群时,有大佬一针见血的指出了这种方式其实也是 IF-ELSE
,本质上也是条件判断,在重新审视代码和逻辑后发现确实如此,但是我依然要指出这样处理 IF-ELSE
的好处,即管道之间的 IF-ELSE
逻辑是隔离的,校验 Token
有效性的管道部分和通过 UserId
获取用户权限的管道之间的条件判断是隔离的,而不是堆在一起形成一段又一段的 IF-ELSE
代码块,当然,这只是一家之言,也只是初步思考以后的实现,肯定会有疏漏,初衷也是期望对大家有所帮助,也希望有大佬可以提出更优美的解法,造福我等 CRUD-BOY
,最后谢谢您的阅读,祝端午安康,阖家幸福
最近其实有机会接触并使用了 Spring Webflux
实现的 Spring Cloud Gateway
,这是 Spring
基于 Reactor
的响应式编程实现,在 Spring Webflux
的场景中,所有的处理结果在 subscribe
之前都是不可直接读取,因为 subscribe
是会 block
当前线程的,这在响应式编程之中是不允许的,所以在 Reactor
中数据流转就是流式的, IF-ELSE
可以控制到出现得很少,而所有的数据必须被 Mono
和 Flux
包裹,而 Mono
和 Flux
的 api
是管道模式的大成者,可以做到一段到底,绝不分割,下面贴出 Spring Webflux
一个小例子,大家可以尝试借鉴一哈
String hello = Mono .just("HelloWorld") //可以直接进行判断 .switchIfEmpty(Mono.just("HelloReactor")) //可以设置默认值 .defaultIfEmpty("MyMan") //可以转换其他值 .flatMap(word -> Mono.defer(() -> Mono.just("HelloPipeline"))) //延迟操作 .doOnSuccess(System.out::println) .doOnError(System.out::println) .block(); System.out.println(hello); 复制代码