逐步深入 Dubbo SPI 原理

什么是 SPI ?

SPI(Service Provider Interface) 是一種服務(wù)發(fā)現(xiàn)機制, 主要原理是在運行時根據(jù)具體參數(shù)去查找約定路徑(JDK 默認(rèn)是在/META-INF/services/)下的配置實現(xiàn)類信息划乖。 通過然后類加載機制(ClassLoader)實現(xiàn)對類信息的加載以及后面的實例化浅妆,實現(xiàn)黑盒擴展的作用语盈。簡單點來說就是在運行時動態(tài)指定并加載實現(xiàn)類只酥,實現(xiàn)指定功能點擴展涨椒。

推薦連接高級開發(fā)必須理解的Java中SPI機制

Dubbo SPI 和JDK SPI 的區(qū)別

是的汪厨,Dubbo并沒有沿用JDK內(nèi)置的SPI機制包竹。 而是自行實現(xiàn)了一套SPI機制厉碟,從Dubbo的官方描述來看,Dubbo是對JDK的SPI進行了一次改進鼎姊。 那這里就有必要說明下巩那。JDK SPI有哪些問題呢? Dubbo 又是如何對其進行改進的呢此蜈?

JDK SPI問題

  • 需要遍歷所有的實現(xiàn),并實例化噪生,然后我們在循環(huán)中才能找到我們需要的實現(xiàn)裆赵。
  • 配置文件中只是簡單的列出了所有的擴展實現(xiàn),而沒有給他們命名跺嗽。導(dǎo)致在程序中很難去準(zhǔn)確的引用它們战授。
  • 擴展如果依賴其他的擴展,做不到自動注入和裝配
  • 不提供類似于Spring的IOC和AOP功能
  • 擴展很難和其他的框架集成桨嫁,比如擴展里面依賴了一個Spring bean植兰,原生的Java SPI不支持

Dubbo SPI的增強

  • 通過Map緩存的機制, 對Class類進行緩存璃吧,在運行時調(diào)用明確名稱的類楣导。 獲取對應(yīng)的類信息,進行實例化畜挨。 提高了啟動性能
  • Dubbo SPI 實現(xiàn)了類似IOC 和AOP 的機制筒繁。提供了查找和動態(tài)擴展的功能噩凹。
  • Dubbo 也提供了從擴展點容器獲取實例對象的功能, 如通過SpringExtensionFactory就實現(xiàn)了通過Spring的容器獲取依賴的功能。 可以更好的和其他框架進行集成毡咏。

Dubbo SPI示例

代碼示例

@SPI
public interface Car {
    void test();
}

//寶馬
public class BWMCar implements Car {

    @Override
    public void test() {
        System.out.println("hi驮宴, 我是寶馬車");
    }
}

public static void main(String[] args) {
        ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);

        Car car = extensionLoader.getExtension("bwm");
        car.test();
}

配置示例
在/META-INF/dubbo/目錄下創(chuàng)建文件com.wgt.dubbo.samples.spi.demo1.car.Car文件,配置信息如下

bwm=com.wgt.dubbo.samples.spi.demo1.car.BWMCar

輸出結(jié)果

hi呕缭, 我是寶馬車

從示例中的來看堵泽。 流程時先ExtensionLoader.getExtensionLoader(Class class) 獲取到一個ExtensionLoader示例, 然后調(diào)用ExtensionLoader的getExtension(String name)方法獲取到目標(biāo)實例, 那么我們從ExtensionLoader.getExtensionLoader(Car.class)開始恢总,進入源碼迎罗,弄清原理。

代碼的示例很簡單离熏, 但是里面的學(xué)問有很多佳谦, 不過不需要心急,我們需要確弊檀粒看源碼的過程中不會迷失钻蔑。 接下來的源碼解析將圍繞著本段示例代碼來進行步步深入分析, 盡量做到細(xì)微入致奸鸯。

Dubbo 加載拓展點

  • 創(chuàng)建ExtensionLoader實例源碼
