dubbo-探索之旅(二)---扩展JDK的SPI

  在此感谢http://blog.csdn.net/quhongwei_zhanqiu/article/details/41577235。斩秋的文章。以下的大部分是在参考了斩秋的文章。
 

   上一节简单的对dubbo的外围知识进行了简单的打探,下面开始真正进入dubbo。开启dubbo的源码探索之旅。
  JDK自带的SPI正因为有在上一节提到的缺点,因此在dubbo中对其进行了扩展。这节重点来看看dubbo是怎么扩展JDK的SPI。

dubbo中定义了注解:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
   /**
     * 缺省扩展点名。
     */
   String value() default "";
}

在dubbo中只有在接口打了@SPI注解的接口,它的实现类才会被当成扩展点的实现进行查找。
例如:接口:Protocol 被SPI注解标注,默认扩展点的名字为dubbo
 @SPI("dubbo")
 public interface Protocol {
  //省
 }


此接口的实现类(相关类):



dubbo中RPC模块默认协议(Protocol )的实现是:DubboProtocol,key:dubbo

  这里插一句:dubbo加载扩展点的地方:
  1)META-INF/dubbo/internal/   //dubbo内部实现的各种扩展都放在了这个目录了
  2)META-INF/dubbo/
  3)META-INF/services/

因此定义DubboProtocol的文件在:

查看com.alibaba.dubbo.rpc.protocol文件中的内容:
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol。Protocol的其中一个扩展点就在此定义。


在文件中定义了接口的扩展点之后,最重要的就是读取配置文件中的信息,并且按需加载相应的扩展点的实现。在dubbo中ExtensionLoader类负责加载扩展点的实现。
下面来重点看看ExtensionLoader类
  首先对ExtensionLoader说明一下:
  1)每一个ExtensionLoader实例仅负责加载被SPI注解扩展的实现
  2)每一个ExtensionLoader实例只负责加载一个特定扩展点实现
  3)每一个扩展点(一个被SPI注解的接口)对应最多只有一个ExtensionLoader实例
  4)每一个扩展点对应最多只有一个ExtensionLoader实例
  5)对于每一个扩展点的每一个实现类,只会有一个实例。


下面介绍一下ExtensionLoader类中的几个重点方法:

1,ExtensionLoader.getExtensionLoader(Protocol.class)
   根据接口类型创建一个ExtensionLoader并且放入缓存中:EXTENSION_LOADERS(一个静态对象),如果已经存在了该接口类型的ExtensionLoader实例,就直接返回。
2,loadExtensionClasses():
   读取扩展点中的实现类
   1)先读取SPI注解的value值,如果有作为默认扩展实现类的key。
   2)依次读取路径的文件
      META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
      META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol
      META-INF/services/com.alibaba.dubbo.rpc.Protocol
3,loadFile():
   逐行读取com.alibaba.dubbo.rpc.Protocol文件中的内容,每行内容以key/value形式存储。
   1)判断实现类上有没有被@Adaptive注解:
      11)如果被@Adaptive的注解,将此实现类作为Protocol的适配类缓存起来,赋值给cachedAdaptiveClass属性,(只能有一个@Adaptive实现类,出现第二个就会报错了)。
      12)如果没有被@Adaptive注解,
        121)判断实现类是否存在入参为接口的构造器,如果存在作为包装类缓存到Set<Class<?>> cachedWrapperClasses中(装饰模式)
        122)如果不是适配类不是包装类,就是扩展点的具体实现对象。将实现类缓存到cachedClasses属性中。

4,创建或获取适配对象getAdaptiveExtension
   1)如果cachedAdaptiveClass有值,说明有且仅有一个实现类打了@Adaptive。
       Object instance = cachedAdaptiveInstance.get();
   2)如果cachedAdaptiveClass为空, 创建适配类字节码
      为什么要创建适配类?一个接口多种实现,SPI机制也是如此,这是策略模式。但是我们在代码执行过程中选择哪种具体的策略呢?Dubbo采用统一数据模式com.alibaba.dubbo.common.URL(它是dubbo定义的数据模型不是jdk的类),它会穿插于系统的整个执行过程,URL中定义的协议类型字段protocol,会根据具体业务设置不同的协议。url.getProtocol()值可以是dubbo也是可以webservice。
     适配类的作用是根据url.getProtocol()的值extName,去ExtensionLoader. getExtension( extName)选取具体的扩展点实现。

插一句:
    能够利用javasist生成适配类的条件
      1)接口方法中必须至少有一个方法打上了@Adaptive注解
      2)打上了@Adaptive注解的方法参数必须有URL类型参数或者有参数中存在getURL()方法。

