dubbo(二)dubbo|dubbo(二)dubbo spi机制
1 在介绍dubbo spi机制之前,我们先说一下jdk spi,spi:当服务提供者提供一个接口的多个实现的时候,一般会在META-INF/services/ 下面建立一个与接口全路径同名的文件,在文件里配置接口具体的实现类。当外部调用模块的时候的时候就能实例化对应的实现类而不需要动源码,
2 为什么dubbo不直接用jdk的spi机制,而是自己模仿实现了一个spi机制呢?jdk的spi会在一次实例化所有实现,可能会比较耗时,而且有些可能用不到的实现类也会实例化,浪费资源而且没有选择。另外dubbo的spi增加了对扩展点IOC和AOP的支持,一个扩展点可以直接setter注入其他扩展点。这是jdk spi不支持的。参考博客(https://blog.csdn.net/xujiajun1994/article/details/81023168)
下面详细介绍一下spi实现过程,对应的项目,已经上传github(https://github.com/xuws2gj/dubbo-spi.git)
1)项目全览
文章图片
1)代码过程解释
package com.shican.spi.loader;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Pattern;
import com.shican.spi.annotation.ShiCanSpi;
import com.shican.spi.util.Holder;
public class ShiCanExtensionLoader { /** shicanExtension点的路径 */
private static final String SHICAN_DIRECTORY = "META-INF/SHICAN/";
/**
* 分割SPI上默认拓展点字符串用的
*/
private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
/** 扩展点加载器的缓存 */
private static final ConcurrentHashMap, ShiCanExtensionLoader>> EXTENSION_LOADERS = new ConcurrentHashMap, ShiCanExtensionLoader>>();
/** 扩展点加缓存 */
private static final ConcurrentHashMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap, Object>();
/**
* 接口的class
*/
private final Class> type;
/** 保存接口ShiCanSpi注解上的值 */
private String cachedDefaultName;
/**
* 异常记录
*/
private Map exceptions = new ConcurrentHashMap();
private final Holder
package com.shican.spi.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author shican
*
*/@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface ShiCanSpi {
String value() default "";
}
package com.shican.spi.car;
import com.shican.spi.annotation.ShiCanSpi;
@ShiCanSpi("smallCar")
public interface Car {
void drive();
}
#shican -spi
bigCar=com.shican.spi.car.impl.BigCar
smallCar=com.shican.spi.car.impl.SmallCar
package com.shican.spi.util;
public class Holder {
private volatile T t;
public T getT() {
return t;
} public void setT(T t) {
this.t = t;
}}
关于SchiCanExtensionLoader类,运行test
1 先进入getExtensionLoader方法
public static ShiCanExtensionLoader getExtensionLoader(Class type) {
/***
* 判断type 接口参数()
*/
if (null == type) {
//第一次进来typecom.shican.spi.car.Car
throw new IllegalArgumentException("Extension type == null");
}
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @"
+ ShiCanSpi.class.getSimpleName() + " Annotation!");
}
//获取spi的加载类,如果是null则new一个出来再放到缓存中(放置map中)
// ConcurrentHashMap, ShiCanExtensionLoader>> EXTENSION_LOADERSShiCanExtensionLoader loader = (ShiCanExtensionLoader) EXTENSION_LOADERS.get(type);
if (null == loader) {
EXTENSION_LOADERS.putIfAbsent(type, new ShiCanExtensionLoader(type));
loader = (ShiCanExtensionLoader) EXTENSION_LOADERS.get(type);
}
return loader;
}
获取一个spi加载器实例,第一次进来为null则先new一个出来,再放到缓存之中(ConcurrentHashMap
2 进入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 = cachedInstances.get(name);
if (null == holder) {
cachedInstances.putIfAbsent(name, new Holder());
holder = cachedInstances.get(name);
}
Object instance = holder.getT();
if (null == instance) {
synchronized (holder) {
instance = holder.getT();
if (null == instance) {
instance = createExtension(name);
holder.setT(instance);
}
}}
return (T) instance;
}
此时根据getExtension(name)name名,相等于key,第一步先判断是否是默认实现是的话走getDefaultExtension,否则接下来,此时ConcurrentMap> cachedInstances作用存放对应实现instance,及key/value,先从cachedInstances缓存中取,没有则先new一个Holder,Holder结构即保存了一个Class的对象,getT()可以取出对象,而cachedInstances缓存Holder,就等于缓存这个实现类对象
2 进入createExtension方法
private T createExtension(String name) {
Class> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T)EXTENSION_INSTANCES.get(clazz);
if(null == instance){
EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
//反射生成对象
instance = (T)EXTENSION_INSTANCES.get(clazz);
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ")could not be instantiated: " + t.getMessage(), t);
}
}
3 进入getExtensionClasses方法
private Map> getExtensionClasses() {
Map> classes = cachedClasses.getT();
if (null == classes) {
synchronized (cachedClasses) {
classes = cachedClasses.getT();
if (null == classes) {
classes = loadExtensionClasses();
cachedClasses.setT(classes);
}
}
}
return classes;
}
先从cachedClasses(Holder中)缓存中取这个class,没有进入
4 进入loadExtensionClasses方法
private Map> loadExtensionClasses() {
final ShiCanSpi defaultAnnotation = type.getAnnotation(ShiCanSpi.class);
if (null != defaultAnnotation) {
String value = https://www.it610.com/article/defaultAnnotation.value();
// 拿到注解value --缺省值
if (value != null && (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];
}
}Map> extensionClasses = new HashMap>();
loadFile(extensionClasses, SHICAN_DIRECTORY);
return extensionClasses;
}
4 进入loadFile方法
private void loadFile(Map> extensionClasses, String dir) {
String fileName = dir + type.getName();
// 固定文件夹 + 接口名全路径// 对本地spi扩展文件操作
try {
Enumeration urls;
ClassLoader classLoader = findClassLoader();
if (null != classLoader) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (null != urls) {
while (urls.hasMoreElements()) {
java.net.URL url = urls.nextElement();
try {
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(url.openStream(), "UTF-8"));
try {
String line = null;
while ((line = bufferedReader.readLine()) != null) {
final int ci = line.indexOf('#');
if (ci > 0)
line = line.substring(0, ci);
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf("=");
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
Class> clazz = Class.forName(line, true, classLoader);
// 鼓掌!这里终于得到spi(classs)
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException(
"Error when load extension class(interface: " + type
+ ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
extensionClasses.put(name, clazz);
// 加入缓存
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException(
"Failed to load extension class(interface: " + type + ", class line: "
+ line + ") in " + url + ", cause: " + t.getMessage(),
t);
exceptions.put(line, e);
}
}
}
} finally {
bufferedReader.close();
}
} catch (Exception e) {
// ignore...
}
}
}
} catch (Exception e) {
// ignore...
} }
这个方法,通过流读取文件方式,把META-INF/SHICAN文件中的实现类都放在extensionClasses(Map)这个map之中
然后再返回到getExtensionClasses方法中把这个map放在了cachedClasses之中(即holder)
再返回到createExtension方法 根据name值取到对应的实现类,这时候在查看EXTENSION_INSTANCES是否有这个实现类,没有就把放进去即放入EXTENSION_INSTANCES缓存
再返回到getExtension方法,把拿出来的实现类放到cachedInstances之中,cachedClasses缓存所有实现类,cachedInstances缓存已经根据key即name取到的具体的实现类
再返回到test方法,用具体的实现类对象执行方法
(二) 关于dubbo -Adaptive详解
为什么要设计adaptive?注解在类上和注解在方法上的区别?
adaptive设计的目的是为了识别固定已知类和扩展未知类。
1.注解在类上:代表人工实现,实现一个装饰类(设计模式中的装饰模式),它主要作用于固定已知类,
目前整个系统只有2个,AdaptiveCompiler、AdaptiveExtensionFactory。(所以方法上有adaptive注解走AdaptiveCompiler.compile()再选择abstractcompile两个子类 jdkcompile还是javassistcompile(默认选择因为compile接口上有spi注解(javassist)))
a.为什么AdaptiveCompiler这个类是固定已知的?因为整个框架仅支持Javassist和JdkCompiler。
a.为什么AdaptiveExtensionFactory这个类是固定已知的?因为整个框架仅支持2个objFactory,一个是spi,另一个是spring
2.注解在方法上:代表自动生成和编译一个动态的Adpative类,它主要是用于SPI,因为spi的类是不固定、未知的扩展类,所以设计了动态$Adaptive类.
例如 Protocol的spi类有 injvm dubbo registry filter listener等等 很多扩展未知类,
它设计了Protocol$Adaptive的类,通过ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(spi类); 来提取对象
关于这个注解,我选了比较好的博客介绍一下
https://www.liangzl.com/get-article-detail-5461.html
https://segmentfault.com/a/1190000013342968?utm_source=tag-newest
5 总结
dubbo spi机制,即定义了各种实现类的配置文件,再根据具体的key寻找具体实现类的过程,中间经历各种放入缓存操作,读取文件及反射,达到不同实现的扩展。而Adaptive注解注在类上,代表已知实现类,优先使用,注在接口的方法上,代表会生成一个$Adaptive适配类, 下一节,我们谈论一下dubbo暴露服务的原理
【dubbo(二)dubbo|dubbo(二)dubbo spi机制】
推荐阅读
- EffectiveObjective-C2.0|EffectiveObjective-C2.0 笔记 - 第二部分
- 遇到一哭二闹三打滚的孩子,怎么办┃山伯教育
- 赢在人生六项精进二阶Day3复盘
- 2019年12月24日
- 陇上秋二|陇上秋二 罗敷媚
- 一百二十三夜,请嫁给我
- 迷失的世界(二十七)
- live|live to inspire 一个普通上班族的流水账0723
- 我要我们在一起(二)
- 基于|基于 antd 风格的 element-table + pagination 的二次封装