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

异常向北,流转向南(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);
复制代码

猜你喜欢

转载自juejin.im/post/5ef702c9e51d45349e2540e7