继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

WMRouter源码分析(3)-路由节点的动态加载

慕哥9229398
关注TA
已关注
手记 1241
粉丝 199
获赞 913

上一篇文章我们了解到:框架会在编译时动态扫描@RouterUri注解并生成注册UriHandlUriAnnotationHandler中的代码。那这些代码在运行时如何调用呢?

先了解一下ServiceLoader这个概念。WMRouter中的ServiceLoader类似于java spi

ServiceLoader

它的基本功能是:

  1. 保存接口与实现类的对应关系。 这个关系是一对多。

  2. 可以实例化一个实现类,返回给调用方使用。

那在WMRouter中,SerciceLoader保存的接口与实现类的关系是哪些呢? 我们看一下ServiceLoader的初始化方法:

    void doInit() {
       Class.forName(“com.sankuai.waimai.router.generated.ServiceLoaderInit”).getMethod("init").invoke(null);
    }

即初始化的时候反射调用了ServiceLoaderInit.init()方法。我全局搜索了一下这个类并没有发现它的声明。
最后发现这个类是使用Gradle Transform APIams库动态生成的。

接下来我们就来研究一下这个类是怎么生成的,先来看一下WMRouter的gradle transform插件是如何生成ServiceLoaderInit这个类的。

WMRouterPlugin

官方是这样描述它的作用的 : 将注解生成器生成的初始化类汇总到ServiceLoader.init,运行时直接调用ServiceLoader.init。 从而完成SerciceLoader的初始化。

这里我大致描述一下这个插件的工作逻辑:

  1. 扫描编译生成的class文件夹或者jar包的指定目录 : com/sankuai/waimai/router/generated/service, 收集目录下的类并保存起来 (这个类其实就是ServiceInit_xxx1这种类)

  2. 使用asm生成ServiceLoaderInit类class文件,这个class文件调用前面扫描到的类的init方法。

即最终产生如下代码:

    public class ServiceLoaderInit {
        public static void init() {
            ServiceInit_xxx1.init();
            ServiceInit_xxx2.init();
        }
    }

到这里就有疑问了,从开始分析到现在我们并没有看到ServiceInit_xxx1这种类是如何生成的呢。那它是在哪里生成的呢?

ServiceInit_xx的生成

在上一篇文章已经了解到UriAnnotationProcessor在编译时会扫描@RouterUri,并且会生成UriAnnotationInit_xx1这种类,UriAnnotationInit_xx1.init方法就是接收一个UriAnnotationHandler实例,然后调用它的register方法。

其实UriAnnotationProcessor在扫描@RouterUri生成UriAnnotationInit_xx1类的同时,还会生成一个类,就是ServiceInit_xx:

//UriAnnotationProcessor.javapublic void buildHandlerInitClass(CodeBlock code, String genClassName, String handlerClassName, String interfaceName) {
    .... // 生成 UriAnnotationInit_xx1 代码
    String fullImplName = Const.GEN_PKG + Const.DOT + genClassName;    String className = "ServiceInit" + Const.SPLITTER + hash(genClassName);    new ServiceInitClassBuilder(className)
            .putDirectly(interfaceName, fullImplName, fullImplName, false)
            .build();
}

