SPI
Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。
dubbo SPI
java也有SPI,不过其存在缺陷,每次加载会将所有实现类加载出来,导致加载慢资源浪费;
dubbo相比java spi,不同的地方有以下几点
- 配置文件通过键值对配置。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下
optimusPrime = org.apache.spi.OptimusPrime bumblebee = org.apache.spi.Bumblebee
- 按需加载实现类
- 实现Class类,实例对象的缓存
- 增加了 IOC 和 AOP 特性
源码解读-按需加载与缓存
dubbo获取一个实例对象的大致流程如下
1、ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。
2、先从缓存中获取实例对象getOrCreateHolder(name);
,如果实例对象不存在,则尝试利用Class来实例化一个
3、接着需要获取Class
` Class<?> clazz = getExtensionClasses().get(name);;
先从缓存中获取ClassName到Class之间的map
Map<String, Class<?» classes = cachedClasses.get();`,
如果为null,则读取配置文件,获取实现类名,利用Class.ForName来得到实例Class对象,并设置到Map和缓存中。
4、有了Class之后,就可以实例化了EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
5、接着对该对象中的依赖进行注入。injectExtension(instance);
6、利用反射,获取该对象中所有set方法,通过set方法可以获得依赖的类型和变量名;
Class<?> pt = method.getParameterTypes()[0];
String property = getSetterProperty(method);
然后根据这两个值去获得依赖对象
Object object = objectFactory.getExtension(pt, property);
最后调用set方法实现依赖注入
method.invoke(instance, object);
自适应拓展机制
有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。这听起来有些矛盾。拓展未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,拓展就无法被加载。
自适应拓展类的核心实现 —- 在拓展接口的方法被调用时,通过 SPI 加载具体的拓展实现类,并调用拓展对象的同名方法。; 也就是说,一开始我们并不注入扩展对象,而是构造一个实现了该接口的拓展代理,由该代理执行同名方法,并由该代理通过SPI加载具体的拓展实现类,再调用实际拓展对象的方法。
使用自适应拓展
- ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
自适应源码解读
1、调用
ExtensionLoader.getExtensionLoader(CLASS).getAdaptiveExtension();
获取自适应拓展实例。先尝试从缓存中获取,Object instance = cachedAdaptiveInstance.get();
如果缓存中没有,则创建一个自适应拓展对象。instance = createAdaptiveExtension();
2、injectExtension((T) getAdaptiveExtensionClass().newInstance());
这段代码中,先是自动生成代码,加载出代理扩展对象class,然后实例化对象,最后自动注入该对象依赖。主要重点在自动生成Class代码并加载Class对象
3、 在获取Class对象之前,也会先尝试从缓存中获取代理拓展Class getExtensionClasses();
,获取不到才执行代码生成
return cachedAdaptiveClass = createAdaptiveExtensionClass();
4、String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
代码生成。
public String generate() {
// no need to generate adaptive class since there's no adaptive method found.
if (!hasAdaptiveMethod()) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}
StringBuilder code = new StringBuilder();
code.append(generatePackageInfo()); //生成page代码 "package %s;\n";
code.append(generateImports()); //生成import代码 "import %s;\n";
code.append(generateClassDeclaration()); //生成 "public class %s$Adaptive implements %s {\n";
//生成各个接口的自适应代码,如果接口不带有@Adaptive,调用将返回异常。
Method[] methods = type.getMethods();
for (Method method : methods) {
code.append(generateMethod(method));
}
code.append("}");
if (logger.isDebugEnabled()) {
logger.debug(code.toString());
}
return code.toString();
}
package %s;\n //type.getPackage().getName()
import %s;\n //ExtensionLoader.class.getName()
public class %s$Adaptive implements %s {\n //type.getSimpleName(), type.getCanonicalName()
//if (adaptiveAnnotation == null) {
public %s %s(%s) %s { //methodReturnType, methodName, methodArgs, methodThrows
throw new UnsupportedOperationException("The method %s of interface %s is not adaptive method!");
}
public %s %s(%s) %s {
//如果接口参数中有url,则可以直接从参数中获取url
if (arg%d == null) throw new IllegalArgumentException("url == null");
%s url = arg%d;
//利用反射,从参数中获取能够get,返回值为URL.class的参数方法,如果没有则抛出异常
if (arg%d == null) throw new IllegalArgumentException("%s argument == null");
if (arg%d.%s() == null) throw new IllegalArgumentException("%s argument %s() == null");
%s url = arg%d.%s();
//获取拓展对象名,会先查看Adaptive的值,没有默认为拓展simple name
if (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");
String methodName = arg%d.getMethodName();\n
String extName = %s; //url.getMethodParameter(methodName, "%s", "%s") || url.getParameter("%s", %s) || url.getProtocol() == null ? (%s) : url.getProtocol() || url.getProtocol()
//extName判空
if(extName == null) throw new IllegalStateException(\"Failed to get extension (%s) name from url (\" + url.toString() + \") use keys(%s)\");\n;
//加载扩展对象
%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);//type.getName(),ExtensionLoader.class.getSimpleName(), type.getName()
//返回,没有return则为""
return extension.%s(%s);//method.getName(), args
}
}
private Class<?> createAdaptiveExtensionClass() {
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();//生成代码
ClassLoader classLoader = findClassLoader();
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader); //编译,生成Class对象
}
java spi dubbo spi 与 dubbo spi自适应拓展机制的区别
- java spi会实例化所有的对象。
- dubbo spi 则是会在用到的时候在实例化,第一次的时候会将所有的Class加载到缓存,然后第一次获取对象的时候在实例化
- 自适应与前者的区别时,第一次获取对象获取到的是代理扩展对象,只有在第一次调用接口方法的时候,才会触发代理由代理实例扩展对象并调用实际的扩展方法,代理和扩展都会缓存起来。