@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null) {
        throw new IllegalArgumentException("Extension type == null");
    }
    // 判斷type 是否為接口
    if (!type.isInterface()) {
        throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
    }
    // 判斷是否標(biāo)注了@SPI注解
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type (" + type +
                ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
    }
        // 從緩存中獲取ExtensionLoader咪笑。 
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        //緩存中沒有的話 則創(chuàng)建一個新的ExtensionLoader加入緩存中。
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

//構(gòu)造方法
private ExtensionLoader(Class<?> type) {
        this.type = type;
            //此處默認(rèn)的objectFactory 為org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory娄涩,
            // 該處的objectFactory 相當(dāng)于一個IOC容器窗怒。 后面在IOC部分詳細(xì)說明
        objectFactory = (type == ExtensionFactory.class ? null :            ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

從中可以看出創(chuàng)建ExtensionLoader實例的源碼是比較簡單的。 疑問點objectFactory對象工廠我們放到后面再說蓄拣。接下來我們先看getExtension方法都做了什么扬虚。

  • getExtension方法源碼分析
@SuppressWarnings("unchecked")
public T getExtension(String name) {
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    // 緩存中根據(jù)name 獲取加載過的實例
    final Holder<Object> holder = getOrCreateHolder(name);
    Object instance = holder.get();
   
    // double check synchronized 確保線程安全
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                // 創(chuàng)建新的Extension 信息
                instance = createExtension(name);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

// 創(chuàng)建新的實例對象
@SuppressWarnings("unchecked")
private T createExtension(String name) {
  // 此處會讀取/META-INF/services/ 下的擴展點文件信息。 將所有類信息加載進來后根據(jù)name 獲取具體的實例對象
  // 當(dāng)前示例主要說明點球恤。 
  Class<?> clazz = getExtensionClasses().get(name);
  if (clazz == null) {
    throw findException(name);
  }
  try {
    T instance = (T) EXTENSION_INSTANCES.get(clazz);
    if (instance == null) {
      // 根據(jù)Class 信息通過反射進行實例化辜昵。 
      EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
      instance = (T) EXTENSION_INSTANCES.get(clazz);
    }
    
    // 進行依賴注入 在IOC 內(nèi)容詳細(xì)說明
    injectExtension(instance);
    
    // AOP 內(nèi)容。 使用裝飾器模式實現(xiàn) 在AOP部分詳細(xì)說明
    Set<Class<?>> wrapperClasses = cachedWrapperClasses;
    if (CollectionUtils.isNotEmpty(wrapperClasses)) {
      for (Class<?> wrapperClass : wrapperClasses) {
        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
      }
    }
    // 調(diào)用instance 的初始化方法咽斧。 如果class 實現(xiàn)了Lifecycle 接口的話
    initExtension(instance);
    return instance;
  } catch (Throwable t) {
    throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                                    type + ") couldn't be instantiated: " + t.getMessage(), t);
  }
}

此處我們可以看出獲取擴展點實例的大致流程如下:

加載接口類型的所有實現(xiàn)類Class信息并緩存 ----> 根據(jù)name去獲取指定擴展實現(xiàn)類Class ----> 通過反射機制對實現(xiàn)類Class進行實例化并緩存 ----> 基于dubbo 的IOC機制對實例對象進行依賴注入----> 判斷是否有包裝類(wrapperClasses)堪置,決定是否要生成代理對象 ----> 最后對對象進行初始化之后返回該實例對象

  • 第一步實現(xiàn)類的加載 getExtensionClasses()
private Map<String, Class<?>> getExtensionClasses() {
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                // 加載所有class
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

private Map<String, Class<?>> loadExtensionClasses() {
            // 通過@SPI("DefaultExtensionName") 緩存默認(rèn)的extension name
        cacheDefaultExtensionName();
  
                //根據(jù)type.name  查找各個配置文件路徑下的實現(xiàn)類拓展配置
        Map<String, Class<?>> extensionClasses = new HashMap<>();
        // internal extension load from ExtensionLoader's ClassLoader first
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName(), true);
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"), true);

        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
}

private void loadDirectory(Map<String, Class<?>> extensionClasses, 
                           String dir, 
                           String type, 
                           boolean extensionLoaderClassLoaderFirst) {
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls = null;
          
            // 獲取ClassLoader
            ClassLoader classLoader = findClassLoader();
            
            // try to load from ExtensionLoader's ClassLoader first
            // 如果需要先嘗試使用ExtensionLoader 查找文件資源
            if (extensionLoaderClassLoaderFirst) {
                ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
                if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
                    urls = extensionLoaderClassLoader.getResources(fileName);
                }
            }
          
            // 使用默認(rèn)加載器獲取資源文件
            if(urls == null || !urls.hasMoreElements()) {
                if (classLoader != null) {
                    urls = classLoader.getResources(fileName);
                } else {
                    urls = ClassLoader.getSystemResources(fileName);
                }
            }
                        
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    // 讀取類配置, 加載類信息
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
}

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
  
  //.... 此處省略文件解析部分代碼张惹,不在主邏輯范疇舀锨,有興趣的同學(xué)可以自行深入

  // 遍歷加載class實現(xiàn)類, 通過Class.forName(line, true, classLoader) 實現(xiàn)類加載
  loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
  //.... 此處省略異常處理
}

