JDK和Dubbo的SPI,分别加载和实例化的时机
前言
说起JDK和Dubbo中SPI区别的第一印象:JDK是按META-INFO/service下,接口全限定命名的文件上,指定该接口的所有实现类全限定名后,会全部加载该接口的所有实现类。而Dubbo和JDK类似,在META-INFO/dubbo/、META-INF/services/、META-INF/dubbo/internal/下指定,只不过接口全限定名文件里,指定的是key-value格式,key为扩展点名称,value为对应实现类的全限定名,然后根据key按需加载。
总的来说,就是JDK的SPI就是全部加载该接口的所有实现类。而Dubbo的就按需key加载。但这句话,其实是不严谨的。这个加载是指加载阶段,还是加载后然后包括实例化呢?
先说答案:这个加载,是指加载后的实例化。
JDK的加载和实例化
JDK案例:
ServiceLoader<Man> load = ServiceLoader.load(Man.class);
Iterator<Man> iterator = load.iterator();
while (iterator.hasNext()) {
iterator.next();
}
以Man接口的全限定名命名的文件,里面指定所有实现类
执行效果:
源码分析
ServerLoader里面自定义了iterator,自己实现了一套hasNext()和next():
public final class ServiceLoader<S>
implements Iterable<S>{
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
private final Class<S> service;
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
// 构造方法
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
// ...暂时省略相关代码
// ServiceLoader的内部类LazyIterator,实现了【Iterator】接口
// Private inner class implementing fully-lazy provider lookup
private class LazyIterator
implements Iterator<S>{
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
// 覆写Iterator接口的hasNext方法
public boolean hasNext() {
// ...暂时省略相关代码
}
// 覆写Iterator接口的next方法
public S next() {
// ...暂时省略相关代码
}
// 覆写Iterator接口的remove方法
public void remove() {
// ...暂时省略相关代码
}
}
// 覆写Iterable接口的iterator方法,返回一个迭代器
public Iterator<S> iterator() {
// ...暂时省略相关代码
}
// ...暂时省略相关代码
}
ServerLoader调用iterator()时,里面就是创建一个自定义iterator的匿名内部类,它里面的hasNext()和next(),主要是委托给LazyIterator这个iterator使用
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
// 调用lookupIterator即LazyIterator的hasNext方法
// 可以看到是委托给LazyIterator的hasNext方法来实现
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
// 调用lookupIterator即LazyIterator的next方法
// 可以看到是委托给LazyIterator的next方法来实现
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
变量名为lookupIterator就是一个LazyIterator,它定义的
public boolean hasNext() {
if (acc == null) {
// 调用hasNextService方法
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
去调hasNextService()
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// PREFIX = "META-INF/services/"
// service.getName()即接口的全限定名
// 还记得前面的代码构建LazyIterator对象时已经给其成员属性service赋值吗
String fullName = PREFIX + service.getName();
// 加载META-INF/services/目录下的接口文件中的服务提供者类
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
// 还记得前面的代码构建LazyIterator对象时已经给其成员属性loader赋值吗
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
// 返回META-INF/services/目录下的接口文件中的服务提供者类并赋值给pending属性
pending = parse(service, configs.nextElement());
}
// 然后取出一个全限定名赋值给LazyIterator的成员变量nextName
nextName = pending.next();
return true;
}
看到它在解析接口全限定名命名的文件,然后取出一个服务提供者实现类的全限定名赋值给LazyIterator的成员变量nextName
现在还未加载,只是去解析:serverLoader.iterator获取的lazyIterator,调用hasNext()去解析的。
加载和实例化时机
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
// 还记得在hasNextService方法中为nextName赋值过服务提供者实现类的全限定名吗
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 【1】去classpath中根据传入的类加载器和服务提供者实现类的全限定名去加载服务提供者实现类
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
// 【2】实例化刚才加载的服务提供者实现类,并进行转换
S p = service.cast(c.newInstance());
// 【3】最终将实例化后的服务提供者实现类放进providers集合
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
看到Class.forName()先加载全部实现类,接着实例化。
从上面案例的执行结果看出,都是打印完“超人初始化”,再打印“超人实例化”,再打印另一个扩展点“穷人初始化”、“穷人实例化”。是因为逐个iterator.hasNext()判断完,然后再逐个执行iterator.next()去加载并实例化。
Dubbo的加载和实例化时机
Dubbo案例
ExtensionLoader<Man> extensionLoader = ExtensionLoader.getExtensionLoader(Man.class);
Man manf = extensionLoader.getExtension("poorMan");
按Man接口全限定名命名的文件,根据key和value指定
执行结果为
看到只是实例化了“穷人”这个扩展点,“超人”只是起到加载作用,觉得奇怪吗?哈哈,那就请各位看官往下看:
源码分析
ExtensionLoader.getExtensionLoader(Man.class);这段代码只是去缓存map中获取该接口对应的扩展点加载器,只关注extensionLoader.getExtension(“poorMan”)即可,第一次调用是去调该方法
private T createExtension(String name, boolean wrap) {
Class<?> clazz = getExtensionClasses().get(name);
// ...暂时省略相关代码
}
先去调getExtensionClasses()->loadExtensionClasses()->loadDirectory()
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
}
提一嘴,笔者用的是Dubbo2.7,这里还使用策略者模式优化成循环去各个目录(也就是上面提到的接口全限定名命名的文件要在指定目录名下指定),在里面调用loadResource()解析文件,解析到多少个key-value,就调多少次
加载时机
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);
该方法用到了Class.forName,也就是会将定义的所有key-value都加载了。也就是说上面案例中只是getExtension()一个扩展点就加载了所有定义的value,这就是加载时机。
实例化时机
关于实例化时机,也是在createExtension()
private T createExtension(String name, boolean wrap) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
//这里就是实例化
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
if (wrap) {
List<Class<?>> wrapperClassesList = new ArrayList<>();
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}
if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
}
}
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
可以标有注释的就是实例化代码,只是根据入参实例化。这就解释得通,案例中,先打印“超人实例化”和“穷人实例化”,然后只打印了“穷人实例化”。
完结,撒花~