5,通过createAdaptiveExtensionClassCode()会生成Protocol接口的适配类的java源码代码。如果生成的适配类源码要被java虚拟机加载执行的话,那必须编译成字节码。dubbo提供两种方式去执行代码的编译1)利用JDK工具类编译2)利用javassit根据源代码生成字节码。
后面会专门探索一下dubbo中的compiler接口。

@Adaptive注解在实现类上和在接口方法上的区别:
1)在实现类上:通过获取扩展文件的时候遇到注解@Adaptive,就把这个类作为适配类缓存在ExtensionLoader中,调用的时候直接返回。
2)在接口上:首先生成JAVA代码。在通过编译器编译成class加载。

6,自动Wrap上扩展点的Wrap类
     我们还是拿protocol来说,ProtocolFilterWrapper, ProtocolListenerWrapper这个两个类是装饰对象实现protocol,用来增强其他扩展点实现的功能。ProtocolFilterWrapper功能主要是在refer 引用远程服务的中透明的设置一系列的过滤器链用来记录日志,处理超时,权限控制等等功能;ProtocolListenerWrapper在provider的exporter,unporter服务和consumer的refer服务,destory调用时添加监听器,dubbo提供了扩展但是没有默认实现哪些监听器。

     Dubbo是如何自动的给扩展点wrap上装饰对象的呢?
       1)在ExtensionLoader.loadFile加载扩展点配置文件的时候
          对扩展点类有接口类型为参数的构造器就是包转对象,缓存到集合中去
       2)在调ExtensionLoader的createExtension(name)根据扩展点key创建扩展的时候, 先实例化扩展点的实现, 在判断时候有此扩展时候有包装类缓存,有的话利用包转器增强这个扩展点实现的功能。
     
       private T createExtension(String name) {
        Class<?> clazz = getExtensionClasses().get(name);//获取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);
            }
            injectExtension(instance);//扩展点注入
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && wrapperClasses.size() > 0) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    //循环遍历所有wrapper实现,实例化wrapper并进行扩展点注入
                    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);
        }
    }
      


7. dubbo的ExtensionLoader在加载扩展实现的时候内部实现了个简单的ioc机制来实现对扩展实现所依赖的参数的注入,dubbo对扩展实现中公有的set方法且入参个数为一个的方法,尝试从对象工厂ObjectFactory获取值注入到扩展点实现中去。
     /**
     * 扩展点参数的自动注入
     * @param instance
     * @return
     */
    private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {// 处理所有set方法
                        Class<?> pt = method.getParameterTypes()[0];// 获取set方法参数类型
                        try {
                            // 获取setter对应的property名称
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            Object object = objectFactory.getExtension(pt, property);// 根据类型,名称信息从ExtensionFactory获取
                            if (object != null) {
                                method.invoke(instance, object);
                                // 如果不为空,说set方法的参数是扩展点类型
                                // instance对象调用method方法,参数为object
                                // 这样来实现动态注入
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }


   AdaptiveExtensionFactory持有所有ExtensionFactory对象的集合,dubbo内部默认实现的对象工厂是SpiExtensionFactory和SpringExtensionFactory,他们存放在ArrayList对象中,getExtension(Class<T> type, String name)遍历获取Extension对象,如果遍历完集合中没有找到扩展对象,就返回NULL。
   1)SpiExtensionFactory工厂获取要被注入的对象,就是要获取dubbo spi扩展的实现,所以传入的参数类型必须是接口类型并且接口上打上了@SPI注解,返回的是一个适配类对象。
   2) SpringExtensionFactory,Dubbo利用spring的扩展机制跟spring做了很好的融合。在发布或者去引用一个服务的时候,会把spring的容器添加到SpringExtensionFactory工厂集合中去, 当SpiExtensionFactory没有获取到对象的时候会遍历SpringExtensionFactory中的spring容器来获取要注入的对象。

最后附上一个活动图(参照斩秋博主的所画):





总结:
ExtensionLoader加载扩展点的各种类型的实现类
   适配类:每个扩展点只会有一个适配实现类出现。如果没有找到适配类,会使用JDK动态代理或者是javasist动态生成一个。
   装饰类:实现类是否存在入参为接口的构造函数,如果有就是装饰类。可以多个
   实现类:如果不是适配类不是包装类,就是扩展点的具体实现对象

以上是我个人的对dubbo的一些见解,欢迎大家一起讨论!

下一节,我想看看ExtensionFactory

猜你喜欢

转载自jishuaige.iteye.com/blog/2332214