SPI:Service Provider Interface
是一种服务发现机制。他通过在classPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
//SPI机制 //对象初始化 ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class); Iterator<SPIService> iterator = load.iterator();
通过服务加载器,加载SPIservice服务,并且得到所有服务的实现类,进行加载启动。
加载的服务所在的路径是固定的。在所有过程项目同级目录新建一个resources,SPIservice文件的路径也要跟包名一致。
SpiService 内部就是指的需要加载的服务实现类名
而底层的加载流程是通过反射。
注解处理器SPI的运用
这里介绍SPI机制,是因为我们APT技术中运用到了,比如我们的在写注解处理器的时候,会在处理器中添加google 的AudoService的注解进行注册。
注册之后,在编译阶段就能找到注解处理器类,并且执行process()方法。
如果我们不去添加AudoService注解,使用我们上面介绍的SPI技术同样可以去完成相关效果。
源码的原理解析
跟踪ServiceLoader.load()方法看源码
provider就是承载所有实现类的容器。
看名字懒加载迭代器。然后对迭代器进行遍历执行。大概就是下面的逻辑
当然这里execute()方法是我们接口类自己定义的。
而源码中的LazyIterator 的迭代逻辑如下:
得到我们指定的resources下面SPIservice文件后进行内容的读取。io的读取把文件中的内容一行一行的读出来,然后进行反射得到class文件放入到provider容器中。
这种spi的机制,对解耦十分有作用。
javac上SPI的运用处理
接下来看我们javac是如何对注解处理器处理的。
首先看javac的源码,首先定位到 Main.java类的main()
执行到另外一个路径下的Main类,得到compiler。
继续定位到对应路径main类,compiler方法
最终其实现类是JavaCompiler类,跟踪JavaCompiler类,可以找到
可以看到processors其实就是Processor集合的迭代器。
compile内部对注解处理器的处理:
这个红框的代码是不是很眼熟,这就是我们在一开始介绍SPI使用的时候自己写的代码。所有可以看到javac 也运用到了spi机制。
关于注解处理器的几个问题。
1.process是怎么回调的
2.调用的次数是怎么决定的 和是否有生成文件有关系
3.返回值有什么用? 注解是否往下传递 true表示不传递set
A1 已经在上面回答过来 通过spi 机制
A2
我们知道process方法是用来处理注解的,但是如果生成的代码中又产生了注解呢,所以这里的process不仅仅会处理一次,而是又while循环的逻辑,直到没有注解全部被处理结束。具体的次数也不是固定的。
A3
返回值指的process方法中的set参数是否继续往下传递。默认都需要返回false,如果返回的true会导致我们处理的所以注解集合set传递中断。