转载

异常向北,流转向南(IF-ELSE 清除计划续)

上一篇, IF-ELSE 清除计划之管道风云 ,诸位老哥看了我一顿乱扯,一锅乱炖,就像我上篇总结的一样,我其实是描述了 IF-ELSE 清除计划的前置条件,具有管道的意识,然后我们继续执行清除计划,这篇我们着重谈谈怎么通过 OptionalFunction 实现管道的短路和流转

切题

异常向北(上),流转向南(下)!如上篇所述,管道模式是有 Valve阀门 )和 Status状态 )的,这两个组件主要是控制代码的流转,也就是真实用来清理 IF-ELSE 的实现,但是我们 CRUD-BOYS 使用的是极简模式的管道,并没有加入这些组件,那我们处理 IF-ELSE 其实是通过以下几种方式

  • 阀门管道(执行断路逻辑)以及通路管道(空值逻辑传递)
  • 异常管道抛出异常使得执行向北,其实就是向上层抛出异常(抛给外部异常捕获框架)
  • 正常逻辑走其他分支管道,流转向南,继续往下执行

来源

Optional

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));
        }
    }
复制代码

Function

//嘿嘿,炒一份冷饭,就是极简管道的核心方法
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 可以控制到出现得很少,而所有的数据必须被 MonoFlux 包裹,而 MonoFluxapi 是管道模式的大成者,可以做到一段到底,绝不分割,下面贴出 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);
复制代码
原文  https://juejin.im/post/5ef702c9e51d45349e2540e7
正文到此结束
Loading...