上一篇文章我们了解到:框架会在编译时动态扫描
@RouterUri
注解并生成注册UriHandl
到UriAnnotationHandler
中的代码。那这些代码在运行时如何调用呢?
先了解一下ServiceLoader
这个概念。WMRouter
中的ServiceLoader
类似于java spi
ServiceLoader
它的基本功能是:
保存接口与实现类的对应关系。 这个关系是一对多。
可以实例化一个实现类,返回给调用方使用。
那在WMRouter
中,SerciceLoader
保存的接口与实现类的关系是哪些呢? 我们看一下ServiceLoader
的初始化方法:
void doInit() { Class.forName(“com.sankuai.waimai.router.generated.ServiceLoaderInit”).getMethod("init").invoke(null); }
即初始化的时候反射调用了ServiceLoaderInit.init()
方法。我全局搜索了一下这个类并没有发现它的声明。
最后发现这个类是使用Gradle Transform API和ams库动态生成的。
接下来我们就来研究一下这个类是怎么生成的,先来看一下WMRouter
的gradle transform插件是如何生成ServiceLoaderInit
这个类的。
WMRouterPlugin
官方是这样描述它的作用的 : 将注解生成器生成的初始化类汇总到ServiceLoader.init
,运行时直接调用ServiceLoader.init
。 从而完成SerciceLoader
的初始化。
这里我大致描述一下这个插件的工作逻辑:
扫描编译生成的class文件夹或者jar包的指定目录 : com/sankuai/waimai/router/generated/service, 收集目录下的类并保存起来 (这个类其实就是
ServiceInit_xxx1
这种类)使用
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(); }
我们看一下ServiceInitClassBuilder
的putDirectly
和build
方法:
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_xx1
的init
方法在什么时候调用呢 ?
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
。 为了便于理解,我们用下面这张图总结一下上面的过程:
路由节点的加载.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