ServiceLoader整理
主要的使用场景是和第三方库解耦,解依赖。比如模块化的时候。比如接触第三方库依赖的时候。
使用
步骤如下:
定义接口
定义接口的实现
创建resources/META-INF/services目录
在上一步创建的目录下创建一个以接口名(类的全名) 命名的文件, 文件的内容是实现类的类名 (类的全名), 如:
在services目录下创建的文件是com.stone.imageloader.ImageLoader 文件中的内容为ImageLoader接口的实现类, 可能是com.stone.imageloader.impl.FrescoImageLoader
使用ServiceLoader查找接口的实现.
原理简单分析
1.final,实现了Iterable
不能被继承,可以被迭代
2.重点关注load方法
1 2 3 4 5
| public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); }
|
直接new一个新的ServiceLoader类,在实例化时会调reload方法实例化LazyIterator迭代类。
3.关注LazyIterator的next方法
接口实现类的查找是在ServiceLoader迭代
时通过调用next和hasNext方法去完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| public boolean hasNext() { if (nextName != null) { return true; } if (configs == null) { try { String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; }
public S next() { if (!hasNext()) { throw new NoSuchElementException(); } String cn = nextName; nextName = null; Class<?> c = null; try { c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found", x); } if (!service.isAssignableFrom(c)) { ClassCastException cce = new ClassCastException( service.getCanonicalName() + " is not assignable from " + c.getCanonicalName()); fail(service, "Provider " + cn + " not a subtype", cce); } try { S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated: " + x, x); } throw new Error(); }
|
hasnext
我们注意到:String PREFIX = “META-INF/services/”;service.getName()得到接口的全名(包名+接口名)。调用getResources(fullName)得到配置文件的URL集合(可能有多个配置文件)。通过这个方法parse(service, configs.nextElement());,参数是接口的Class对象和配置文件的URL来解析配置文件,返回值是配置文件里面的内容,也就是实现类的全名(包名+类名)。
parse
是解析配置文件,注意:返回值是一个数组,里面就是names,实现类的全名。
之后
利用Java的反射机制,根据类的全名类创建对象。
这个c = Class.forName(cn, false, loader);,第一个参数是实现类的全名,第三个是类加载器,返回值是实现类的Class对象。再看下面的S p = service.cast(c.newInstance());作用是创建实例并强转成接口的类型,最后返回。
缺点及改进
1.ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。
2.获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
3.不是单例。
与classloader的区别
JVM利用ClassLoader将类载入内存,这是一个类声明周期的第一步(一个java类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段。
Link:https://blog.csdn.net/a910626/article/details/78811273