前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >dubbo源码之dubbo SPI

dubbo源码之dubbo SPI

作者头像
山行AI
发布2019-07-12 15:21:36
9630
发布2019-07-12 15:21:36
举报
文章被收录于专栏:山行AI

SPI 全称为 (Service Provider Interface),即服务提供商接口。常出现的地方有日志门面接口实现类加载、Spring SPI、JDK SPI、dubbo SPI、hadoop、solr cloud、elasticjob等,这里主要介绍下JDK SPI和dubbo SPI

1. JDK SPI机制

java.util.ServiceLoader:

  • Class service 要加载的接口
  • ClassLoader loader 类加载器
  • LazyIterator lookupIterator主要用来延迟加载
  • LinkedHashMap providers = new LinkedHashMap<>()用于缓存接口实现的提供者(线程不安全的)

它的使用方法是:

假设我们有一个服务类型是com.example.CodecSet,它有两个abstract方法:

代码语言:javascript
复制
public abstract Encoder getEncoder(String encodingName);public abstract Decoder getDecoder(String encodingName);

其中有一种提供者的编解码实现为com.example.impl.StandardCodecs,它实现了com.example.CodecSet的方法,那么可以在工程的 META-INF/services/com.example.CodecSet目录中添加一行com.example.impl.StandardCodecs # Standard codecs

然后在项目中就可以使用ServiceLoader.load(CodecSet.class)来获取和使用StandardCodecs了。

动态发现服务提供者有几点要求:

  • 实现者的类全限定名配置在META-INF/services/服务接口的全限定名对应的文件下;
  • 使用utf8编码
  • 工程中能加载到实现者的类

加载方式及优缺点

  1. 加载方式
  • 创建ServiceLoader:
代码语言:javascript
复制
java.util.ServiceLoader#load(java.lang.Class<S>, java.lang.ClassLoader): /**     * Creates a new service loader for the given service type and class     * loader.     *     * @param  <S> the class of the service type     *     * @param  service     *         The interface or abstract class representing the service     *     * @param  loader     *         The class loader to be used to load provider-configuration files     *         and provider classes, or <tt>null</tt> if the system class     *         loader (or, failing that, the bootstrap class loader) is to be     *         used     *     * @return A new service loader     */    public static <S> ServiceLoader<S> load(Class<S> service,                                            ClassLoader loader)    {        return new ServiceLoader<>(service, loader);    }

可以看到,这里可以传入自定义的类加载器,可以从其他目录读取。

  • 加载: 在上面的图中我们可以看到,ServiceLoader中有一个LazyIterator lookupIterator主要用来延迟加载:

实际加载过程在java.util.ServiceLoader#iterator方法中:

代码语言:javascript
复制
    public Iterator<S> iterator() {           return new Iterator<S>() {               Iterator<Map.Entry<String,S>> knownProviders                   = providers.entrySet().iterator();               public boolean hasNext() {                   if (knownProviders.hasNext())                       return true;                   return lookupIterator.hasNext();               }               public S next() {                   if (knownProviders.hasNext())                       return knownProviders.next().getValue();                   return lookupIterator.next();               }               public void remove() {                   throw new UnsupportedOperationException();               }           };       }

这里有两点需要注意:1.查找接口时只能通过迭代的方式,查找时实际上是使用的lookupIterator的迭带方法来获取; 2.providers在多线程并发操作时会有线程安全问题

  • 优点:java SPI机制可以实现接口的定义与接口实现方的解耦,在多个不同的实现方实现了一套相同的接口定义之后,调用方可以选择性地以jar形式加载某个实现方的实现。
  • 缺点: 1.虽然有延迟加载,但是只能通过遍历的形式获取实现,不能通过类型或参数的形式灵活获取。

2.并发操作时的线程不安全性,如providers的使用上面。

2. dubbo的SPI机制

dubbo中使用了大量的SPI,如rpc协议(Protocol),过滤器(Filter),序列化(Serialization),底层传输方式(Transporter),代理工厂(ProxyFactory),(注册中心)Registry,(负载均衡)LoadBalance,(容器)Container等多个地方,具体如下图:

ExtensionLoader

com.alibaba.dubbo.common.extension.ExtensionLoader是spi拓展加载器。在ExtensionLoader的源码注释上说明了其在jdk spi上做的几点改进:

  1. 自动注入关联扩展点。这个对应的是com.alibaba.dubbo.common.extension.ExtensionLoader#injectExtension方法,可以进行依赖注入。
代码语言:javascript
复制
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())) {                        Class<?> pt = method.getParameterTypes()[0];                        try {                            //针对set方法大小写区分规范的属性进行注入                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";                            Object object = objectFactory.getExtension(pt, property);                            if (object != null) {                                method.invoke(instance, 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;    }

这里注入的主要是@Adaptive和Wrapper实现。关于Adaptive和Wrapper后面会有专门的文章介绍。

  1. 自动Wrap上扩展点的Wrap类。比如:Protocol的两个实现类:ProtocolFilterWrapper、ProtocolListenerWrapper。
  2. 缺省获得的的扩展点是一个Adaptive Instance。

示例可以看看ServiceConfig中的使用方法:

关于Adaptive extension的部分后面会专门分析,这里咱们关注一下com.alibaba.dubbo.common.extension.ExtensionLoader#getAdaptiveExtension方法:

代码语言:javascript
复制
private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>(); public T getAdaptiveExtension() {        Object instance = cachedAdaptiveInstance.get();        if (instance == null) {            if(createAdaptiveInstanceError == null) {                synchronized (cachedAdaptiveInstance) {                    instance = cachedAdaptiveInstance.get();                    if (instance == null) {                        try {                            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;    }    public class Holder<T> {        private volatile T value;        public void set(T value) {            this.value = value;        }        public T get() {            return value;        }    }

这里是用一个Holder对象来缓存adaptive实例,使用double check的机制进行延迟加载,一方面防止重排序,另一方面降低同步的开销,Holder中的value为volatile是为了保证多线程条件下的线程可见性,这样double-check才能正常工作(http://gee.cs.oswego.edu/dl/cpj/updates.html)。相同的用法在 ExtensionLoader中还有很多,有兴趣的可以自己去翻一翻。

3. 总结

这里介绍了jdk spi和dubbo spi的相应机制,通过spi机制极大地提高了接口设计的灵活性,这也是dubbo能够达到高拓展性的一个基础。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开发架构二三事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. JDK SPI机制
    • 它的使用方法是:
      • 动态发现服务提供者有几点要求:
        • 加载方式及优缺点
        • 2. dubbo的SPI机制
          • ExtensionLoader
          • 3. 总结
          相关产品与服务
          负载均衡
          负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档