Transform+ASM实战--手动实现IOC

1,206 阅读7分钟
   绩效定了,钱也发了,虽然有些不如意,仍需努力吧。工作至今马上三年,也完成了自己金库可支配现金流的
   一个小目标,接下来就要减肥了,同时年纪也不小了,是时候考虑了,但是作为一个自卑且不自信的人,真的
   太难了,尽力而为吧。现在什么行情都不好,谁也不能保证自己可以一直干下去,我们能做的就是多学习,让
   自己的技术更加精湛,这个可能是当下最需要做的。而且也要早早的想后路了,程序员不可能干一辈子的,这
   个东西还是要在脑子里有一根弦的。
   
   最近看到罗翔老师说的一句话很喜欢,天道酬勤,在当今的社会可能我们不要辩证的去看,不是说努力了就一
   定会有收货的,我们需要管理自己的预期。其实人生中可能只有百分之五的事情使我们可以决定的,剩下的大
   头百分之九十五我们是决定不了的。但是我们仍需努力,我们能做的就是尝试用这微小的我们可以左右的可能
   性去撬动大部分的未知的未来。

问题

既然上篇说道了ASM+Transform,我们也仅仅是Demo级别的实践,那么有什么具体的应用场景吗?就是当前项目大规模用到的。答案是有的-IOC。(IOC就不具体的介绍了,可以自行百度一下。简单来说就是获取能力的实例对象,我们依赖接口,会有专门统一的管理容器给我们提供对象,这个其实是Spring后端常用的)

  • Android目前的项目为什么需要IOC呢?
  • IOC具体需要怎么实现呢?
    • 对象实例创建,需要手动吗?保障单例?
    • 对象什么时候把实例注入容器?
    • 模块比较多,分散在各个module,如何保证统一注册不遗漏?

Android目前的项目为什么需要IOC

当前现状

当前的APP越来越大了,我们都是模块化开发,比如首页模块,我的模块,基础能力有网络模块,安全加密模块,而且模块之间是有耦合的,比如首页和我的可能有一些页面要相互调用,而且他们都需要使用网络和安全加密模块,如果直接依赖,依赖关系会非常复杂,也可以看到模块的全貌,也不易于维护,这个时候参考Spring的IOC,其实android也可以实现自己的IOC。

第一步解耦

我们可以把模块拆分成链两个部分,接口 Interface+实现Impl两部分,(当然,如果拆分成两个Module也是可以的),如果首页模块,要使用网络,直接依赖网络的接口Module。这一步,我们就实现了业务之间的解耦,而且从Java设计的角度,我只关心我需要能力,模块其他的部分对我来说也是不可见的。

注入实现

我们实现了接口的依赖,但是看不到实现啊 ?没有实现我们怎么调用呢?这个时候就需要IOC了,如果 提供一个容器 ,我们传递依赖的接口就返回给我们需要的额实例是不是就可以了?是的,答案是可以的。我们让当前模块再次依赖IOC容器即可(暂时当成黑盒,后面会介绍),这样我们就可以获取到实例了。这样依赖不仅简单,而且我们需要什么能力,就依赖能力的接口就好。

IOC具体实现方案

这里我们也是讲方案,不会精细到每一行代码怎么实现。

生成实例对象代码
定义接口

接口的统一顶层接口类Service。如果要创建网络的接口,比如INetService,需要继承Service。后面会讲到。

同时定义register接口,目的是实现Service的注册,注册到IOC容器之中。

public interface Service {

}

public interface INetService extends Service {

}

public interface IServiceRegister {
  void register();
}
定义实现注解

构建生成代码的实现类注解。需要把这个注解用到具体Service的实现类。比如NetServiceImpl。

@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE})
public @interface InjectService {
  
}

@InjectService
public class NetServiceImpl implements INetService {
  ...
}
定义生成代码基类

