JDK SPI 和Dubbo SPI的区别:
- JDK SPI 会一次性实例化所有配置的实例:如果某些实例在程序中并不需要,那将会是极大的浪费。Dubbo SPI只会实例化需要的类。
- Dubbo SPI 还支持IOC(注入其他的实例)
- Dubbo SPI的灵活性更强,功能也更加强大。
Dubbo SPI 将分成两篇来研究:
- dubbo原理:SPI机制(一): 主要研究dubbo SPI的原理。
- dubbo原理:SPI机制(二),主要研究dubbo IOC的原理
研究dubbo SPI机制时,文章均从dubbo的测试类开始,这样让读者更容易理解dubbo SPI的实际用途。
1. 起点:测试类
SimpleExt@SPI("impl1")
public interface SimpleExt {
// @Adaptive example, do not specify a explicit key.
@Adaptive
String echo(URL url, String s);
@Adaptive({"key1", "key2"})
String yell(URL url, String s);
// no @Adaptive
String bang(URL url, int i);
}
接口SimpleExt有三个实现类;并且SimpleExt添加类SPI注解,定义类两个Adaptive方法。
我们先来看下面这段测试代码:
SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();
Map<String, String> map = new HashMap<String, String>();
URL url = new URL("p1", "1.2.3.4", 1010, "path1", map);
String echo = ext.echo(url, "haha");
assertEquals("Ext1Impl1-echo", echo);
2. 获取ExtensionLoader
首先会获取测试类的ExtensionLoader:每个类都会有一个ExtensionLoader,自适应类就是在ExtensionLoader中产生的。
a. getExtensionLoader
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type){
//首先会校验传入的type:是否为空、是否是interface、是否使用类SPI注解。
...
// 尝试从缓存中获取ExtensionLoader,每个type都有一个ExtensionLoader
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
b. ExtensionLoader 构造函数
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
ExtensionLoader的构造函数,其实就是采用SPI的方式,生成类一个ExtensionFactory类。我们来看看ExtensionFactory是如何生成的。
3. 生成ExtensionFactory自适应类
a. getAdaptiveExtensionClass
ExtensionFactory自适应类的创建和其他自适应类的方式相同。
private Class<?> getAdaptiveExtensionClass() {
//获取ExtensionFactory接口可能的实现类;这一步和JDK一样,Dubbo到规定的几个路径下(比如META-INF/dubbo/internal)去寻找ExtensionFactory实现类
getExtensionClasses();
// 如果有哪个实现类标注类@Adaptive,在这里就直接返回
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
b. 配置文件加载
与JDK SPI不同的是,Dubbo SPI 文件内容是“key=value”的形式:
adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory
getExtensionClasses 方法会读取SPI配置文件,将实现类保存在extensionClasses中
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
//省略读文件的具体操作
while ((line = reader.readLine()) != null) {
...
name = line.substring(0, i).trim();//key
line = line.substring(i + 1).trim();//value
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
....
}
}
在loadClass时也有需要注意的地方,不是所有的实现类都会被添加到extenssionClasses中去的。
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) {
// 如果实现类有Adative注解,则直接将该类缓存起来
if (clazz.isAnnotationPresent(Adaptive.class)) {
//就是:cachedAdaptiveClass = clazz;
cacheAdaptiveClass(clazz);
}else {
// 将class保存到extensionClasses中
saveInExtensionClass(extensionClasses, clazz, name);
}
}
最终extensionClasses的值如下图:
extensionClasses再回到getAdaptiveExtensionClass方法中,由于在loadSource时,AdaptiveExtensionFactory上有Adaptive注解,那么cachedAdaptiveClass = AdaptiveExtensionFactory.class。Dubbo直接默认指定了自适应类,直接返回。
于是最终通过getAdaptiveExtensionClass返回的ExtensionFactory返回的是AdaptiveExtensionFactory类。
5. 生成SimpleExt自适应类
和生成ExtensionFactory类似,SimpleExt的自适应类也需要调用getAdaptiveExtension方法,loadSource
a. loadSource
同样也是在"META-INF/dubbo/internal" 路径下加载SimpleExt的实现类
impl2=org.apache.dubbo.common.extension.ext1.impl.SimpleExtImpl2 # Comment 2
impl1=org.apache.dubbo.common.extension.ext1.impl.SimpleExtImpl1#Hello World
impl3=org.apache.dubbo.common.extension.ext1.impl.SimpleExtImpl3 # with head space
在SPI配置文件中定义了三个实现类,我们来看看最终的extensionClasses
SimpleExt由于在三个实现类,都没有使用Adaptive注解,因此会进入到createAdaptiveExtensionClass方法
b. createAdaptiveExtensionClass
createAdaptiveExtensionClass 通过AdaptiveClassCodeGenerator代码生成器生成代码,然后使用Dubbo定义的compiler将代码文件转换成Class,最终生成自适应类。
代码生成的代码过于复杂,这里只展示生成代码最终的样子,以便理解。
package org.apache.dubbo.common.extension.ext1;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class SimpleExt$Adaptive implements org.apache.dubbo.common.extension.ext1.SimpleExt {
public java.lang.String bang(org.apache.dubbo.common.URL arg0, int arg1) {
throw new UnsupportedOperationException("The method public abstract java.lang.String org.apache.dubbo.common.extension.ext1.SimpleExt.bang(org.apache.dubbo.common.URL,int) of interface org.apache.dubbo.common.extension.ext1.SimpleExt is not adaptive method!");
}
public java.lang.String echo(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("simple.ext", "impl1");
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([simple.ext])");
org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName);
return extension.echo(arg0, arg1);
}
public java.lang.String yell(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("key1", url.getParameter("key2", "impl1"));
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([key1, key2])");
org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName);
return extension.yell(arg0, arg1);
}
从上面的代码中,我们可以发现以下几点:
- 没有使用@Adaptive的bang方法,直接抛出异常
- 自适应interface的方法中,一定要有URL参数:直接( 方法参数中直接有URL参数)、间接( 从参数中通过getUrl方法,获取URL);如果没有URL参数,就会抛出异常
- 对于没有指定parameter的方法,如echo方法,在取参数时,会将simple.ext作为key(将SimpleExt拆分开,并用"."隔开);指定类parameter的方法,就按照key的顺序来取。
- 自定义类的生成只有在调用方法的时候,才会在方法内生成
- 最后会根据key取得的参数,来决定生成哪一个实现类:
ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName)
, 最后实现方法的调用。
6. 回到测试
让我们回到最初的测试:
- 测试1:默认实现
{
SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();
Map<String, String> map = new HashMap<String, String>();
URL url = new URL("p1", "1.2.3.4", 1010, "path1", map);
String echo = ext.echo(url, "haha");
assertEquals("Ext1Impl1-echo", echo);
}
由于SimpleExt在定义时指定默认的自适应类(@SPI("impl1")), 于是即使没有显式指明自适应的类,Dubbo也会为我们生成SimpleExtImpl1。
- 测试2:使用默认key,指定实现
{
SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();
Map<String, String> map = new HashMap<String, String>();
map.put("simple.ext", "impl2");
URL url = new URL("p1", "1.2.3.4", 1010, "path1", map);
String echo = ext.echo(url, "haha");
assertEquals("Ext1Impl2-echo", echo);
测试2中显式指明了要实现的自适应类impl2,于是最终生成的是SimpleExtImpl2.
- 测试3: 使用自定义key,指定实现;key的优先级是从左到右的
{
SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();
Map<String, String> map = new HashMap<String, String>();
map.put("key2", "impl2");
URL url = new URL("p1", "1.2.3.4", 1010, "path1", map);
String echo = ext.yell(url, "haha");
assertEquals("Ext1Impl2-yell", echo);
url = url.addParameter("key1", "impl3"); // note: URL is value's type
echo = ext.yell(url, "haha");
assertEquals("Ext1Impl3-yell", echo);
}
yell 方法自定义了key1,key2,于是就不能再使用"simple.ext"作为key了。
- 测试4: 没有url参数
SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();
try {
ext.echo(null, "haha");
fail();
} catch (IllegalArgumentException e) {
assertEquals("url == null", e.getMessage());
}
在没有url参数的时候,就会抛出异常。
网友评论