1. 程式人生 > >SPI(Service Provider Interface)--通過接口獲取服務

SPI(Service Provider Interface)--通過接口獲取服務

obj 資源 源文件 tac bubuko 註解 nfa amp factory

spi 現在已有實現

  1. jdk 提供實現
  2. dubbo裏的spi實現

一、jdk實現

  • 配置
    1. 定義接口
    2. 定義實現類
    3. 配置資源文件 classpath下創建(META-INF/services/接口全面:META-INF/services/spring.design.mode.test4.spi.DogService)

 技術分享圖片

  • 調用方法
        ServiceLoader<DogService> loaders = ServiceLoader.load(DogService.class);
        for (DogService d : loaders) {
            d.sleep();
        }

  • 測試結果
黑色dog。。。汪汪叫,不睡覺...
白色dog。。。呼呼大睡覺...

  • 代碼下載

    https://files.cnblogs.com/files/z-test/spi-jdk.rar

二、dubbo 裏的spi實現。

  • 用法介紹
         //得到一個自適應實現類,用@Adaptive 註解的類,沒有就自動生產一個自適應類,可以根據調用方法的參數,動態獲取處理類
         ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
         
         
//得到默認的實現類 ExtensionLoader.getExtensionLoader(Protocol.class).getDefaultExtension(); //通過名字得到實現類。 ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("name"); //通過url裏的 key對應的參數獲取實現類。1.先加載默認的激活實現,2加載key對應的value裏的值。 ExtensionLoader.getExtensionLoader(Protocol.class
).getActivateExtension(url, key);
  • 配置
    • 文件位置
//加載順序 DUBBO_INTERNAL_DIRECTORY,DUBBO_DIRECTORY,SERVICES_DIRECTORY
    //即用戶可以覆蓋調源碼裏的實現。
    
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
    
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
    
    private static final String SERVICES_DIRECTORY = "META-INF/services/";
    • 配置文件內容如下 (文件名:接口全名;內容是key=value:自定義名稱:接口實現類全名)

技術分享圖片

  • 註解解釋
    • @SPI("dubbo") 標記接口,提供一個默認的實現 一便於getDefaultExtension()得到默認實現類
    • @Adaptive
      •   1.出現在實現類上,.getAdaptiveExtension() 可以得到對應的實現類,
      •   2.出現在 接口裏的對應的方法上 強制要求對應的方法有URL參數,或者參數裏包含URL對象。(否則執行報錯)
        • 實現類是com.alibaba.dubbo.rpc.Protocol 動態獲取協議 根據url.getProtocol() == null ? "dubbo" : url.getProtocol() 獲取協議名稱,url參數protocol 為空時使用默認名稱即@SPI("dubbo")中的名稱
        •     public int getDefaultPort() {
                  throw new UnsupportedOperationException(
                          "method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
              }
          
              public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0)
                      throws com.alibaba.dubbo.rpc.RpcException {
                  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.getProtocol() == null ? "dubbo" : url.getProtocol());
                  if (extName == null)
                      throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url("
                              + url.toString() + ") use keys([protocol])");
                  com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader
                          .getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
                  return extension.export(arg0);
              }

        • 接口對應方法中含有com.alibaba.dubbo.rpc.Invocation的根據 url.getMethodParameter(methodName, "cache", "lru");“cache“為參數名,lru 為@spi中指定的默認值
        •     public com.alibaba.dubbo.cache.Cache getCache(com.alibaba.dubbo.common.URL arg0,
                      com.alibaba.dubbo.rpc.Invocation arg1) {
                  if (arg0 == null)
                      throw new IllegalArgumentException("url == null");
                  com.alibaba.dubbo.common.URL url = arg0;
                  if (arg1 == null)
                      throw new IllegalArgumentException("invocation == null");
                  String methodName = arg1.getMethodName();
                  String extName = url.getMethodParameter(methodName, "cache", "lru");
                  if (extName == null)
                      throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.cache.CacheFactory) name from url("
                              + url.toString() + ") use keys([cache])");
                  com.alibaba.dubbo.cache.CacheFactory extension = (com.alibaba.dubbo.cache.CacheFactory) ExtensionLoader
                          .getExtensionLoader(com.alibaba.dubbo.cache.CacheFactory.class).getExtension(extName);
                  return extension.getCache(arg0, arg1);
              }

        • 其他的實現為url.getParameter("channel.handler", "all") 如果@Adaptive({Constants.DISPATCHER_KEY, "dispather", "channel.handler"}) url.getParameter("dispatcher",url.getParameter("dispather", url.getParameter("channel.handler", "all")));
        •     public com.alibaba.dubbo.remoting.ChannelHandler dispatch(com.alibaba.dubbo.remoting.ChannelHandler arg0,
                      com.alibaba.dubbo.common.URL arg1) {
                  if (arg1 == null)
                      throw new IllegalArgumentException("url == null");
                  com.alibaba.dubbo.common.URL url = arg1;
                  String extName = url.getParameter("dispatcher",
                          url.getParameter("dispather", url.getParameter("channel.handler", "all")));
                  if (extName == null)
                      throw new IllegalStateException(
                              "Fail to get extension(com.alibaba.dubbo.remoting.Dispatcher) name from url(" + url.toString()
                                      + ") use keys([dispatcher, dispather, channel.handler])");
                  com.alibaba.dubbo.remoting.Dispatcher extension = (com.alibaba.dubbo.remoting.Dispatcher) ExtensionLoader
                          .getExtensionLoader(com.alibaba.dubbo.remoting.Dispatcher.class).getExtension(extName);
                  return extension.dispatch(arg0, arg1);
              }

    • @Activate(group = {Constants.PROVIDER, Constants.CONSUMER}) 激活的類,適用於getActivateExtension(url, key) 1.先查找適配的@activie對應的類,2,查找url裏key的對應的value所對應的類。

  • 動態註入屬性(查找set方法,根據set方法參數類型和變量名查找可用對象註入到 spi生產的對象裏面。可以註入spi裏的對象和springcontext裏的對象)

  在 接口類對應的配置文件裏,有兩個實現類,spi 和spring

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}

spring 獲取需要註入的對象

public class SpringExtensionFactory implements ExtensionFactory {

    private static final Set<ApplicationContext> contexts = new ConcurrentHashSet<ApplicationContext>();

    public static void addApplicationContext(ApplicationContext context) {
        contexts.add(context);
    }

    public static void removeApplicationContext(ApplicationContext context) {
        contexts.remove(context);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getExtension(Class<T> type, String name) {
        for (ApplicationContext context : contexts) {
            if (context.containsBean(name)) {
                Object bean = context.getBean(name);
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }
        return null;
    }

}

spi

public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}

大家可以看看源碼,根據用法介紹裏的方法,跟進看看。

SPI(Service Provider Interface)--通過接口獲取服務