cover_image

一种业务耦合的分治方案设计

慕柯 闲鱼技术
2022年02月10日 02:54

前言

在业务迭代的过程中,避免不了业务代码交叉混排的情况,同一个调用时机出现各种不同业务或功能的代码。我们需要将调用点作为能力抽象出来,从而成为扩展点,将原本串行交叉的代码进行抽离,回到真正属于它的归处。本文聚焦于实现一种Android技术框架,以尽可能小的开发成本,实现混排业务分治的能力。


方案设计

设计原则

基于接口实现对通用能力的抽离,进行耦合拆分。面向接口编程,通过一定的抽象,保证业务分治的同时,还可以保证扩展性。

  1. 开闭原则,基于扩展点的设计,方便地进行扩展,无需修改原类;

  2. 依赖倒置,面向接口编程,不依赖具体的实现类;

  3. 接口隔离,将功能抽象成多个隔离的接口,降低耦合;

设计方案

图片

  1. 有A、B、C三个模块,B和C存在相互依赖、相互调用的情况,且在A模块中被交叉调用。

  2. 由A抽离扩展点能力到 A_Interface,B和C在A中的调用,就变成了 A_Interface 的实现类在A中的调用了,确保A不依赖B和C的具体实现。

  3. 将B、C各分成两层,上层 implement 为业务实现层,代码封闭不对外暴露。下层 interface 为接口层,会对外部公开,可以被外部依赖,表示对外提供的接口能力。B和C之间通过相互提供的接口能力调用。

  4. 分层之后,implement 实现层之间的依赖耦合被去除,只会依赖接口层。

  5. 将接口和实现的映射关系保存到工具API类中,实现层通过工具API类实现相互调用。


Chain框架

框架设计

图片

  • 实现类标注自己实现的接口和名字,生成一种索引关系,将索引保存到API单例类 ChainBlock 中。

  • ChainBlock 通过 interface.class 和 key 唯一索引到一个实现类上,调用实现类的接口方法。

编译期索引生成

将接口类和实例对象的索引关系,通过手动调用的方式注册到 ChainBlock 的单例中,会变得相当繁琐。如果编译时可以扫描到所有注解类,通过注解生成索引关系,这样在运行时就可以直接使用,大大降低了使用成本。

图片

  1. 有A、B、C三个 module,拆成独立的库,分别编译成aar集成到主工程。

  2. 在每个 module 中,通过@Chain标注需要通过接口公开的实现类上,被注解的类会在子库编译成aar时,通过 AnnotationProcessor 和 javapoet 在编译期被索引到 XChainImpl.class 中,每个子库都会生成对应的XChainImpl.class。

  3. 在主工程编译时,通过自定义的 plugin,通过 transform、asm、javassist,将子库所有的索引类XChainImpl.class 插桩到指定的 ChainJoint.class 中,实现对索引类的聚合。

  4. 在运行时,ChainBlock工具类可以从 ChainJoint 中获取所有的索引,Amodule 就可以通过 ChainBlock 获取 Bmodule 中注解了@Chain的实现类了。

运行时API实现

  1. 添加注解 @ChainHost、@Chain、@Block,分别对接口、实现类、方法进行注解,加上注解才能成为对外公开的API能力。

    图片

  2. 编译时索引,通过接口类、实现类名字,索引到唯一的实现类上。索引在编译时建立,不占用运行时效率。

    图片

  3. 运行时调用,通过ChainBlock工具类,完成具体实现类的调用。

    图片

  4. 详细API还包括,获取所有的Chain列表,以及通过动态代理的方式调用所有实现类的方法,甚至可以通过一个自定义的协议url调起某个公开实现类的方法。

    // 1. 调用指定ChainChainBlock.instance().obtainChain(IProvider.class, "A").log("hello");ChainBlock.instance().obtainChain(IProvider.class, "BProvider").log("hi");// 2. 获取所有ChainList<IProvider.class> list = ChainBlock.instance().getChainList(IProvider.class);// 3. 按优先级调用所有Chain,按方法上@Block注解的priority从高到低调用ChainBlock.instance().priorityListProxy(IProvider.class).log("hello");// 4. 通过chainblock自定义协议调用ChainBlock.instance().runChainBlock(    "chainblock://iprovider/A/log?text=hello", context);
  5. 编译时文档输出,将所有子库中添加了注解的接口和实现类在编译时输出到文档,用于查阅现有的一些能力。

    Generated by @Chain# interface com.taobao.idlefish.IProvider  > 描述接口的作用  1. A : { annotatedClass:com.taobao.idlefish.AProvider, singleton:false }  1. BProvider : { annotatedClass:com.taobao.idlefish.BProvider, singleton:false }  1. CProvider : { annotatedClass:com.taobao.idlefish.CProvider, singleton:false }
  6. 基于 Graphviz工具 的可视化输出,更直观看到ChainBlock的整体链路。图片

