前言
dubbo模拟java实现了自己的spi机制,同时进行了相应的扩展,给开发者提供了扩展点。理解dubbo的spi机制,对阅读dubbo的源码有很大的帮助!
环境:dubbo : 2.6.2、JDK 1.8
配置环境:XXX为实现类
ExtensionLoader
ExtensionLoader是dubbo实现spi的核心类!
1.1 getExtension
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
//1:通过spi的name先从容器中找,找不到就创建一个Holder
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//2: 实例化对象
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
1.1.1 若cachedInstances容器中没有相应的对象,则调用createExtension方法去创建。下面代码为实例化对象的过程:
private T createExtension(String name) {
//1,加载配置文件中的内容,一行行解析,存入一个Map<String,Class>的容器中
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
//2,完成对象的实例化 class->object
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//3,自动注入 set方法,涉及Adaptive相关内容
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
//4,若wrapperClass不为空,则接口中存在包装类,需将调用包装类的有参构造方法进行实例化
//构造参数即为上步实例化好的instance,最后返回给用户一个包装类的对象
//完成代理的功能
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}
spi实例化对象的过程:
第一步:解析配置文件中的内容,解析完成后,存入Holder<Map<String, Class<?>>> cachedClasses中;
解析的过程:若实现类中,有参数为此接口的构造函数,则此类被认为是包装类,存入cachedWrapperClasses中;
若实现类中,类上有Adaptive注解,则此类视为一个自适应类,即代理类,存入变量 cachedAdaptiveClass。 在自动注入时,会注入自适应实现类;
若实现类不是上面的两种情况,则将其加入到extensionClasses容器中,之后存入cachedClasses。
第二步:实例化对象。获取对应的class对象,并将其实例化为object对象,class->object;
第三步:set方法自动注入。遍历set方法,若参数值为spi接口,则需自动注入。
自动注入过程:若前期的解析中,未发现有自适应的扩展类,则系统自动生成一个自适应的扩展类。采用工厂模 式,通过SpiExtensionFactory,调用ExtensionLoader#getAdaptiveExtension,生成自适应扩展类
第四步:若存在包装类,则需调用其参数为spi接口的构造函数,实例化对象,并完成自动注入流程。最后返回的是一个包装类。
1.1.1 自动注入,是如何生成自适应实现类的,且看以下代码:
private Class<?> createAdaptiveExtensionClass() {
//1,以字符串形式拼接Adaptive类,
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
//通过javassist 将code封装成ctclass对象,再转换为字节码,生成class对象
return compiler.compile(code, classLoader);
}
//拼接Car$Adaptive类
private String createAdaptiveExtensionClassCode() {
StringBuilder codeBuilder = new StringBuilder();
...
...
codeBuilder.append("package ").append(type.getPackage().getName()).append(";");
codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";");
//拼接类名
codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {");
...
// find URL getter method
LBL_PTS:
for (int i = 0; i < pts.length; ++i) {
Method[] ms = pts[i].getMethods();
for (Method m : ms) {
String name = m.getName();
if ((name.startsWith("get") || name.length() > 3)
&& Modifier.isPublic(m.getModifiers())
&& !Modifier.isStatic(m.getModifiers())
&& m.getParameterTypes().length == 0
&& m.getReturnType() == URL.class) {
urlTypeIndex = i;
attribMethod = name;
break LBL_PTS;
}
}
}
}
//javassit技术
public Class toClass(CtClass ct, ClassLoader loader, ProtectionDomain domain)
throws CannotCompileException
{
try {
//将ctclass对象转换为字节数组
byte[] b = ct.toBytecode();
java.lang.reflect.Method method;
Object[] args;
if (domain == null) {
method = defineClass1;
args = new Object[] { ct.getName(), b, new Integer(0),
new Integer(b.length)};
}
else {
method = defineClass2;
args = new Object[] { ct.getName(), b, new Integer(0),
new Integer(b.length), domain};
}
//生成class对象,加载到jvm内存中
return (Class)toClass2(method, loader, args);
}
catch (RuntimeException e) {
throw e;
}
catch (java.lang.reflect.InvocationTargetException e) {
throw new CannotCompileException(e.getTargetException());
}
catch (Exception e) {
throw new CannotCompileException(e);
}
}
以上代码,先拼接好代码,再利用javassist技术来,将字符串形式的代码封装成CtClass对象,并转换成字节码,生成class对象。
在jvm内存中查看自动生成的xxx$AdaptiveClass
可看到,dubbo为我们生成了一个Car$Adaptive的类,这就是set方法传入的对象。细看其实现方法,是通过URL作为参数来找到程序员想要注入的实现类,这就需要在定义spi接口时,方法需传入URL,且方法上需加@Adaptive注解,类似下面的接口:
(注意,若参数列表中没有URL将报错,不加@Adaptive也将报错)
而调用方法时,需构造一个URL,并传入一个map,将@Adaptive中的值作为key,配置文件中实现类的name作为value,如下:
总结
dubbo的spi可做的事情:
①可完成自动加载spi接口的实现类;
②若含有包装类,可为实现类自动代理;
③可完成实现类的自动注入(setXXX);