//解析類信息
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }
            // 緩存標(biāo)注了@Adaptive的擴展實現(xiàn)類宛逗, 具體作用后面自適應(yīng)問題詳細(xì)說明
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz);
        // 判斷是否為包裝類坎匿, 緩存包裝類
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            clazz.getConstructor();
            // 如果沒有指定name 生成name
            if (StringUtils.isEmpty(name)) {
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }
                        // 分割name, 因為可以多個name 指向同一個實現(xiàn)類。例: aa, bb = org.apache.dubbo.extension.C
            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                // 緩存@Activate標(biāo)注的類信息
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    // 遍歷緩存 name和class 信息
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n);
                }
            }
        }
}

到此基本了解了拓展類的加載過程碑诉, 基本邏輯也非常簡單彪腔。 獲取到classLoader并調(diào)用Class.forname("className")將擴展類信息加載進JVM中, 之后就是從功能擴展的維度进栽,對class進行不同緯度的緩存德挣, 方便后面在某項功能點中快速獲取類信息。

Dubbo IOC實現(xiàn)

  • 基于IOC的依賴注入 injectExtension(instance)
private T injectExtension(T instance) {

    if (objectFactory == null) {
        return instance;
    }

    try {
        for (Method method : instance.getClass().getMethods()) {
            if (!isSetter(method)) {
                continue;
            }
            /**
             * Check {@link DisableInject} to see if we need auto injection for this property
             */
            if (method.getAnnotation(DisableInject.class) != null) {
                continue;
            }
            Class<?> pt = method.getParameterTypes()[0];
            if (ReflectUtils.isPrimitives(pt)) {
                continue;
            }

            try {
                String property = getSetterProperty(method);
                Object object = objectFactory.getExtension(pt, property);
                if (object != null) {
                    method.invoke(instance, object);
                }
            } catch (Exception e) {
                logger.error("Failed to inject via method " + method.getName()
                        + " of interface " + type.getName() + ": " + e.getMessage(), e);
            }

        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

從上面的邏輯很簡單的看出大致邏輯就是先獲取所有的setter方法快毛,獲取setter方法的參數(shù)的類型和名稱格嗅, 最后通過我們前面看到過的objectFactory獲取到依賴對象, 然后對其進行依賴注入唠帝。 那這里我們就需要深扒一下這個objectFactory 到底是個神馬屯掖。前面我在objectfactory的注釋說明了默認(rèn)為AdaptiveExtensionFactory類, 我們先來看下他的源碼

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
        // 保存了ExtensionFactory的實現(xiàn)類襟衰。 
    private final List<ExtensionFactory> factories;
        
    public AdaptiveExtensionFactory() {
        // 初始化將 其他ExtensionFactory拓展類加載緩存
        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;
    }
}

很明顯該類是一個策略類贴铜, 啟動時候會講其他的ExtensionFactory實現(xiàn)類加載進來, 然后再獲取Extension實現(xiàn)的時候直接遍歷各個容器來進行查找瀑晒。那我們來看下ExtensionFactory默認(rèn)實現(xiàn)的子類绍坝。

// SPI 默認(rèn)的IOC實現(xiàn),
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;
    }
}

