文档章节

dubboSPI机制浅谈

hyssop
 hyssop
发布于 2015/11/16 14:59
字数 1796
阅读 1217
收藏 6

本文重点讲述SPI机制,从jdk和dubbo

1、jdk spi机制

2、dubbo spi实现

首先spi是什么?

SPI是为某个接口寻找服务实现的机制。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。

其次java spi是怎么找到实现类的?

java spi和所有实现接口的厂商有一个俗称的约定,只要将META-INF/services文件夹下生成一个和抽象类全名称(路径+类名称)相同的配置文件,那么厂商的jar包只要在工程路径下就能找到实现类。这种方式主要是解决不同厂商不同实现类的加载问题。在不修改java文件的情况下,如何才能够定位到不同的实现类。

JDK的spi机制有一个缺点,就是如果多个厂商的spi实现的jar包都在路径下,那么就要加载所有的实现类,这样很浪费资源。dubbo中每一种接口都有很多种实现,如果使用jdk这种方式自然做不到优雅的根据一个接口来获得该接口的实现。dubbo的目标就是:根据你配置的name获取某一个特定的接口实现,没有用到的其他接口实现就不能被实例化,免得浪费。因此,dubbo就按照SPI机制的原理自己实现了一套扩展机制。为实现dubbo的扩展机制,dubbo的SPI做到了一下三个方面。

1 可以方便的获取某一个想要的扩展实现,java的SPI机制就没有提供这样的功能
2 对于扩展实现IOC   依赖注入功能

3 对扩展采用装饰器模式进行功能增强,类似AOP实现的功能

dubbo的SPI实现具体如下。

首先定义一个SPI注解类

@Documented
@Retention
(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {

/**
    * 缺省扩展点名。
    */
String value() default "";

}

dubbo里面很多的接口都打了SPI注解,这些注解的实现要从一下三个文件夹下去寻找实现。

META-INF/dubbo/internal/   //dubbo内部实现的各种扩展都放在了这个目录了

META-INF/dubbo

/META-INF/services/



文件里面的内容全部都是key-value的形式存在。dubbo的各种接口有很多类型的实现。拿Protocol举例,它的实现:DubboProtocol InjvmProtocolHessianProtocol WebServiceProtocol等等。dubbo的扩展机制如何去查找你的实现类,并实现调用的呢?

ExtensionLoader<T>
拿ServiceConfig<T>中的这两个变量举例子。
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
这两个变量最终生成的既不是接口也不是具体的实现类,而是一个接口适配器类。这个适配器类动态的生成的,如下所示的代码:

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adpative implements com.alibaba.dubbo.rpc.ProxyFactory {
public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = url.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
//根据url的信息去获取真实的实现类
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getProxy(arg0);
}
public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2) throws java.lang.Object {
if (arg2 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg2;
String extName = url.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getInvoker(arg0, arg1, arg2);
}
}

首先,我们来看适配器类是如何生成

com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);


第一步:通过proxyFactory.class类型生成一个ExtensionLoader<T>实例。

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {每个定义的spi的接口都会构建一个ExtensionLoader实例,存储在EXTENSION_LOADERS
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
    loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;}


第二步:创建Adaptive实例,放入数组cachedAdaptiveInstance中。

public T getAdaptiveExtension() {
    Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if(createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {//创建Adaptive实例,放入数组cachedAdaptiveInstance中。
                        instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                    }
                }
            }
        }
else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
        }
    }
