Dubbo的SPI
SPI什么是SPI,SPI全称为Service Provider Interface,是一种服务发现机制,SPI的本质是将接口实现类的全限定名配置到文件中,并由服务器加载读取配置文件,加载实现类,这样可以在运行时,动态为接口替换实现类,正因此特性,我们可以很容易的通过SPI机制为我们的程序提供扩展功能,SPI机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重的模块。
1、Dubbo SPI
先看一下java原生的SPI,用到了反射机制,跟Spring的IOC里的一样,通过全限定类名,在程序运行阶段,在内存中创建对象 newInstance。
创建一个接口类和两个实现类
public interface User {
public void service();
}
public class UserOneImpl implements User {
public void service(){
System.out.println("实现类一");
}
}
public class UserTwoImpl implements User {
public void service(){
System.out.println("实现类二");
}
}
在resources目录下创建一个目录,META-INF.services
在这个目录下把,接口的全限定类名
com.jd.service.User ,里面配置内容是接口实现类的全限定类名
com.jd.service.impl.UserOneImpl
com.jd.service.impl.UserTwoImpl
然后编写一个测试类
public class SpiTest {
public static void main(String[] args) {
ServiceLoader load = ServiceLoader.load(User.class);
//使用了反射机制,创建了对象。
Iterator iterator = load.iterator();
while (iterator.hasNext()){
User userImpl = iterator.next();
userImpl.service();
}
}
}
控制台输出了 :实现类一 实现类二
原生SPI源码分析原理,SerivceLoader进入这个类,可以看到成员变量写好了路径,然后调用load方法
private static final String PREFIX = "META-INF/services/";
进入load.iterator(),重写了hasNext方法
public Iterator iterator() {
return new Iterator() {
Iterator> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
进入hasNext方法
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction action = new PrivilegedAction() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
进入hasNextService方法
private boolean hasNextService() {
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;
}
存到pending中,pending赋值给了nextName。
接下来回到iterator()方法中,继续走next方法,进入lookupIterator中的next方法
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction action = new PrivilegedAction() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
然后进入nextService()方法中。把之前的nextName值付给了cn,cn通过反射,取消类名c,类c 被实例化newInstance(),赋值给了p,然后返回p。p就是实例化对象,然后我们用接口进行接收的。这个就是Java原生的SPI的一个源码分析。
private S nextService() {
if (!hasNextService())
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");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
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);
}
throw new Error(); // This cannot happen
}
这就是java的原生SPI。有一些缺点:
接口的所有实现类全部都需要加载并实例化
无法根据参数来获取所对应的实现类
不能解决IOC、AOP的问题。基于以上问题,dubbo在原生SPI机制进行了增强,解决以上问题。
Dubbo的SPI机制,Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在META-INF/dubbo 路径下,配置内容如下,源码
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
test1=com.jd.service.impl.UserOneImpl
test2=com.jd.service.impl.UserTwoImpl
先引入坐标依赖,
com.alibaba
dubbo
2.5.3
在接口上加一个注解@SPI
重新编写一个测试类
public class DubboSpi {
public static void main(String[] args) {
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(User.class);
User userImpl = extensionLoader.getExtension("test1");
userImpl.service();
}
}
控制台输出 ,实现类一,因为只配了key值是test1
源码分析,首先进入的是getExtensionLoader方法,通过 ExtensionLoader 的 getExtensionLoader 方法获取一ExtensionLoader 实例,该方法方法先从缓存中获取与拓展类对应的 ExtensionLoader,若缓存未命中,则创建一个新的实例
else {
//从缓存中获取,如果缓存中有直接返回loader,如果缓存中没有,重新创建一个loader对象
ExtensionLoader loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
if(loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
}
return loader;
接下来看getExtension方法
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
//先从缓存中取,如果缓存中没有就新建一个holder对象
Holder
接下来进入createExtension方法中
private T createExtension(String name) {
//1、根据name值就是key值,通过getExtensionClasses()获取所有的拓展类,如果等于null就报异常
Class> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
//也是,从缓存中取拿,如果缓存没有,就创建一个
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
//2、通过反射创建拓展对象实例
EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//3、需要依赖注入,向实例中注入依赖
//此处injectExtension方法依赖注入实现原理:Dubbo 首先会通过反射获取到实例的所
// 有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过
//ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象
// 中。
injectExtension(instance);
Set> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
//4、将拓展对象包裹在相应的 Wrapper 对象中循环创建 Wrapper 实例
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);
}
}
Dubbo的服务暴露
Dubbo的服务引入