// Spring 實現(xiàn)
public class SpringExtensionFactory implements ExtensionFactory {
    private static final Logger logger = LoggerFactory.getLogger(SpringExtensionFactory.class);

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

    public static void addApplicationContext(ApplicationContext context) {
        CONTEXTS.add(context);
        if (context instanceof ConfigurableApplicationContext) {
            ((ConfigurableApplicationContext) context).registerShutdownHook();
        }
    }

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

    public static Set<ApplicationContext> getContexts() {
        return CONTEXTS;
    }

    // currently for test purpose
    public static void clearContexts() {
        CONTEXTS.clear();
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getExtension(Class<T> type, String name) {

        //SPI should be get from SpiExtensionFactory
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            return null;
        }

        for (ApplicationContext context : CONTEXTS) {
            T bean = BeanFactoryUtils.getOptionalBean(context, name, type);
            if (bean != null) {
                return bean;
            }
        }

        logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());

        return null;
    }
}

以上ExtensionFactory主要分為兩類

  • SpiExtensionFactory是Dubbo 的IOC默認(rèn)容器苔悦,對象是從前面加載緩存中獲取的SPI的擴展對象轩褐。 這是Dubbo 的默認(rèn)機制

  • SpringExtensionFactory是使用Spring的IOC容器。我們可以將Spring容器中的Bean注入到Extension實例中玖详,通過該容器我們可以更好的繼承Spring框架甚至其他組件把介。

Dubbo Aop實現(xiàn)

可能用過一些容器框架的同學(xué)都知道AOP的基本原理。 甚至有的同學(xué)可能首先想到的就是動態(tài)代理模式蟋座,這并沒有錯拗踢。 但是方法不是唯一的, Dubbo中獲取的代理對象是通過裝飾器模式實現(xiàn)的向臀,接下來我們繼續(xù)深入看下源碼秒拔。

通過前面創(chuàng)建Extension我們可以知道, 是否需要生成對實力對象進行封裝的依據(jù)是cachedWrapperClasses是否為空飒硅。同時通過類信息加載過程可以知道,在加載類的時候會對cachedWrapperClasses進行緩存作谚, 那我們這邊舉例一個wrapper類三娩,然后再次運行。

  • CarWrapper
public class CarWrapper implements Car{

    private Car car;

    public CarWrapper(Car car){
        this.car = car;
    }

    @Override
    public void test(CarBrand brand) {
        System.out.println("包裝類前置執(zhí)行");
        car.test(brand);
        System.out.println("包裝類后置執(zhí)行");
    }
}
  • 配置信息

bwm=com.wgt.dubbo.samples.spi.demo1.car.BWMCar
audi=com.wgt.dubbo.samples.spi.demo1.car.AudiCar

# 代理類配置
com.wgt.dubbo.samples.spi.demo1.car.CarWrapper
  • 控制臺輸出
包裝類前置執(zhí)行
hi妹懒, 我是寶馬車
包裝類后置執(zhí)行

從輸出結(jié)果來看雀监, 很明顯在我根據(jù)(name="bwm"), 去獲取Extension實例的時候,實際獲取到的是CarWrapper類会前, 只不過BWMCar是作為一個代理對象傳入到CarWrapper中好乐。

  • 源碼參考
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
    for (Class<?> wrapperClass : wrapperClasses) {
        // 獲取CarWrapper類的有參構(gòu)造起, 并將instance作為構(gòu)造參數(shù)返回新的包裝類瓦宜。
        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
    }
}

當(dāng)前方式可以獲取到包裝后的實例對象蔚万, 但是此種方法還是不夠靈活。 Dubbo 中還有一種自適應(yīng)的機制临庇, 可以根據(jù)請求參數(shù)動態(tài)的獲取拓展類進行調(diào)用反璃, 通過策略+裝飾器的模式實現(xiàn)更加完善的代理

