serviceloader java_【java编程】ServiceLoader使用看这一篇就够了
轉(zhuǎn)載:https://www.jianshu.com/p/7601ba434ff4
想必大家多多少少聽過spi,具體的解釋我就不多說了。但是它具體是怎么實(shí)現(xiàn)的呢?它的原理是什么呢?下面我就圍繞這兩個(gè)問題來解釋:
實(shí)現(xiàn): 其實(shí)具體的實(shí)現(xiàn)類就是java.util.ServiceLoader這個(gè)類。
要想了解一個(gè)機(jī)制的原理,首先得知道它是怎么運(yùn)行的,需要什么配置,才能運(yùn)行起來。然后再分解來了解實(shí)現(xiàn)。對(duì)于技術(shù)實(shí)現(xiàn)也是一樣,先看這個(gè)類是怎么實(shí)現(xiàn)的,先讓它跑起來,看到效果。然后再講原理。
按照使用說明文檔,應(yīng)該分下面幾個(gè)步驟來使用:
創(chuàng)建一個(gè)接口文件
在resources資源目錄下創(chuàng)建META-INF/services文件夾
在services文件夾中創(chuàng)建文件,以接口全名命名
創(chuàng)建接口實(shí)現(xiàn)類
我們想測(cè)試一下,一般是在這個(gè)工程中建立一個(gè)測(cè)試類來測(cè)試。來看下代碼片段:
接口類
public interfaceIMyServiceLoader {
String sayHello();
String getName();
}
View Code
實(shí)現(xiàn)類:
public class MyServiceLoaderImpl1 implementsIMyServiceLoader {
@OverridepublicString sayHello() {return "hello1";
}
@OverridepublicString getName() {return "name1";
}
}public class MyServiceLoaderImpl2 implementsIMyServiceLoader {
@OverridepublicString sayHello() {return "hello2";
}
@OverridepublicString getName() {return "name2";
}
}
View Code
測(cè)試類:
public classTestMyServiceLoader {public static voidmain(String[] argus){
ServiceLoader serviceLoader = ServiceLoader.load(IMyServiceLoader.class);for(IMyServiceLoader myServiceLoader : serviceLoader){
System.out.println(myServiceLoader.getName()+myServiceLoader.sayHello());
}
}
}
View Code
正常情況下這里應(yīng)該輸出
name2hello2
name1hello1
View Code
看了這些步驟,想必你也知道原理了,我在這里總結(jié)下。
原理:在ServiceLoader.load的時(shí)候,根據(jù)傳入的接口類,遍歷META-INF/services目錄下的以該類命名的文件中的所有類,并實(shí)例化返回。
相信看到這里,有的看客該爆粗話了,說啥子看著一篇就夠了,這些知識(shí)點(diǎn)隨便一搜,到處都是好伐。是的,上面說的,確實(shí)隨便一搜都可以搜到,所以這里我要?jiǎng)澲攸c(diǎn)了:
一、問題
上面說了,正常情況下會(huì)那樣輸出,但是你運(yùn)行程序你就會(huì)發(fā)現(xiàn),馬丹,怎么不起作用啊,我哪里做錯(cuò)了,都是按照文章步驟來做的。弄的你都開始懷疑人生了。不要懷疑人生,在一個(gè)工程中做測(cè)試,確實(shí)不能實(shí)現(xiàn)想要的效果。
二、回憶場(chǎng)景
回憶一下spi的使用場(chǎng)景。它是給制作標(biāo)準(zhǔn)的一放用的,用來指定標(biāo)準(zhǔn),然后不同實(shí)現(xiàn)方,用不同的方式實(shí)現(xiàn)標(biāo)準(zhǔn)供使用方使用。那標(biāo)準(zhǔn)方和實(shí)現(xiàn)方必然不是一個(gè)。想到這里,你應(yīng)該能夠向明白了吧。
三、解決方案
要解決問題,就把之前做的打jar包,引入新工程測(cè)試,這樣就可以了。
四、疑問
但是有人會(huì)說標(biāo)準(zhǔn)方和實(shí)現(xiàn)方也可能是一個(gè)啊,好比標(biāo)準(zhǔn)方我提供一個(gè)內(nèi)部的實(shí)現(xiàn)方案也是可以的啊。也確實(shí)有道理啊,那這種怎么實(shí)現(xiàn)呢?
五、思考
當(dāng)然也有辦法,下面就說下實(shí)現(xiàn)方法。想要實(shí)現(xiàn)上面的需求,首先要知道攔阻這個(gè)需求實(shí)現(xiàn)的問題,然后把這些問題都解決了,需求自然也就實(shí)現(xiàn)了。那就先來分析問題吧,為什么在一個(gè)工程中獲取不到接口的實(shí)現(xiàn)類呢?經(jīng)過觀察發(fā)現(xiàn)是因?yàn)橘Y源文件沒有在classPath中,為什么這么說呢,可以看下build的目錄下面是沒有META-INF文件夾。現(xiàn)在知道了原因,這么解決呢?
六、疑問臨時(shí)解決方案
最簡單的方法,把資源下的META-INF文件夾拷貝到build目錄下,然后再運(yùn)行,發(fā)現(xiàn)可以了,這也就驗(yàn)證了,確實(shí)是這個(gè)問題造成的。搞定!
七、再次發(fā)出疑問
這樣就結(jié)束了,那我總不能手動(dòng)拷貝吧,這不算解決方案,只是臨時(shí)方案。那要怎么解決呢?
我就不賣關(guān)子了。其實(shí)要解決這個(gè)問題,只要在編譯的時(shí)候把這些文件放到build目錄中就行了,是不是很簡單。思路是有了,可是怎么實(shí)現(xiàn)呢?這個(gè)時(shí)候要用到攔截編譯處理,然后再里面做這件事情。
方案一
繼承AbsStractProcessor,在process方法中把資源文件移到build目錄下。
方案二
這里用到了google開源的AutoService
大概看了下autoService的源碼,其實(shí)它也是使用方案一的方法,攔截編譯過程,然后再build目錄下生成配置文件,這里來大概看下它的process方法:
ublic boolean process(Set extends TypeElement>annotations, RoundEnvironment roundEnv) {try{returnprocessImpl(annotations, roundEnv);
}catch(Exception e) {
...return true;
}
}private boolean processImpl(Set extends TypeElement>annotations, RoundEnvironment roundEnv) {if(roundEnv.processingOver()) {
generateConfigFiles();
}else{
processAnnotations(annotations, roundEnv);
}return true;
}
View Code
這里你會(huì)發(fā)現(xiàn)其實(shí)就是generateConfigFiles()和processAnnotations(annotations, roundEnv)看名字可以猜到processAnnotations是處理注解的,這里實(shí)現(xiàn)類都實(shí)現(xiàn)了注解,所以這里應(yīng)該是找到實(shí)現(xiàn)類。
rivate void processAnnotations(Set extends TypeElement>annotations,
RoundEnvironment roundEnv) {
Set extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);for(Element e : elements) {
TypeElement providerImplementer=(TypeElement) e;
AnnotationMirror providerAnnotation= getAnnotationMirror(e, AutoService.class).get();
DeclaredType providerInterface=getProviderInterface(providerAnnotation);
TypeElement providerType=(TypeElement) providerInterface.asElement();
...
String providerTypeName=getBinaryName(providerType);
String providerImplementerName=getBinaryName(providerImplementer);
providers.put(providerTypeName, providerImplementerName);
}
}
View Code
確實(shí)如此,這里會(huì)把所有的實(shí)現(xiàn)類存起來。
再來看看generateConfigFiles()方法
private voidgenerateConfigFiles() {
Filer filer=processingEnv.getFiler();for(String providerInterface : providers.keySet()) {
String resourceFile= "META-INF/services/" +providerInterface;try{
SortedSet allServices =Sets.newTreeSet();try{
FileObject existingFile= filer.getResource(StandardLocation.CLASS_OUTPUT, "",
resourceFile);
Set oldServices =ServicesFiles.readServiceFile(existingFile.openInputStream());
allServices.addAll(oldServices);
}catch(IOException e) {
}
Set newServices = new HashSet(providers.get(providerInterface));
allServices.addAll(newServices);
FileObject fileObject= filer.createResource(StandardLocation.CLASS_OUTPUT, "",
resourceFile);
OutputStream out=fileObject.openOutputStream();
ServicesFiles.writeServiceFile(allServices, out);
out.close();
}catch(IOException e) {return;
}
}
}
View Code
這里是在build下創(chuàng)建META-INF目錄。和我們想的一模一樣。
八、總結(jié)
好了,要實(shí)現(xiàn)文章開頭的需求,除非你覺得你比google開源AutoService的工程師寫的更好,不然就直接使用AutoService吧。這篇文章不僅是分析ServiceLoader的原理,實(shí)現(xiàn)我們的需求,更重要的是高速我們遇到問題該怎么分析問題,解決問題。
九、擴(kuò)展
其實(shí)還有很多比較好玩的,比如在攔截到編譯過程時(shí),可以再編譯期生成一些有意思的代碼,來幫我們實(shí)現(xiàn)一些自動(dòng)化處理。這就需要?jiǎng)佑梦覀兊拇竽X就想了,介紹一下生成代碼的庫javapoet,大家可以了解一下。
總結(jié)
以上是生活随笔為你收集整理的serviceloader java_【java编程】ServiceLoader使用看这一篇就够了的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python顺序执行 toggle_编写
- 下一篇: java 中字符串比较方法_java中常