我在新公司做的第一个技术优化原创
你好,我是yes。
我相信大部分人到新公司都会有阵痛,不管是环境的改变、流程的不适、新同事的磨合还是项目上的熟悉。
关于这方面的我到时候再写一篇文章,今天来盘盘我看到的项目里面的一个小小优化点。
起因
入职后比较重要一点就是快速上手项目,也就是熟悉需求以及代码,作为一个新人我肯定是能毫无感情的发现项目里诸多奇怪的点(不会管历史遗留包袱,因为我根本不知道)。
当然,作为新人你不能过于激进地用批判的眼光去看一切,存在即合理。
不过我发现公司项目里有一系列类确实是冗余的,这些类的功能纯粹是为了包装 dubbo 的接口来对 RPC 调用进行统一的 catch。
所以只要有一个依赖外部项目的 rpc 接口,对应就有一个包装的 service。
我简化下包装的 serivce 逻辑,咱们看下代码就清晰了。
比如要调用远程的订单服务,对应的项目里就创建了一个订单服务包装类,拿通过 id 获取订单方法来看,内部逻辑如下所示:
public OrderDTO getOrderById(Long id) {
Response res;
try {
res = orderSerivce.getOrderById(id); //orderService是外部服务接口
} catch (Exception e) {
logger.error("远程服务抛错,根据 ID 获取订单异常, ID: {}", id);
throw new RpcInvocationException(e);
}
if (!res.isSuccess()) {
logger.error("远程服务业务抛错,根据 ID 获取订单失败, ID: {}", id);
throw new RpcBusinnessException(res.getErrorInfo());
}
return res.getResult();
}
包装类大概就是上面这个意思,这个包装意义就是为了判断调用下游接口的报错到底异常还是失败。
如果是异常的话说明下游出现了预知外的错误,属于 RPC 调用出错,而失败指的是预知内的错误,属于业务错误。
这样一来通过报错就可以很方便的辨别调用下游接口的情况,便于问题的排查。
但是我们想想,一个服务依赖诸多下游接口,每来一个我们都为之建一个类来封装之,是不是很麻烦?
100 个外部接口我们就需要创建 100 个包装类。
解决方案
那如何解决这个麻烦呢?我们想想可以有哪几种方案:
- 代码生成器
- aop
- dubbo filter
代码生成器这个属于定制化插件开发了。
想必我们应该都用过 idea 里面的 Generate 吧?就是在这里做个扩展。
像没 lombok 前, getter 和 setter 都是用它来生成的,还有我们的单元测试类的生成等等。
也就是说写个插件,在此扩展,根据模板解析对应的 dubbo 接口,一键生成对应的类。
这个方案比较麻烦,要熟悉插件的开发,这是一个成本,而且虽然重复的代码不需要人为去写,但是项目中还是会生成很多里面的逻辑看起来都是重复的包装类。
当然,如果你们项目组已经在维护一个自己的插件了,那么可以基于此扩展,这种生成出来的代码也更容易定制修改逻辑,比如可能要为某个 rpc 接口编写专门特殊的逻辑等。
AOP
这个就不说了,常规代理操作,想必大家项目里或多或少都会用到 Spring AOP 功能。
Dubbo Filter
这是我们今天这篇文章要讲的重点。
其实到公司一堆包装类的代码,我脑子第一个蹦出来想法就是基于 Dubbo 的 Filter 进行扩展。
因为我们用的是 dubbo 框架,其次上述包装 service 仅是为了解析返回的 rpc 结果,里面的逻辑高度重复。
从高度重复我们就要想到抽象封装,减少重复冗余代码。
从目的来说我们仅为了解析 rpc 结果进行 catch,方便判断是下游异常还是业务抛错。
所以我们只要统一在 dubbo 调用结果返回之前进行拦截判断,就可以去除这些冗余的代码。
此时就要联想到 dubbo 遗留了哪些扩展点供我们增强功能呢?
那就是 dubbo spi,在这个场景再具体些就是里面的 Filter 机制。
在这篇文章dubbo 靠它崭露头角里,我已经详细剖析了 spi 机制,这篇不深入 spi 逻辑,仅结合 Filter 简单讲下用法。
自定义 Filter 实战
首先我们看下 Dubbo 提供的 Filter 接口,这里的源码是 2.7.5 版本。
我们的第一步就是实现 Filter 接口,写一个自定义的异常类。
ps:这里的为了方便说明,我写的自定义 Filter 逻辑比较简单,生产上使用的话还是要细化很多东西的
需要注意的几个点我已经用红框标注了。
@Activate(group = CommonConstants.CONSUMER) 这个表明仅在消费端生效,我们的目的就是消费端消费的时候对结果进行解析。
实现 Filter 和 Filter.Listener,老版本的例子应该会实现 Filter 里面的 onResponse ,不过既然已经标注废弃了,我们就别用了。
onMessage 里面就是上面例子的逻辑了,可以看到还是很简单的,dubbo 也封装的很好,直接根据 appResponse 就能判断是否有异常,然后进行日志的记录和异常的封装。
实现完自定义 Filter 之后,如何应用到我们的项目中呢?
也非常简单,SPI 都帮我们封装好了。
我们仅需在项目的 /resources/META-INF/dubbo 目录下添加一个 SPI 配置文件,文件名就是 Filter 路径 org.apache.dubbo.rpc.Filter,表明我们要扩展的是 Filter。
而文件里面的内容就是我们自定义的 Filter 路径,具体如下图所示:
图片dubbo 在加载的时候会扫描上述的目录,找到 Filter 扩展,并根据文件内容加载我们自定义 Filter ,这样过滤链上就有我们的罗家啦,即自定义 Filter 就生效了。
自定义 Filter 使用示例
我在消费端简单写个消费逻辑,就是调用 provider 的方法:
然后在 provider 端对应方法里模拟业务抛错:
调用消费端方法可以看到 Filter 生效了:
然后我们再在 provider 端对应方法里模拟异常抛错:
此时再调用消费端方法可以看到也成功处理了:
最后
好了,这个小优化到此结束。
通过开源框架提供的扩展机制其实可以方便的处理很多问题,一般脱颖而出的框架肯定会预留很多供我们定制化的手段,所以我们需要好好掌握这些框架,这样可以提高我们平日开发的效率,写起代码来更加得心应手与灵活。
这里提一下,其实 dubbo 自身已经实现了很多 Filter 类供我们使用:
图片其实看名字能猜出很多 Filter 的作用,这里我就不一一介绍了,有兴趣的小伙伴可以自己去看看,避免重复造轮子,使用方法和自定义的一样,也是利用 SPI 机制。
好嘞,今天的分享到此结束,这段时间我在负责一个反射框架的开发,等后面写完好了我再来分享下。
我是yes,从一点点到亿点点,我们下篇见~
💥看到这里的你,如果对于我写的内容很感兴趣,有任何疑问,欢迎在下面留言📥,会第一次时间给大家解答,谢谢!