Dubbo-SPI機制

前言

SPI全稱為Service Provider Interface,是Java提供的一種服務發現機制。SPI的本質是將介面實現類的全限定名配置在文件中,並由服務載入器讀取配置文件,載入實現類。這樣可以在運行時,動態為介面替換實現類。 正因此特性,我們可以很容易的通過SPI機製為我們的程式提供拓展功能。今天文章就從Java SPI到Dubbo的SPI,讓我們看看Dubbo做了擴展。

Java SPI

實現

  1. 定義一個介面和介面的實現;
/**
 * 動物
 *
 * @author wangtongzhou
 * @since 2022-06-12 11:26
 */
public interface Animal {

    String getName();
}


/**
 * 貓
 *
 * @author wangtongzhou
 * @since 2022-06-12 11:37
 */
public class Cat implements Animal {
    @Override
    public String getName() {
        System.out.println("我是一隻貓");
        return "貓";
    }
}
  1. 在resources目錄下創建META-INF/services目錄;
  2. 在對應的目錄下創建介面全路徑的文件;
  3. 添加文件內容為實現介面的全路徑;
  4. 使用ServiceLoader載入類相關內容;
/**
 * spi
 *
 * @author wangtongzhou
 * @since 2022-06-12 11:47
 */
public class Test {

    public static void main(String[] args) {
        ServiceLoader<Animal> animals=ServiceLoader.load(Animal.class);
        animals.forEach(Animal::getName);
    }
}

使用場景

  1. 資料庫驅動載入介面實現類的載入,JDBC載入不同類型資料庫的驅動;
  2. 日誌門面介面實現類載入,SLF4J載入不同提供商的日誌實現類
  3. SpringBoot中的自動配置原理,即SpringFactoriesLoader類讀取配置文件 spring.factories 中的配置文件的這種方式是一種 SPI 的思想;
  4. Dubbo中也大量使用SPI的方式實現框架的擴展,不過它對Java提供的原生SPI做了封裝;

總結

優點

使用Java SPI機制的優勢是實現解耦,使得第三方服務模組的裝配控制的邏輯與調用者的業務程式碼分離,而不是耦合在一起。應用程式可以根據實際業務情況啟用框架擴展或替換框架組件。

缺點
  1. JDK 標準的 SPI 會一次性載入實例化擴展點的所有實現,也就是介面的實現類全部載入並實例化一遍。如果你並不想用某些實現類,它也被載入並實例化了,這就造成了浪費;
  2. 獲取某個實現類的方式不夠靈活,只能通過Iterator形式獲取,不能根據某個參數來獲取對應的實現類;

Dubbo SPI

為什麼學習Dubbo SPI

Dubbo的SPI層所有的介面都可以基於Dubbo框架做訂製性的二次開發,擴展功能。比如負載均衡、協議、註冊中心等等都可以擴展,這個是Dubbo強大和靈活的所在。

Dubbo SPI優勢

  1. 按需載入,Dubbo SPI配置文件採用KV格式存儲,key 被稱為擴展名,當我們在為一個介面查找具體實現類時,可以指定擴展名來選擇相應的擴展實現,只實例化這一個擴展實現即可,無須實例化 SPI 配置文件中的其他擴展實現類,避免資源浪費,此外通過 KV 格式的 SPI 配置文件,當我們使用的一個擴展實現類所在的 jar 包沒有引入到項目中時,Dubbo SPI 在拋出異常的時候,會攜帶該擴展名資訊,而不是簡單地提示擴展實現類無法載入。這些更加準確的異常資訊降低了排查問題的難度,提高了排查問題的效率。;
  2. 增加擴展類的 IOC 能力,Dubbo 的擴展能力並不僅僅只是發現擴展服務實現類,而是在此基礎上更進一步,如果該擴展類的屬性依賴其他對象,則 Dubbo 會自動的完成該依賴對象的注入功能;
  3. 增加擴展類的 AOP 能力,Dubbo 擴展能力會自動的發現擴展類的包裝類,完成包裝類的構造,增強擴展類的功能;

使用

  1. 自定義SPI註解介面以及實現對應介面;
/**
 * 動物
 *
 * @author wangtongzhou
 * @since 2022-06-12 11:26
 */
@SPI
public interface Animal {

    String getName();
}


/**
 * 貓
 *
 * @author wangtongzhou
 * @since 2022-06-12 11:37
 */
