概述
Dubbo系列之自定义SPI协议拓展点_codain的博客-CSDN博客Dubbo系列之自定义SPI协议拓展点https://blog.csdn.net/qq_38377525/article/details/123002941这上面的链接中,讲解了Dubbo的SPI机制实现步骤,接下来我们要讲解一下Dubbo是如何实现SPI或者说SPI运行原理是怎么样的,正好我们也可以学习一下Dubbo框架的底层设计
上面链接中最后调用SPI的时候,使用的是getExtension(),那么我们就从这个方法出发,来分析一下Dubbo中是如何实现SPI的
首先,这段代码是有两层的:
第一层:
是我们通过getExtensionLoader获得一个ExtensionLoader实例,然后通过getExtension()方法来获得指定key的拓展点,我们在这一层进一步分析
1、ExtensionLoader.getExtensionLoader
这个方法用于返回一个ExtensionLoader实例,他的逻辑如下:
①先从缓存中获取与拓展类对应的ExtensionLoader,可以查询ExtensionLoader这个类
②如果未命中缓存,就创建一个实例,保存到EXTENSION_LOADERS集合中缓存起来
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { //如果没有拓展点类,就抛异常 if (type == null) { throw new IllegalArgumentException("Extension type == null"); //如果入参的类不是接口类,抛异常 } else if (!type.isInterface()) { throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); //如果没有使用@SPI注解,就抛异常 } else if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } else { //先读取缓存 ExtensionLoader<T> loader = (ExtensionLoader)EXTENSION_LOADERS.get(type); if (loader == null) { //如果缓存是null,就需要创建实例,并缓存 EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type)); loader = (ExtensionLoader)EXTENSION_LOADERS.get(type); } return loader; } }
③在ExtensionLoader构造方法中,初始化一个工厂类
private ExtensionLoader(Class<?> type) { this.type = type; this.objectFactory = type == ExtensionFactory.class ? null : (ExtensionFactory)getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension(); }
2、getExtension()
这个方法是根据指定的名称,来获得对应的拓展点并返回,比如getExtension()的入参如果是mysqlDriver,那么返回的实现类就是MysqlDriver
①name入参用于判断参数,如果name=true,就会返回一个默认的拓展实现
②创建一个Holder对象,用户缓存该拓展点的实例
③如果缓存不存在,就会通过createExtension(name)来创建一个拓展点
public T getExtension(String name) { //如果没有拓展点,就报错 if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name == null"); //如果是true,就返回默认的拓展点 } else if ("true".equals(name)) { return this.getDefaultExtension(); } else { //创建Holder对象 Holder<Object> holder = this.getOrCreateHolder(name); //获取缓存数据 Object instance = holder.get(); if (instance == null) { synchronized(holder) { instance = holder.get();//源码代码重复了 if (instance == null) { //如果缓存不存在,就需要创建一个拓展点 instance = this.createExtension(name);//看下方代码剖析 holder.set(instance); } } } return instance; } }
3、createExtension(name)创建拓展点源码如下:
private T createExtension(String name) { //返回拓展类 Class<?> clazz = (Class)this.getExtensionClasses().get(name);//看下一段源码片段 if (clazz == null) { throw this.findException(name); } else { try { //获取缓存中该类的实例 T instance = EXTENSION_INSTANCES.get(clazz); if (instance == null) { //ruguo EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = EXTENSION_INSTANCES.get(clazz); } //依赖注入 this.injectExtension(instance); //包装 Set<Class<?>> wrapperClasses = this.cachedWrapperClasses; Class wrapperClass; if (CollectionUtils.isNotEmpty(wrapperClasses)) { //循环注入,利用到了装饰器模式 for(Iterator var5 = wrapperClasses.iterator(); var5.hasNext(); instance = this.injectExtension(wrapperClass.getConstructor(this.type).newInstance(instance))) { wrapperClass = (Class)var5.next(); } } //返回实例 return instance; } catch (Throwable var7) { throw new IllegalStateException("Extension instance (name: " + name + ", class: " + this.type + ") couldn't be instantiated: " + var7.getMessage(), var7); } } }
上面是第一层的代码,其实是加载拓展类的关键实现,其他部分都是不主要但不可少的功能,第二层就要从依赖注入和Wrapper来分析了
第二层:
4、getExtensionClasses()方法直接返回一个Map集合
key和value对应的就是resources下的文件的key和value
private Map<String, Class<?>> getExtensionClasses() { //获取缓存中的拓展类 Map<String, Class<?>> classes = (Map)this.cachedClasses.get(); //如果没有缓存数据 if (classes == null) { Holder var2 = this.cachedClasses; synchronized(this.cachedClasses) { classes = (Map)this.cachedClasses.get(); if (classes == null) { //调用loadExtensionClasses重新加载拓展类 classes = this.loadExtensionClasses();//看下一段代码分析 this.cachedClasses.set(classes); } } } return classes; }
Dubbo的代码其实大家不难看出,基本都是先访问缓存,没有就通过loadExtensionClasses来加载,那这个方法是怎么实现得嘞,继续看
5、loadExtensionClasses()
private Map<String, Class<?>> loadExtensionClasses() { //获取当前type接口默认的拓展类并缓存 this.cacheDefaultExtensionName(); Map<String, Class<?>> extensionClasses = new HashMap(); //解析并获取指定路径下的拓展点文件 this.loadDirectory(extensionClasses, "META-INF/dubbo/internal/", this.type.getName()); this.loadDirectory(extensionClasses, "META-INF/dubbo/internal/", this.type.getName().replace("org.apache", "com.alibaba")); this.loadDirectory(extensionClasses, "META-INF/dubbo/", this.type.getName()); this.loadDirectory(extensionClasses, "META-INF/dubbo/", this.type.getName().replace("org.apache", "com.alibaba")); this.loadDirectory(extensionClasses, "META-INF/services/", this.type.getName()); this.loadDirectory(extensionClasses, "META-INF/services/", this.type.getName().replace("org.apache", "com.alibaba")); return extensionClasses; }
其中的loadDirectory()方法大家也能看出来是什么意思,就是从指定的这几个目录下,找到入参进来的type对应的文件,解析文件内容之后加载并保存到extensionClasses集合中,第一行的cacheDefaultExtensionName()这个方法也很简单,但是逻辑稍微偏业务一点,需要再对他分析一波
6、cacheDefaultExtensionName()
private void cacheDefaultExtensionName() { //获得入参的type类上面的注解@SPI SPI defaultAnnotation = (SPI)this.type.getAnnotation(SPI.class); if (defaultAnnotation != null) { //获取拓展点文件的内容value 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 " + this.type.getName() + ": " + Arrays.toString(names)); } //保存到cachedDefaultName属性中 if (names.length == 1) { this.cachedDefaultName = names[0]; } } } }
@SPI注解默认值是dubbo,只是没有显式出来,全称是@SPI("dubbo")
以上就是Dubbo针对SPI的源码实现,其实并不复杂,好好消化一下吧,接下来还会讲到另外一种:自适应拓展点(Adaptive),感兴趣的话,可以关注一下哦
最后
以上就是害羞雪糕为你收集整理的Dubbo系列之SPI拓展点源码分析的全部内容,希望文章能够帮你解决Dubbo系列之SPI拓展点源码分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复