Dubbo 自適應(yīng)機制

第一次聽到自適應(yīng)機制難免有些陌生, 擴展的自適應(yīng)實例其實就是一個Extension的代理假夺,它實現(xiàn)了擴展點接口淮蜈。在調(diào)用擴展點的接口方法時,會根據(jù)實際的參數(shù)來決定要使用哪個擴展已卷。

  • @Adaptive

    @Adaptive注解是作用在做自適應(yīng)擴展點的注解類梧田, 可以作用在類上和方法上

    • 當(dāng)@Adaptive標(biāo)注在類上時,在調(diào)用getAdaptiveExtension方法時侧蘸,直接返回該類裁眯,表示代理類由手工實現(xiàn),并不需要Dubbo自動生成實現(xiàn)類闺魏。 可以參考 AdaptiveCompiler 和 AdaptiveExtensionFactory 實現(xiàn)
    • 當(dāng)@Adaptive標(biāo)注在接口的方法上時未状, 表明調(diào)用該方法可以通過URL參數(shù)動態(tài)調(diào)用擴展實現(xiàn)類的對應(yīng)方法。
    • 被@Adaptive修飾得方法得參數(shù) 必須滿足參數(shù)中有一個是URL類型析桥,或者有至少一個參數(shù)有一個公共的返回URL的get方法
    • 在調(diào)用動態(tài)生成代理類的非@Adaptive方法是司草, 默認(rèn)會拋出UnsupportedOperationException異常
  • 代碼示例

    說明: @Adaptive標(biāo)注在類上的邏輯相當(dāng)簡單。 這里就不贅述了泡仗。 直接以標(biāo)注在方法上的例子為例

    @SPI
    public interface Car {
          
          //動態(tài)生成代理類接口
        @Adaptive
        void test(CarBrand brand);
    }
    
    //自適應(yīng)節(jié)點埋虹, 獲取org.apache.dubbo.common.URL參數(shù)的接口
    public interface CustomerAdaptiveNode {
        URL getUrl();
    }
    
    //車品牌
    public interface CarBrand extends CustomerAdaptiveNode {
    
        CarBrand BWM = getCarBrand("bwm");
        CarBrand AUDI = getCarBrand("audi");
        CarBrand BENZ = getCarBrand("benz");
    
        static CarBrand getCarBrand(String brandName){
            return new CarBrand() {
                @Override
                public URL getUrl() {
                    URL url = new URL(null, null, 0);
                    url = url.addParameter("car", brandName);
                    return url;
                }
            };
        }
    }
    
    //寶馬車
    public class BWMCar implements Car {
    
        @Override
        public void test(CarBrand brand) {
            System.out.println("hi, 我是寶馬車");
        }
    }
    //..... 此處省略 奔馳車和奧迪車源碼
    
    
    //Main方法
    public static void main(String[] args) {
            ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
    
            //Car car = extensionLoader.getExtension("bwm");
                  // 獲取自適應(yīng)代理實例
            Car car = extensionLoader.getAdaptiveExtension();
                  //通過車品牌調(diào)用接口
            car.test(CarBrand.BENZ);
            car.test(CarBrand.BWM);
            car.test(CarBrand.AUDI);
    }
    
  • 控制臺輸出
hi, 我是奔馳車
hi娩怎,我是寶馬車
hi, 我是奧迪車