遇到的问题

  1. 索引关系覆盖问题

    • 通过 @Chain 注解添加的 name,有可能会重复,导致可能会覆盖其他Chain的索引,问题一旦出现,只能在运行时发现。

    • 解决办法:在编译时进行干预,在Plugin的transform里,扫描所有的jar包,通过URLClassLoader加载XXXChainImpl.class的索引类,将索引关系添加到 Map 里面,检查覆盖情况,发现覆盖直接终止编译报错,将问题在编译时提前暴露出来。

  1. 稳定性问题

    • @Chain 注解的实现类有可能会被删除,删除之后obtainChain(IProvider.class, "A").log("hello")方法调用会遇到空指针问题。

    • 解决办法:通过 obtainChain 返回的不是真正的实现类,返回的是动态代理的 Proxy 对象,代理到真正的实现类上,即使实现类为 null 了,调用代理类也不会报错。

  1. 编译时遇到的 broken jar 问题

    • 在 Gradle 编译时运行 Plugin 的时候,出现了broken jar 的错误

    • 解决办法:broken jar 出现的原因是读取了一个未被正常释放的 jar 包,可以通过./gradlew --stop临时解决。经过排查之后,发现 URLClassLoader 和 javassist 的 ClassPool 在加载类对象的时候,需要将 jar 包路径 add 进去,未被正常释放导致的问题,正确释放之后解决。

特点总结

  1. 接口下沉,基于接口的依赖解耦;

  2. 低侵入式,注解即可;

  3. 自动组件注册,无需手动管理;

  4. 自定义协议,支持跨进程调用;

  5. 自动报表文档输出,图形化展示;

  6. 混淆友好,不需要额外新增混淆规则;

最佳实践

不建议使用的场景:

  1. 如果代码可以直接调用到,没必要使用Chain。

  2. 如果代码属于业务无关的通用能力,可以直接下沉到底层库,没必要使用Chain。

建议使用的场景:

  1. Chain更适合业务能力的抽象,相互隔离的业务子库之间,有相关代码需要复用,可以通过Chain实现。

  2. 向外提供扩展点,通过接口约束扩展点的方法,可以使用Chain拿到所有实现类来批量处理,并且新增实现类的时候对扩展点是无感知的。比如JSBridge的调用分发,实现类可以通过Chain进行聚合分发。


基于Chain的业务改造

图片

  • 改造前,在 MainActivity 中各种业务调用相互穿插耦合,难以维护。

  • 改造后,抽象一个 IWorkflow 接口,将 MainActivity 中的生命周期能力提供出来,各业务方实现这个接口并添加注解,MainActivity 就可以通过 ChainBlock 工具将生命周期分发到各个业务实现上。各个业务模块代码可以收敛到一处,进行分治互不影响。


总结

目前Chain的能力在业务拆解过程中发挥了比较重要的作用,实现了各业务代码的分治,同时低侵入式的特性也大大降低了改造的成本。Chain可以作为组件化能力的补充,希望可以给大家带来一些启发和收获。


图片


🍊橙子说


闲鱼技术联合大淘宝技术

新春拜年


“虎虎虎”

纸质红包大派送


图片


扫码关注”淘系技术“回复"红包“即可获得领取方式

(2月28日18:00截止)


继续滑动看下一个
闲鱼技术
向上滑动看下一个