return (T) instance;
}private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
    }
}private Class<?> createAdaptiveExtensionClass() {
//生成类适配器,
    String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
    com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();//编译这个类适配器为class文件
return compiler.compile(code, classLoader);
}
// 此方法已经getExtensionClasses方法同步过。
private Map<String, Class<?>> loadExtensionClasses() {//先读取SPI注解的value值,有值作为默认扩展实现的key
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if(defaultAnnotation != null) {
        String value = defaultAnnotation.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<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();//读取三个文件夹下的文件,将key-class放置到extensionClasses中
    loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadFile(extensionClasses, DUBBO_DIRECTORY);
    loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
//获取文件路径 目录+type名称
    String fileName = dir + type.getName();
    try {
        Enumeration<java.net.URL> urls;
        //类加载器
        ClassLoader classLoader = findClassLoader();
        if (classLoader != null) {
            urls = classLoader.getResources(fileName);
        } else {
            urls = ClassLoader.getSystemResources(fileName);
        }
        if (urls != null) {
            while (urls.hasMoreElements()) {
                java.net.URL url = urls.nextElement();
                try {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
                    try {
                        String line = null;
                        while ((line = reader.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);
//判断实现类是否实现了type接口
                                        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.");
                                        }
//类中是否有方法打了Adaptive注解
                                        if (clazz.isAnnotationPresent(Adaptive.class)) {
//将打了Adaptive注解的类放置到cachedAdaptiveClass 中去。
  if(cachedAdaptiveClass == null) {
      cachedAdaptiveClass = clazz;
   }
 else if (! cachedAdaptiveClass.equals(clazz)) {
                                                throw new IllegalStateException("More than 1 adaptive class found: "
                                                        + cachedAdaptiveClass.getClass().getName()
                                                        + ", " + clazz.getClass().getName());
                                            }
                                        } else {
                                       
                                              //判断该是否有实现类是否存在入参为接口的构造器
                                                clazz.getConstructor(type);
                                                Set<Class<?>> wrappers = cachedWrapperClasses;
                                                if (wrappers == null) {
                                                    cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                                                    wrappers = cachedWrapperClasses;
                                                }
                                                //按照装饰器的角色将该类加进来
                                                wrappers.add(clazz);
                                           
            } // end of while urls
        }
    } catch (Throwable t) {
        logger.error("Exception when load extension class(interface: " +
                type + ", description file: " + fileName + ").", t);
    }
}

适配器中发现这个实现类也是由这个方法生成extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName)


//创建扩展类
instance = createExtension(name);
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 (instance == null) {//容器中不存在,则按照类-实例对的形式放置到容器中
            EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());//获取该实例数据对
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }//set注入参数
        injectExtension(instance);//获取包装器类。
        class com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper   class com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
  Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && wrapperClasses.size() > 0) {
            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的SPI扩展机制,使得我们给出url就能动态的去获取真实的实现类。获得到真实的实现类,是实现功能的第一步。下面我们来看看spring如何加载到dubbo解析器类,并将dubbo功能收入囊下的。未完待续。


参考文献

http://m.blog.csdn.net/blog/u010311445/41577235





© 著作权归作者所有

上一篇: 注解 @Scheduled
hyssop
粉丝 20
博文 102
码字总数 111521
作品 0
昌平
程序员
私信 提问
Java 定时任务调度工具 Quartz(Part 2)

一、浅谈Job和JobDetail 1、Job接口:实现业务逻辑的任务接口,execute方法中实现具体逻辑(类似与TimerTask的run方法), 1.1 Job实例在Quartz中的生命周期: 每次调度器执行Job时,它在调用...

nutsKevin
2018/01/18
65
0
《浅谈JavaScript系列》系列技术文章整理收藏

《浅谈JavaScript系列》系列技术文章整理收藏 1浅谈JavaScript中面向对象技术的模拟 2浅谈javascript函数劫持[转自xfocus]第1/3页 3浅谈javascript 面向对象编程 4老鱼 浅谈javascript面向对...

开元中国2015
2015/07/27
1K
0
细说JavaScript数据类型及转换

细说JavaScript数据类型及转换 JavaScript数据类型 1.Boolean(布尔) 布尔:(值类型)var b1=true;//布尔类型 2.Number(数字) 数值:(值类型)var n1=3.1415926;//数值类型 n1.toFixed...

开元中国2015
2015/07/13
95
1
浅谈 Attention 机制的理解

什么是注意力机制? 注意力机制模仿了生物观察行为的内部过程,即一种将内部经验和外部感觉对齐从而增加部分区域的观察精细度的机制。例如人的视觉在处理一张图片时,会通过快速扫描全局图像...

Maple17
06/17
0
0
浅谈Cookie与Session

  前几天接触了一个实现自动登录的项目,其中涉及到了Cookie和Session的相关知识,忽然发现,虽然自己经常使用Cookie和Session,但对两者的原理和联系却知之甚少,特写一篇博客来总结一下。...

HAHawk
05/20
0
0

没有更多内容

加载失败,请刷新页面

加载更多

SpringBoot中 集成 redisTemplate 对 Redis 的操作(二)

SpringBoot中 集成 redisTemplate 对 Redis 的操作(二) List 类型的操作 1、 向列表左侧添加数据 Long leftPush = redisTemplate.opsForList().leftPush("name", name); 2、 向列表右......

TcWong
今天
3
0
排序––快速排序(二)

根据排序––快速排序(一)的描述,现准备写一个快速排序的主体框架: 1、首先需要设置一个枢轴元素即setPivot(int i); 2、然后需要与枢轴元素进行比较即int comparePivot(int j); 3、最后...

FAT_mt
昨天
4
0
mysql概览

学习知识,首先要有一个总体的认识。以下为mysql概览 1-架构图 2-Detail csdn |简书 | 头条 | SegmentFault 思否 | 掘金 | 开源中国 |

程序员深夜写bug
昨天
10
0
golang微服务框架go-micro 入门笔记2.2 micro工具之微应用利器micro web

micro web micro 功能非常强大,本文将详细阐述micro web 命令行的功能 阅读本文前你可能需要进行如下知识储备 golang分布式微服务框架go-micro 入门笔记1:搭建go-micro环境, golang微服务框架...

非正式解决方案
昨天
7
0
前端——使用base64编码在页面嵌入图片

因为页面中插入一个图片都要写明图片的路径——相对路径或者绝对路径。而除了具体的网站图片的图片地址,如果是在自己电脑文件夹里的图片,当我们的HTML文件在别人电脑上打开的时候图片则由于...

被毒打的程序猿
昨天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部