public class Cat implements Animal {
    @Override
    public String getName() {
        System.out.println("我是一隻貓");
        return "貓";
    }
}

  1. 需要在 resource 目錄下配置 META-INF/dubbo 或者 META-INF/dubbo/internal 或者 META-INF/services,並基於 SPI 介面去創建一個文件;
  2. 文件名稱和介面名稱保持一致,文件內容與Java SPI有差異,內容是KEY 對應 Value;
  3. 使用ExtensionLoader調用;
/**
 * 測試
 *
 * @author wangtongzhou
 * @since 2022-06-12 17:32
 */
public class Test {
    public static void main(String[] args) {
        Animal animal= ExtensionLoader.getExtensionLoader(Animal.class).getExtension("cat");
        System.out.println(animal.getName());
    }
}

源碼解析

通過上面的使用案例來看,Dubbo的某個介面被@SPI修飾的時候,就表示該介面是擴展介面,是通過ExtensionLoader.getExtensionLoader完成ExtensionLoader的初始化,然後再通過ExtensionLoader 的 getExtension 方法獲取拓展類對象。接下來我們來整體看下@SPI載入過程。

Animal animal= ExtensionLoader.getExtensionLoader(Animal.class).getExtension("cat");

getExtensionLoader

首先是getExtensionLoader的方法,完成ExtensionLoader創建,該方法內部完成一些關於type的校驗,然後根據EXTENSION_LOADERS快取獲取ExtensionLoader的實例,EXTENSION_LOADERS集合快取了全部 ExtensionLoader 實例,其中的 Key 為擴展介面,Value 為載入其擴展實現的 ExtensionLoader 實例,如果不存在快取的時候,則主動創建一個。

    @SuppressWarnings("unchecked")
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        //空判斷
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }
        //必須是介面
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
        }
        //必須@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) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

接下來我們看一下ExtensionLoader構造函數的初始化,關於ExtensionLoader是一個私有的構造函數,如果當前的 type=ExtensionFactory,那麼objectFactory=null, 否則會創建一個自適應擴展點給objectFacotry,這裡介紹一下objectFactory,objectFactory有是三種實現,如下圖:

其中AdaptiveExtensionFactory 不實現任何具體的功能,而是用來適配 ExtensionFactory 的 SpiExtensionFactory 和 SpringExtensionFactory 這兩種實現。前者用於創建自適應的拓展,後則是到 Spring 的 IOC 容器中獲取所需拓展。AdaptiveExtensionFactory 會根據運行時的一些狀態來選擇具體調用 ExtensionFactory 的哪個實現。
關於objectFactory的實現為AdaptiveExtensionFactory,AdaptiveExtensionFactory 內部維護了一個ExtensionFactory 列表,用於存儲SpiExtensionFactory 和 SpringExtensionFactory的實現。

    private ExtensionLoader(Class<?> type) {
        this.type = type;
        //type為普通介面時,objectFactory實現為AdaptiveExtensionFactory
        //實現dubbo spi ioc功能
        objectFactory =
                (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

關於這三者實現如下,不同點在於AdaptiveExtensionFactory類上面增加@Adaptive註解,至於為什麼實現是AdaptiveExtensionFactory,應該有兩處決定的一個是LoadingStrategy對象,該對象進行排序,保障了DubboExternalLoadingStrategy目錄下的文件優先載入,另外一個就是ExtensionFactory的配置文件,AdaptiveExtensionFactory的配置在前。

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    //存儲其他類型的 ExtensionFactory對象
    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        //獲取拓展點介面ExtensionFactory對應的ExtensionLoader
        //每個拓展點介面都有一個對應的ExtensionLoader對象
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        //getSupportedExtensions得到所有非@Adaptive標註
        //也非ExtensionFactory包裝類的實現類的配置名
        //getExtension通過配置名得到對象
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        //遍歷所有其它ExtensionFactory實現類,調用它們的getExtension
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}

public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            //獲取對應type的ExtensionLoader實例
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                //獲取適配器實現
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}


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();
            // see //github.com/apache/dubbo/issues/7093
            DubboShutdownHook.getDubboShutdownHook().unregister();
        }
    }

    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) {

        //type必須是@SPI
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            return null;
        }
        //從Spring容器中獲取Bean
        for (ApplicationContext context : CONTEXTS) {
            T bean = 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;
    }

}

getAdaptiveExtension

先來看下整體的時序圖,重點看createAdaptiveExtension,