可以看到搔课,我們在調(diào)用同一個實例的同一個方法, 我們根據(jù)不同的CarBrand 參數(shù)實現(xiàn)了自適應(yīng)的去調(diào)用對應(yīng)擴展實例的方法截亦。

  • 源碼參考

    public T getAdaptiveExtension() {
          // 一如既往的緩存套路
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError != null) {
                throw new IllegalStateException("Failed to create adaptive instance: " +
                        createAdaptiveInstanceError.toString(),
                        createAdaptiveInstanceError);
            }
                  
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                          // 創(chuàng)建自適應(yīng)實例
                        instance = createAdaptiveExtension();
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        createAdaptiveInstanceError = t;
                        throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                    }
                }
            }
        }
        return (T) instance;
    }
    
    private T createAdaptiveExtension() {
            try {
                  // 獲取自適應(yīng)擴展類Class爬泥, 調(diào)用newInstance方法進行實例化,然后進行注入
                return injectExtension((T) getAdaptiveExtensionClass().newInstance());
            } catch (Exception e) {
                throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
            }
    }
    
    private Class<?> getAdaptiveExtensionClass() {
                  // 眼熟的初始加載加載擴展累信息
            getExtensionClasses();
                  // 判斷是否有標(biāo)注在類上的@Adaptive 擴展類
            if (cachedAdaptiveClass != null) {
                return cachedAdaptiveClass;
            }
                  //動態(tài)生成自適應(yīng)擴展累
            return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
    
    private Class<?> createAdaptiveExtensionClass() {
                  // 自適應(yīng)擴展累代碼生成器 生成源代碼崩瓤,
                  // 此處不在深入生成代碼原理袍啡, 有興趣的小伙伴自行研究, 下面會貼出生成好的代碼
            String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
                  //獲取classLoader
            ClassLoader classLoader = findClassLoader();
                  // 獲取編譯器
            org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
                  // 將源代碼進行編譯生成Class 對象
            return compiler.compile(code, classLoader);
    }
    
  • 自適應(yīng)擴展累源碼

    package com.wgt.dubbo.samples.spi.demo1.car;
    
    import org.apache.dubbo.common.extension.ExtensionLoader;
    
    public class Car$Adaptive implements com.wgt.dubbo.samples.spi.demo1.car.Car {
        public void test(com.wgt.dubbo.samples.spi.demo1.brand.CarBrand arg0) {
          if (arg0 == null)
              throw new IllegalArgumentException("com.wgt.dubbo.samples.spi.demo1.brand.CarBrand argument == null");
            
          if (arg0.getUrl() == null)
              throw new IllegalArgumentException("com.wgt.dubbo.samples.spi.demo1.brand.CarBrand argument getUrl() == null");
            
          org.apache.dubbo.common.URL url = arg0.getUrl();
          String extName = url.getParameter("car", "bwm");
          if (extName == null)
              throw new IllegalStateException("Failed to get extension (com.wgt.dubbo.samples.spi.demo1.car.Car) name from url (" + url.toString() + ") use keys([car])");
          
          com.wgt.dubbo.samples.spi.demo1.car.Car extension = (com.wgt.dubbo.samples.spi.demo1.car.Car)       ExtensionLoader.getExtensionLoader(com.wgt.dubbo.samples.spi.demo1.car.Car.class).getExtension(extName);
          extension.test(arg0);
        }
    }
    

    從以上的源代碼可以看出却桶, 所謂的根據(jù)參數(shù)自適應(yīng)境输,其實就是通過URL對象中的parameter 參數(shù)去獲取對應(yīng)的擴展實現(xiàn)蔗牡。 獲取的擴展的方式同樣是通過策略模式進行實現(xiàn)。 不過此種方式可以確保在運行時才能確定具體執(zhí)行的擴展累嗅剖,對于SPI機制的靈活性來說非常的有意義的辩越。

Dubbo @Activate

? 相較于前面的內(nèi)容,都是根據(jù)一些特定參數(shù)獲取具體的擴展類信粮, 但其實除此之外黔攒, 我們還會有同時用到多個擴展類,最常見的就是Dubbo 的Filter 機制蒋院,當(dāng)我們需要在執(zhí)行rpc調(diào)用的前后做一個操作時亏钩, 我們就可以通過實現(xiàn)Filter接口,并將其配置到org.apache.dubbo.rpc.Filter下欺旧。 但是我們在一次rpc調(diào)用時可能會有多個filter姑丑, 那么如何決定每個filter 的作用域,以及執(zhí)行順序呢辞友?

此時@Activate的作用的出現(xiàn)了栅哀,我們可以通過group, 和value 兩者來決定是否此次的調(diào)用啟用對應(yīng)攔截器称龙,并通過order 排序決定執(zhí)行順序留拾。

  • 源碼參考
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
    /**
     *  根據(jù)分組 
     *  例如 
     *      CommonConstants.PROVIDER
     *      CommonConstants.CONSUMER
     * @return
     */
    String[] group() default {};
        
    /**
     *  根據(jù)org.apache.dubbo.common.URL 中的parameters 參數(shù)決定是否啟用
     * @return
     */
    String[] value() default {};

    @Deprecated
    String[] before() default {};

    @Deprecated
    String[] after() default {};
    
    /**
     * Filter 順序
     * @return
     */
    int order() default 0;
}
  • 攔截器獲取源碼邏輯
  public List<T> getActivateExtension(URL url, String[] values, String group) {
          List<T> activateExtensions = new ArrayList<>();
          List<String> names = values == null ? new ArrayList<>(0) : asList(values);
          if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
              getExtensionClasses();
              for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
                  String name = entry.getKey();
                  Object activate = entry.getValue();
  
                  String[] activateGroup, activateValue;
  
                  if (activate instanceof Activate) {
                      activateGroup = ((Activate) activate).group();
                      activateValue = ((Activate) activate).value();
                  } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
                      activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
                      activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
                  } else {
                      continue;
                  }
                  if (isMatchGroup(group, activateGroup)
                          && !names.contains(name)
                          && !names.contains(REMOVE_VALUE_PREFIX + name)
                          && isActive(activateValue, url)) {
                      activateExtensions.add(getExtension(name));
                  }
              }
              activateExtensions.sort(ActivateComparator.COMPARATOR);
          }
          List<T> loadedExtensions = new ArrayList<>();
          for (int i = 0; i < names.size(); i++) {
              String name = names.get(i);
              if (!name.startsWith(REMOVE_VALUE_PREFIX)
                      && !names.contains(REMOVE_VALUE_PREFIX + name)) {
                  if (DEFAULT_KEY.equals(name)) {
                      if (!loadedExtensions.isEmpty()) {
                          activateExtensions.addAll(0, loadedExtensions);
                          loadedExtensions.clear();
                      }
                  } else {
                      loadedExtensions.add(getExtension(name));
                  }
              }
          }
          if (!loadedExtensions.isEmpty()) {
              activateExtensions.addAll(loadedExtensions);
          }
          return activateExtensions;
      }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鲫尊,隨后出現(xiàn)的幾起案子痴柔,更是在濱河造成了極大的恐慌,老刑警劉巖疫向,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咳蔚,死亡現(xiàn)場離奇詭異,居然都是意外死亡搔驼,警方通過查閱死者的電腦和手機谈火,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舌涨,“玉大人糯耍,你說我怎么就攤上這事∧壹危” “怎么了温技?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長扭粱。 經(jīng)常有香客問我舵鳞,道長,這世上最難降的妖魔是什么焊刹? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上虐块,老公的妹妹穿的比我還像新娘俩滥。我一直安慰自己,他們只是感情好贺奠,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布霜旧。 她就那樣靜靜地躺著,像睡著了一般儡率。 火紅的嫁衣襯著肌膚如雪挂据。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天儿普,我揣著相機與錄音崎逃,去河邊找鬼。 笑死眉孩,一個胖子當(dāng)著我的面吹牛个绍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播浪汪,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼巴柿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了死遭?” 一聲冷哼從身側(cè)響起广恢,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎呀潭,沒想到半個月后钉迷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡蜗侈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年篷牌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片踏幻。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡枷颊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出该面,到底是詐尸還是另有隱情夭苗,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布隔缀,位于F島的核電站题造,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏猾瘸。R本人自食惡果不足惜界赔,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一丢习、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧淮悼,春花似錦咐低、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至羹令,卻和暖如春鲤屡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背福侈。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工酒来, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人癌刽。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓役首,卻偏偏與公主長得像,于是被迫代替她去往敵國和親显拜。 傳聞我的和親對象是個殘疾皇子衡奥,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348