为了实现单例,我们创建一个ServiceWrapper基类,这是一个包裹类,只有调用getInstance的时候,才会创建真正的Service实例,这样可以保证单例的同时也是延迟创建。(为什么getInstance的权限是package呢?因为这个类是在IOC的module的,getInstance我也不希望外界其他module感知并调用,你直接从容器中统一入口获取就好,这个getInstance的方法,会在ioc的module自己调用)

public abstract class ServiceWrapper<T> {

  private T mInstance;

  protected abstract T newInstance();

  final T getInstance() {
    if (this.mInstance == null) {
      synchronized (this) {
        this.mInstance = this.create();
      }
    }
    return this.mInstance;
  }

  private T create() {
    return newInstance();
  }

}
生成代码

这个我们可以通过JavaPoet来实现,如果有小伙伴不会,B站链接www.bilibili.com/video/BV1RW…

我们可以读取@InjectService注解,找到这个类NetServiceImpl,然后生成代码,如下:

首先生成NetServiceWrapper类,然后生成NetServiceRegister,其中的register方法,就是把Wrapper注入到IOC容器之中。很好理解。

public class NetServiceWrapper extends ServiceWrapper<INetService> {
  protected INetService newInstance() {
    return new NetServiceImpl();
  }
}

public class NetServiceRegister implements IServiceRegister {
  public void register() {
    ServiceManager.register(INetService.class, new NetServiceWrapper());
  }
}

为什么我们需要INetService继承Service呢,因为哈,NetServiceImpl可能除了实现INetService ,可能还会实现InterfaceA,InterfaceB,那么我们怎么确定ServiceWrapper的泛型呢?答案就是INetService继承Service,通过Service来确定最终的泛型。

小结

这样,我们就通过注解和接口,让JavaPoet生成了代码,我们的实际Service实例都是在ServiceWrapper中,我们接下来就需要吧各种ServiceWrapper的实现类放到我们的IOC容器即可。(容器其实是一个Map,key就是INetService接口,value就是servicewrapper对象)

Transform获取Register

既然各种ServiceWrapper都已经生成了,并且都在Register中准备好嘞。那么如何获取到这些ServiceRegister呢?很显然,我们可以通过Transform获取。

tips:如果对于Transform不是很熟悉,可以参考上一篇博客哈juejin.cn/post/708751…

简单回顾:Transform就是在class2dex的时候,我们可以读取class字节码,以及修改class字节码(可以参考android打包流程哈),我们需要的就是自定义Transform,读取字节码,找到各种register的class。而且Transform主要是关心的是input(输入)和output(修改的字节码),这部分代码其实是可以从网上复制的模板代码。

获取IServiceRegister

通过Transform获取到各种register,他们都是实现了IServiceRegister接口的,比如NetServiceRegister,存放到一个List中。

定义注册入口

这里,我们为了方便,同样定义一个注解@RegisterInit,目的是放我们找到对应的类(ServiceRegisterInit),方便ASM字节码注入到init方法中。在init方法中把之前的IServiceRegister的各种实现类,依次调用register方法,把ServiceWrapper注入到IOC容器中。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
@interface RegisterInit {
}

@RegisterInit
public class ServiceRegisterInit {
  /**
   * ASM字节码注入
   */
  public static void init() {}
}
外界调用

外界只需要在Application的onCreate方法中,调用ServiceRegisterInit的init方法即可,这样IOC容器中就会注册好所有需要的Service能力了。

小结

通过Transform找到需要的实现类,然后ASM字节码插桩,把初始化的代码注入到指定的方法之中。

总结

这篇文章我们知道IOC实现的一种方式即可,IOC=JavaPoet+Transform+ASM。然后外界在Application的onCreate调用初始化入口即可。

  • JavaPoet:生成代码,生成的代码主要存放着Service的实例对象。
  • Transform:获取到生活曾代码的class,存放到一个ServiceList当中,方便后续的字节码插桩。
  • 找到初始化的入口,然后获取到ServiceList,执行字节码插桩,把注册代码写入其中。