createAdaptiveExtension方法內部有個核心的方法getAdaptiveExtensionClass,關於這個方法我們可以看到分成兩步,一個種是通過getExtensionClasses載入,另外一種通過編譯器生成程式碼;

    @SuppressWarnings("unchecked")
    private T createAdaptiveExtension() {
        try {
            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();
        //如果可以適配
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        //如果沒有適配擴展類就創建
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
getExtensionClasses

首先來看下getExtensionClasses方法,該方法獲取所有的擴展類,主要是通過調用loadExtensionClasses來實現的,根據擴展名從 cachedClasses 快取中獲取擴展實現類。如果 cachedClasses 未初始化,則會掃描 SPI 目錄獲取查找相應的 SPI 配置文件,然後載入其中的擴展實現類,最後將擴展名和擴展實現類的映射關係記錄到 cachedClasses 快取中。
在Dubbo中按照SPI配置文件的用途,將其分成了三類目錄。
● META-INF/services/ 目錄:該目錄下的 SPI 配置文件用來兼容 JDK SPI ;
● META-INF/dubbo/ 目錄:該目錄用於存放用戶自定義 SPI 配置文件;
● META-INF/dubbo/internal/ 目錄:該目錄用於存放 Dubbo 內部使用的 SPI 配置文件;

    private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    //載入對應類資訊
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

loadExtensionClasses從配置文件中載入擴展類資訊,cacheDefaultExtensionName邏輯是在把SPI註解中的默認值放到快取中去,loadDirectory從一個配置文件中擴展類資訊;這裡介紹一下Loading這個對象,該對象有是三個默認實現,採用的是Java SPI方式載入,同時都繼承了Prioritized優先順序介面,整體的繼承圖如下:

此處三個對象也對應了載入的目錄的地址:

    private Map<String, Class<?>> loadExtensionClasses() {
        //快取@SPI組件資訊
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();


        //載入 META-INF/dubbo/internal META-INF/dubbo META-INF/services/
        //對應目錄下的配置資訊
        for (LoadingStrategy strategy : strategies) {
            loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(),
                    strategy.overridden(), strategy.excludedPackages());
            loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache""com.alibaba"),
                    strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
        }

        return extensionClasses;
    }
    private void cacheDefaultExtensionName() {
        //獲取註解SPI介面
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation == null) {
            return;
        }
        //如果存在默認值 將值賦值給cachedDefaultName
        String value = defaultAnnotation.value();
        if ((value = value.trim()).length() > 0) {
            //@SPI value 只能是一個 此處相當於在進行參數校驗
            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];
            }
        }
    }
loadResource

loadResource載入配置文件中的內容,主要的邏輯就是跳過「#」注釋的內容,根據配置文件中的key=value的形式去分割,然後去載入value通過反射載入類,然後調用 loadClass 方法進行操作快取。

    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
                              java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
        try {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
                String line;
                String clazz = 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和實現類
                                name = line.substring(0, i).trim();
                                clazz = line.substring(i + 1).trim();
                            } else {
                                clazz = line;
                            }
                            if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages)) {
                                //載入擴展類
                                loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);
                            }
                        } catch (Throwable t) {
                            IllegalStateException e = new IllegalStateException(
                                    "Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL +
                                            ", cause: " + t.getMessage(), t);
                            exceptions.put(line, e);
                        }
                    }
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }

loadClass 方法操作了不同的快取,比如 cachedAdaptiveClass、cachedWrapperClasses 和
cachedNames 等等。

    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
                           boolean overridden) 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.");
        }
        //判斷類是否載入Adaptive註解
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz, overridden);
            //是否是擴展類,是的話就加入 cachedWrapperClasses 屬性
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            //檢測是否有默認構造起
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                //未配置擴展名,自動生成,主要用於兼容java SPI的配置。
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException(
                            "No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }
            // 獲得擴展名,可以是數組,有多個拓擴展名。
            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                //如果是自動激活的實現類,則加入到快取
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    //存儲Class到名字的映射關係
                    cacheName(clazz, n);
                    //存儲名字到Class的映射關係
                    saveInExtensionClass(extensionClasses, clazz, n, overridden);
                }
            }
        }
    }

getExtension

接下來我們看下getExtension方法,該方法入參是配置文件的key,name是cat,如果name為true,返回getDefaultExtension(), 對於Holder只是用來存放對象,cachedInstances用來存放對象的快取,快取了 ExtensionLoader 載入的擴展名與擴展實現對象之間的映射關係;當holder中沒有存在對象的時候,採用雙重鎖檢查機制,通過createExtension創建擴展類,並放入Holder對象中;

    @SuppressWarnings("unchecked")
    public T getExtension(String name) {
        return getExtension(name, true);
    }

    public T getExtension(String name, boolean wrap) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            //獲取默認擴展類實現
            return getDefaultExtension();
        }
        //獲取目標對象
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    //創建擴展類
                    instance = createExtension(name, wrap);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }
    
    private Holder<Object> getOrCreateHolder(String name) {
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            //如果name對應的Holder對象為null 就創建一個空Holder對象
            cachedInstances.putIfAbsent(name, new Holder<>());
            holder = cachedInstances.get(name);
        }
        return holder;
    }