我们看一下ServiceInitClassBuilderputDirectlybuild方法:

  public class ServiceInitClassBuilder {
        ...        public ServiceInitClassBuilder putDirectly(String interfaceName, String key, String implementName, boolean singleton) {
            builder.addStatement("$T.put($T.class, $S, $L.class, $L)",
                    serviceLoaderClass, className(interfaceName), key, implementName, singleton);            return this;
        }        public void build() {
            MethodSpec methodSpec = MethodSpec.methodBuilder(Const.INIT_METHOD)
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(TypeName.VOID)
                    .addCode(this.builder.build())
                    .build();

            TypeSpec typeSpec = TypeSpec.classBuilder(this.className)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(methodSpec)
                    .build();

            JavaFile.builder(“com.sankuai.waimai.router.generated.service”, typeSpec)
                    .build()
                    .writeTo(filer);
        }

其实就是会把下面代码生成到com/sankuai/waimai/router/generated/service文件夹下:

public class ServiceInit_xx1 {
  public static void init() {
       ServiceLoader.put(IUriAnnotationInit.class, "com.xxx.UriAnnotationInit_xx1", com.xx.UriAnnotationInit_xx1.class, false);
  }
}

可以看到 ServiceInit_xx1.init的作用就是把接口实现类的关系保存到ServiceLoader中。

WMRouter的transform插件会扫描com/sankuai/waimai/router/generated/service下的类。即会扫描到ServiceInit_xx1这个类,然后按照刚开始所说的生成调用其init()的代码:

    public class ServiceLoaderInit {
        public static void init() {
            ServiceInit_xxx1.init();
            ServiceInit_xxx2.init();
        }
    }

综上所述,ServiceLoaderInit.init被调用后,SerciceLoader中就保存了IUriAnnotationInit的接口实现类UriAnnotationInit_xx1

接下来的问题就是IUriAnnotationInit的实现类UriAnnotationInit_xx1init方法在什么时候调用呢 ?

UriAnnotationInit_xx1.init的调用时机

其实UriAnnotationInit_xx1.init就是为了把前面经过UriAnnotationProcessor生成的路由节点信息注册到UriAnnotationHandler中。我们来看一下UriAnnotationHandler的初始化方法:

   protected void initAnnotationConfig() {
        RouterComponents.loadAnnotation(this, IUriAnnotationInit.class);
    }

上面的代码最终会调用到这里:

    List<? extends AnnotationInit<T>> services = ServiceLoader.load(clazz).getAll();    for (AnnotationInit<T> service : services) {
        service.init(handler);
    }

即会通过SerciceLoader来获取IUriAnnotationInit的实现类,并调用其init方法。

前面我们已经分析过了SerciceLoader在初始化时,就已经把IUriAnnotationInit与其实现类的关系保存起来了。所以上面的service.init(handler)实际上就是调用下面的代码

//IUriAnnotationInit的实现类public class UriAnnotationInit_xx1 implements IUriAnnotationInit {    public void init(UriAnnotationHandler handler) {
        handler.register("", "", "/jump_activity_1", "com.sankuai.waimai.router.demo.basic.TestBasicActivity", false);
    }

结合我们前面所了解的,这样UriAnnotationHandler就生成了用于处理被@RouterUri标记的页面UriHandlder。 为了便于理解,我们用下面这张图总结一下上面的过程:

webp

路由节点的加载.png

ServiceLoader中更强大的功能

其实上面利用ServiceLoader来加载一个接口的实现类这个功能,WMRouter这个框架已经把它单独抽取出来了。即利用SerciceLoader可以跨模块加载一个接口的实现类。我们看一下怎么使用

@RouterService

java 中的spi机制需要我们在 META-INF/services规范好接口与实现类的关系,WMRouter中提供@RouterService,来简化了这个操作。举个例子 :

比如在一个项目中有3个库: interface、lib1、lib2

//定义在interface库中public abstract class LibraryModule {    public abstract String getModuleName();
}//定义在lib1中@RouterService(interfaces = LibraryModule.class)public class LibraryModule1 extends LibraryModule {
}//定义在lib2@RouterService(interfaces = LibraryModule.class)public class LibraryModule2 extends LibraryModule {
}

WMRouter中有一个ServiceAnnotationProcessor负责处理RouterService注解,它会把标记了这个注解的类,生成ServiceInit_xx, 即

public class ServiceInit_f3649d9f5ff15a62b844e64ca8434259 {
  public static void init() {
      ServiceLoader.put(IUriAnnotationInit.class, "xxx",xxx.class, false);
  }
}

这样再由WMRouter的插件转换生成 ServiceLoaderInit.init()中的调用代码。就达到在运行时把LibraryModule的实现注入到SerciceLoader中,从而我们可以获得不同库的实现。



作者:susion哒哒
链接:https://www.jianshu.com/p/d3179f8b69c2


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP