登陆

章鱼币竞猜-面试官:你能谈谈Dubbo SPI扩展原理吗?

admin 2019-10-29 315人围观 ,发现0个评论

最好有AOP、IOC、MVC结构根底和dubbo运用根底再阅览噢。

什么是SPI

spi全称Service Provider Interface, 服务供给接口, 是Java供给的一套用来被第三方完成或许扩展的API。

没有运用过JDK SPI的能够百度一个比方自己跑下,这儿只讲源码。

SPI的中心思维是解耦,依据接口、战略形式、装备完成完成类的动态扩展。

经验丰富的开发者必定用过很多个Driver的完成类产品, 比方oracle.jdbc.driver.OracleDriver和oracle.jdbc.OracleDriver、还有ODBC(衔接微软的那个数据库),以JDBC驱动为例,咱们剖析一下JDK是怎么做到动态扩展的:

在mysql-connector-java-5.1.44.jar!/META-INF/services/java.sql.Driver文件中:

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

这两个都是java.sql.Driver的完成类的全限制名,咱们看看哪里在加载这个Driver.class:

private static void loadInitialDrivers() {
// 先从JVM发动参数里边获取到jdbc.drivers的值 (-Djdbc.drivers=xxx)
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
// JDK供给的加载SPI办法:ServiceLoader
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}

我来解说下大约流程:

  1. 加载JVM发动特点,拿到driverName
  2. 用ServiceLoader加载Driver的一切完成类
  3. 依据jvm特点中的driver姓名加载类: Class.forName(driverName)

第一步和第三部咱们应该都很了解。来探求下ServiceLoader的骨干源码:

首要它完成了迭代类

public final class ServiceLoader implements Iterable

所以从该目标中拿到的迭代器, 是定制的迭代器,咱们再调用迭代器的hasNext办法,事实上调用的是:

public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction action = new PrivilegedAction() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// service便是咱们传进来的接口类目标
// PREFIX 是常量: private static final String PREFIX = "META-INF/services/";
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;
}
// 这儿面会翻开流去解析每一行, 把完成类全限制名加到names列表并回来其迭代器目标,用pending来接纳
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}

由此咱们能够逆向推出JDK的SPI运用办法:

  1. SPI的装备文件途径:META-INF/services/,不能改动。
  2. 装备文件名以接口的全限制类名声明
  3. 装备文件中每一行是一个完成类的全限制类名

Java SPI 的问题

Dubbo为什么不必java的SPI而挑选自己再重写一套呢。

  1. JDK 规范的 SPI 会一次性实例化扩展点一切完成,假如有扩展完成初始化很耗时,但假如没用上也加载,会很浪费资源。
  2. 假如扩展点加载失利,连扩展点的称号都拿不到了。
  3. 不支持AOP和IOC。

Dubbo SPI

Dubbo 的SPI 扩展从 JDK 规范的 SPI (Service Provider Interface) 扩展点发现机制加强而来。改进了 JDK 规范的 SPI 的以上问题。

运用在这儿不说了,没有用过的同学能够去官方看下:http://dubbo.apache.org/zh-cn/docs/dev/SPI.html

 public static void main(String[] args) {
ExtensionLoader loader = ExtensionLoader.getExtensionLoader(Color.class);
Color yellow = loader.getExtension("yellow");
System.out.println(yellow.getColor());
}

这是测验Dubbo SPI的一段代码,能够看到getExtensionLoader是dubbo SPI的进口,咱们来看看dubbo是怎么完成动态扩展的。

Color yellow = loader.getExtension("yellow");在这行代码中,阅历了从装备的扩展类name到取得该类的实例化的进程,那咱们从getExtension办法(相似spirng的getBean())看起。

 public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
// 依据接口名获取它的持有类, 假如有从缓存中取, 没有就从头new
final Holder holder = getOrCreateHolder(name);
// 获取到实例
Object instance = holder.get();
// 两层检锁 获取和创立实例
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}

假定现在什么都没有,则会到了createExtension(name);办法, 这个办法创立了扩展类的实例。

private T createExtension(String name) {
// 依据接口名获取一切完成类
Class
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);
}
// inject 依靠注入, 给实例注入需求的依靠目标
injectExtension(instance);
// 从缓存拿接口的包装类(也是完成类)
Set
// 假如包装类不是空, 就遍历经过结构办法反射创立实例
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}

createExtension 办法包括了如下的过程:

  1. 经过 getExtensionClasses获取一切的完成类
  2. 经过反射clazz.newInstance()创立扩展目标
  3. injectExtension(instance);向拓宽目标中注入依靠
  4. 实例化带接口参数的结构办法,将拓宽目标包裹在相应的 Wrapper 目标中

从wrapperClass.getConstructor(type).newInstance(instance);代码中能够看到, Wrapper目标必定要有参数为接口类目标的结构办法。

那怎么获取一切的扩展类的呢?咱们探究一下getExtensionClasses办法:

 private Map
// 从缓存中拿map, 假如没有,就调用loadExtensionClasses
Map
if (classes == null) {
synchronize章鱼币竞猜-面试官:你能谈谈Dubbo SPI扩展原理吗?d (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}

这个办法很简单,从缓存中拿map, 假如没有,就调用loadExtensionClasses,看下loadExtensionClasses办法做了啥:

private Map
cacheDefaultExtensionName();
Map<>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
  1. 调用cacheDefaultExtensionName拿到默许完成类
  2. 调用loadDirectory从几个文件下寻觅扩展类装备文件

先来看看cacheDefaultExtensionName是怎么拿到默许完成类的:

 private void cacheDefaultExtensionName() {
// 获取SPI上的注解, 假如有设置value值,则取出, 这个值是默许完成类噢
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if (names.length > 1) {
throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
if (names.length == 1) {
cachedDefaultName = names[0];
}
}
}
}

这个办法很简单, 便是将SPI注解的value取出来来对应默许的完成类。

private void loadDirectory(Map
String fileName = dir + type;
try {
Enumeration urls;
// 阿里封装的获取 classLoader 的办法
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
// jdk的办法, 与java spi相同的噢
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
// 拿到url后遍历调用loadResource办法
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading exte章鱼币竞猜-面试官:你能谈谈Dubbo SPI扩展原理吗?nsion class (interface: " +
type 毛骗终结篇+ ", description file: " + fileName + ").", t);
}
}

再详细下去咱们自己看,我给描绘下做了什么:

  1. 用url获取到文件流
  2. 解析每一行, 将=号前面的赋值为name,后边的赋值为完成类的全限制途径
  3. 反射实例化每一行的完成类, 并调用loadClass办法完成对extensionClasses的终究赋值(它new了一个extensionClasses的map目标直到后边才进行赋值)。
private void loadClass(Map
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
// 假如是Adaptive注解的类, 就把它放到cacheAdaptiveClass缓存中
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
// 切割name, 由于装备文件中name能够有很多个,然后每个name对应赋章鱼币竞猜-面试官:你能谈谈Dubbo SPI扩展原理吗?值一个完成类目标(即便目标都相同)
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}


  1. 判别是否是Adaptive等注解的类, 就把它放到对应的cacheAdaptiveClass等缓存中
  2. 切割name, 由于装备文件中name能够有很多个,然后每个name对应赋值一个完成类目标(即便目标都相同)

好了,到这儿扩展类的加载就完成了, 接下来就到它的依靠注入(injectExtension办法):

 private T injectExtension(T instance) {
try {
// 你必定很猎奇,假如objectFactory是空怎么办,其实再
if (objectFactory != null) {
// 遍历实例一切的办法
for (Method method : instance.getClass().getMethods()) {
// 假如是setter办法
if (isSetter(method)) {
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// 获取setter办法特点
String property = getSetterProperty(method);
// 依据第一个参数和类型 从List中获取实例
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// 运用反射将获取的依靠注入到当时实例中
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}


  1. 假如objectFactory是空(证明还没有加载扩展类),则先加载扩展类到dubbo的IOC。
  2. 获取setter办法的参数和类型, 并从dubbo的IOC中拿到依靠目标的实例。
  3. 反射将依靠目标注入到当时实例中。

objectFactory为什么必定不是空呢,由于再调用ExtensionLoader.getExtensionLoader(Color.class);办法的时分, dubbo现已对扩展类IOC进行初始化了, 概况看下面。

其实在调用getExtensionLoader的时分, 就把objectFactory实例化了, 并且初始化了IOC:

public static  ExtensionLoader getExtensionLoader(Class type) {
...
if (loader == null) {
// 在这儿new的目标
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
}
return loader;
}

private ExtensionLoader(Class
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

它先后调用了这几个办法:

  1. getAdaptiveExtension(从缓存里取,没有就调用下个办法)
  2. createAdaptiveExtension(从缓存里取,没有就调用下个办法)
  3. getAdaptiveExtensionClass 调用办法4, 然后调用办法5
  4. getExtensionClasses这个办法就和前面(loader.getExtension("yellow");)的相同了。
  5. createAdaptiveExtensionClass 办法,默许运用javassist生成署理字节码,然后用dubbo的Compiler解析成类并回来。

不知不觉中,dubbo的SPI还有自己共同的AOP和IOC源码就看完了。

你或许疑惑,我没看到AOP啊。

记住解析Wrapper那一块吗, dubbo用结构办法实例化Wrapper再将依靠注入,就完成了相似AOP的功用, 你能够再Wrapper里边定制你的逻辑。

由于篇幅够长了,javassist部分足以拿出来重立一篇来讲,假如你读完感到很茫然,那就再读一遍,读源码,最重要的是自动接纳的心态。多调试几遍,加油!

原文:https://mp.weixin.qq.com/s/782QMYBxzbi8Ms8uHLAJpw

作者:奔头哥

来历:微信大众号

免费共享java技术资料,需求的朋友能够在重视后私信我

请关注微信公众号
微信二维码
不容错过
Powered By Z-BlogPHP