createExtension

接下來我們看下createExtension,該方法完成了 SPI 配置文件的查找以及相應擴展實現類的實例化,同時還實現了自動裝配以及自動 Wrapper 包裝等功能。核心功能大致上可以分為5個步驟:

  1. 通過 getExtensionClasses 獲取所有擴展類,然後根據擴展名從 cachedClasses 快取中獲取擴展實現類。如果 cachedClasses 未初始化,則會掃描前面介紹的三個 SPI 目錄獲取查找相應的 SPI 配置文件,然後載入其中的擴展實現類,最後將擴展名和擴展實現類的映射關係記錄到 cachedClasses 快取中;
  2. 根據擴展實現類從 EXTENSION_INSTANCES 快取中查找相應的實例。如果沒有查詢到,通過反射創建擴展實現對象;
  3. 對擴展對象採用自動裝配擴展實現對象中的屬性,也就是我們說的IOC;
  4. 判斷擴展對象是否是包裝類,將擴展對象包裹進對應的 Wrapper 對象中;
  5. 如果擴展實現類實現了 Lifecycle 介面,在 initExtension() 方法中會調用 initialize() 方法進行初始化;
    private T createExtension(String name, boolean wrap) {
        //從配置文件中載入所有擴展類,形成配置項名稱與配置類的映射關係
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null || unacceptableExceptions.contains(name)) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                //採用反射創建對應實例
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //向對象中注入依賴的屬性 IOC自動裝配
            injectExtension(instance);


            if (wrap) {
                //創建Wrapper擴展對象
                List<Class<?>> wrapperClassesList = new ArrayList<>();
                if (cachedWrapperClasses != null) {
                    wrapperClassesList.addAll(cachedWrapperClasses);
                    wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                    Collections.reverse(wrapperClassesList);
                }

                if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                    for (Class<?> wrapperClass : wrapperClassesList) {
                        Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                        if (wrapper == null
                                || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                            //將當前instance作為參數創建wrapper實例,然後向wrapper實例中注入屬性值
                            //並將wrapper實例賦值給instance
                            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                        }
                    }
                }
            }
            //Lifecycle初始化
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

至此就完成類載入載入以及初始化的整個過程,此處主要理解Dubbo如何將類載入的整個過程,整個過程中使用策略模式、適配器模式應用都是值得我們學習的,其中createAdaptiveExtensionClass方法沒有進行介紹,這部分我們放到@Adaptive註解的介紹。

injectExtension(Dubbo IOC)

injectExtension就是Dubbo IOC的體現,主要是通過 setter 方法實現依賴注入。步驟就是獲取實例的所有方法,在遍歷的過程中看看是否名字有 setter 欄位。如果有就通過 ObjectFactory 獲取依賴對象,然後通過反射調用 setter 方法將依賴設置到目標對象當中。簡單來說,自動裝配屬性就是在載入一個擴展點的時候,將其依賴的擴展點一併載入,並進行裝配。

    private T injectExtension(T instance) {

        //如果為空直接返回
        if (objectFactory == null) {
            return instance;
        }

        try {
            //反射獲取所有方法
            for (Method method : instance.getClass().getMethods()) {
                //如果是不是set方法跳過
                if (!isSetter(method)) {
                    continue;
                }
                //如果有 @DisableInject 註解的化就不會進行注入
                if (method.getAnnotation(DisableInject.class) != null) {
                    continue;
                }
                Class<?> pt = method.getParameterTypes()[0];
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }

                try {
                    //根據setter方法的名稱確定屬性名稱 
                    String property = getSetterProperty(method);
                    //載入並實例化擴展實現
                    Object object = objectFactory.getExtension(pt, property);
                    if (object != null) {
                        //調用setter方法進行裝配
                        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;
    }

結束

從四月之後就比較少更新公眾號,原因是處理一些生活上的事,完成了今年生活上一個重要的目標,從該篇文章開始恢復每周一更,後續文章分享的規劃應該會從三個方面:

  1. Java框架方面的內容,Dubbo源碼以及Netty部分內容解讀,Netty後續會做一個小小的項目;
  2. 準備刷刷演算法相關內容,計劃採用Go語言來刷演算法;
  3. 雲原生技術持續關注與學習,分享istio相關內容;
    歡迎大家點點關注,點點贊!