成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

趁同事上廁所時間,看完了 Dubbo SPI 的源碼,瞬間覺得 JDK SPI不香了

開發 前端
SPI 全稱 Service Provider Interface ,是 Java 提供的一套用來被第三方實現或者擴展的 API,它可以用來啟用框架擴展和替換組件。

一、引言

兄弟們,上次的故障結果出來了

還好銷售團隊給力,沒有讓客戶幾千萬的單子丟掉,成功挽回了本次損失

不過內部處罰還是相對嚴重,年終獎懸了

這也告誡我們 要對生產保持敬畏之情!

恰巧最近領導看我在寫 Dubbo 源碼系列,看到我們的項目中用了 SPI 擴展

于是給我一個將功補過的機會,讓我好好的分析分析 Dubbo 的 SPI 的擴展機制,進行組內技術分享

作為一個常年分享 源碼系列 文章的選手,當然不會拒絕!

乾坤未定,你我皆是黑馬,沖!

二、SPI是什么

SPI 全稱 Service Provider Interface ,是 Java 提供的一套用來被第三方實現或者擴展的 API,它可以用來啟用框架擴展和替換組件。

Java SPI 實際上是 基于接口的編程+策略模式+配置文件 組合實現的動態加載機制。

Java SPI 就是提供這樣的一個機制:為某個接口尋找服務實現的機制。

將裝配的控制權移到程序之外,在模塊化設計中這個機制尤其重要。

所以 SPI 的核心思想就是解耦。

三、使用介紹

我們定義一個接口:City

@SPI
public interface City {
    String getCityName();
}

實現其兩個類:

  • BeijingCity
public class BeijingCity implements City{
    @Override
    public String getCityName() {
        return "北京";
    }
}
  • TianjinCity
public class TianjinCity implements City{
    @Override
    public String getCityName() {
        return "天津";
    }
}

重點來了:我們要在 resources 文件夾下面建立一個路徑:META-INF/dubbo

然后我們建立一個 txt 名為:com.dubbo.provider.SPI.Dubbo.City,如下:

我們在這個文件中寫上各實現類的路徑:

beijing=com.dubbo.provider.SPI.Dubbo.BeijingCity
tianjin=com.dubbo.provider.SPI.Dubbo.TianjinCity

有的朋友可能會問,這里為什么和 Java SPI 的實現不同?

這也正是 Dubbo 實現精準實例化的原因,我們后面也會聊到

測試方法:

public class DubboSPITest {
    public static void main(String[] args) {
        ExtensionLoader<City> loader = ExtensionLoader.getExtensionLoader(City.class);
        City tianjin = loader.getExtension("beijing");
        System.out.println(tianjin.getCityName());
    }
}

測試結果:

北京

從這里我們可以看出,Dubbo 可以通過 loader.getExtension("beijing") 精確的生成我們需要的實例

精確生成是如何實現的呢?我們繼續往下看

四、原理介紹

在源碼介紹之前,我們先說幾個原理細節,防止大家后面的源碼看迷糊

1、SPI注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {


    /**
     * default extension name
     */
    String value() default "";


    /**
     * scope of SPI, default value is application scope.
     */
    ExtensionScope scope() default ExtensionScope.APPLICATION;
}

在 SPI 注解中,存在兩個參數:value、scope

value

  • 作用:如果某個 SPI 擴展沒有指定實現類名稱,則會使用 @SPI 注解中指定的默認值

scope:指定 SPI 擴展實現類的作用域( Constants.SINGLETON)

  • Constants.FRAMEWORK(框架作用域):實現類在 Dubbo 框架中只會創建一個實例,并且在整個應用程序中共享。
  • Constants.APPLICATION(應用程序作用域):實現類在應用程序上下文中只會創建一個實例,并且在整個應用程序中共享。
  • Constants.MODULE(模塊作用域):實現類在模塊上下文中只會創建一個實例,并且在整個模塊中共享。
  • Constants.SELF(自定義作用域):實現類的作用范圍由用戶自行定義,可以是任何范圍。

當然,這里 Dubbo 默認的是 Constants.APPLICATION,我們也只需要關注這個即可。

五、源碼剖析

1、Loader的創建

我們 Dubbo 的 SPI 從ExtensionLoader.getExtensionLoader(City.class) 開始,看一看其實現方案

public <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    // 1、校驗
    checkDestroyed();


    // 2、是否有本地Loader緩存
    ExtensionLoader<T> loader = (ExtensionLoader<T>) extensionLoadersMap.get(type);


    // 3、是否有本地Scope緩存
    ExtensionScope scope = extensionScopeMap.get(type);
    
    // 4、如果當前的Scope為空
    // 4.1 獲取當前接口類的SPI注解
    // 4.2 獲取當前注解的scope
    // 4.3 放入scope緩存
    if (scope == null) {
        SPI annotation = type.getAnnotation(SPI.class);
        scope = annotation.scope();
        extensionScopeMap.put(type, scope);
    }


    // 5、如果加載器為空且當前是SELF,直接創建loader
    if (loader == null && scope == ExtensionScope.SELF) {
        loader = createExtensionLoader0(type);
    }


    // 6、如果當前加載器為空,去父類找加載器
    if (loader == null) {
        if (this.parent != null) {
            loader = this.parent.getExtensionLoader(type);
        }
    }


    // 7、如果父類也沒有實例化,那么實例化并放入緩存
    if (loader == null) {
        loader = createExtensionLoader(type);
    }
  
    // 8、返回加載器
    return loader;
}

從上面的源碼我們可以看到,獲取 ExtensionLoader 采用了 緩存 + 父類繼承 的模式

這種繼承機制設計得比較巧妙,可以避免重復加載類,提高系統性能。

2、獲取實例

Dubbo 通過 loader.getExtension("tianjin") 獲取對應的實例

public T getExtension(String name) {
    T extension = getExtension(name, true);
    return extension;
}


public T getExtension(String name, boolean wrap) {
    // 1、校驗
    checkDestroyed();
    
    // 2、參數為true,表明采用默認的實現類
    // 2.1 我們上面SPI中的value參數,若指定tianjin,則采用tianjin的實現類
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    
    String cacheKey = name;
    if (!wrap) {
        cacheKey += "_origin";
    }
    // 3、查看當前緩存中是否含有該實例
    // 3.1 如果當前的cacheKey沒有Holder的話,創建一個
    final Holder<Object> holder = getOrCreateHolder(cacheKey);
    
    // 4、如果實例為空,采用DCL機制創建實例
    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) {
    // 1、獲取當前name的Holder
    Holder<Object> holder = cachedInstances.get(name);
    // 2、沒有則創建并扔進緩存
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<>());
        holder = cachedInstances.get(name);
    }
    // 3、返回
    return holder;
}

Holder 類是一個簡單的容器類,用于保存某個對象的引用

在 Dubbo 的 ExtensionLoader 類中,Holder 類被用于實現對 SPI 擴展實現類的緩存

Holder 結構如下:

public class Holder<T> {
    private volatile T value;
    public void set(T value) {
        this.value = value;
    }
    public T get() {
        return value;
    }
}

我們創建實例一共有以下幾部分:

  • 解析文件配置得到對應的類
  • 通過實例化創建相關的類
  • 初始化之前前置操作
  • 依賴注入
  • 初始化之后后置操作
  • Wrapper 的包裝
  • 是否具有生命周期管理的能力

我們挨個的講解

2.1 解析文件配置

Class<?> clazz = getExtensionClasses().get(name);


private Map<String, Class<?>> getExtensionClasses() {
    // 1、從緩存中獲取類的信息
    Map<String, Class<?>> classes = cachedClasses.get();
    
    // 2、DCL創建(經典的單例設計模式)
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                // 3、加載類信息并放至緩存中
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}


private Map<String, Class<?>> loadExtensionClasses() throws InterruptedException {
    // 1、校驗
    checkDestroyed();
    
    // 2、是否有默認的類
    // 2.1 我們之前聊過的SPI注解的value機制
    cacheDefaultExtensionName();
    
    // 3、這里有三個文件解析器
    // 3.1 DubboInternalLoadingStrategy:解析META-INF/dubbo/internal/
    // 3.2 DubboLoadingStrategy:解析META-INF/dubbo/
    // 3.3 ServicesLoadingStrategy:解析META-INF/services/
    // 3.4 解析文件并放至緩存
    Map<String, Class<?>> extensionClasses = new HashMap<>();
    for (LoadingStrategy strategy : strategies) {
        loadDirectory(extensionClasses, strategy, type.getName());
    
        if (this.type == ExtensionInjector.class) {
            loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());
        }
    }
  
    // tianjin:"class com.msb.dubbo.provider.SPI.Dubbo.TianjinCity"
    // beijing:"class com.msb.dubbo.provider.SPI.Dubbo.BeijingCity"
    return extensionClasses;
}

2.2 實例化創建

// 1、從緩存中獲取
T instance = (T) extensionInstances.get(clazz);
if (instance == null) {
    // 2、緩存為空則創建并放至緩存
    extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
    instance = (T) extensionInstances.get(clazz);
}


// 1、獲取當前類的所有的構造方法
// 2、判斷是否有符合的構造方法,若沒有則報錯
// 3、有符合的構造犯法,返回即可
private Object createExtensionInstance(Class<?> type) throws ReflectiveOperationException {
    return instantiationStrategy.instantiate(type);
}

2.3 前置處理

類似 Spirng 的前置處理器,之前也說過,感興趣的可以看一下,整體思路區別不大

instance = postProcessBeforeInitialization(instance, name);


private T postProcessBeforeInitialization(T instance, String name) throws Exception {
    if (extensionPostProcessors != null) {
        for (ExtensionPostProcessor processor : extensionPostProcessors) {
            instance = (T) processor.postProcessBeforeInitialization(instance, name);
        }
    }
    return instance;
}

2.4 依賴注入

  • 首先,如果依賴注入器為 null,則直接返回傳入的實例。
  • 然后,遍歷傳入實例的所有方法,找到所有的 setter 方法。
  • 對于每個 setter 方法,如果標注了 @DisableInject 注解,則跳過該方法,不進行注入。
  • 如果 setter 方法的參數類型是基本類型,則跳過該方法,不進行注入。
  • 如果 setter 方法的參數類型不是基本類型,則嘗試從依賴注入器中獲取該類型對應的實例,并調用該 setter 方法進行注入。
  • 如果獲取實例失敗,則記錄錯誤日志。
  • 最后,返回注入后的實例。
injectExtension(instance);


private T injectExtension(T instance) {
    for (Method method : instance.getClass().getMethods()) {
        // 1、如果不是setter方法,直接跳過
        if (!isSetter(method)) {
            continue;
        }
        
        // 2、包含了DisableInject注解,直接跳過
        if (method.isAnnotationPresent(DisableInject.class)) {
                continue;
            }


            
            if (method.getDeclaringClass() == ScopeModelAware.class) {
                continue;
            }
          
          // 3、如果是基本數據類型,跳過
            if (instance instanceof ScopeModelAware || instance instanceof ExtensionAccessorAware) {
                if (ignoredInjectMethodsDesc.contains(ReflectUtils.getDesc(method))) {
                    continue;
                }
            }
            Class<?> pt = method.getParameterTypes()[0];
            if (ReflectUtils.isPrimitives(pt)) {
                continue;
            }


            try {
                // 4、依賴注入器中獲取該類型對應的實例,并調用該 setter 方法進行注入
                // 4.1 這里直接拿取的ListableBeanFactory->DefaultListableBeanFactory
                String property = getSetterProperty(method);
                Object object = injector.getInstance(pt, property);
                
                // 5、將當前的對象注入到實例里面
                if (object != null) {
                    method.invoke(instance, object);
                }
            }
    return instance;
}

2.5 后置操作

  • 類似 Spirng 的后置處理器,之前也說過,感興趣的可以看一下,整體思路區別不大
instance = postProcessAfterInitialization(instance, name);


private T postProcessAfterInitialization(T instance, String name) throws Exception {
    if (instance instanceof ExtensionAccessorAware) {
        ((ExtensionAccessorAware) instance).setExtensionAccessor(extensionDirector);
    }
    if (extensionPostProcessors != null) {
        for (ExtensionPostProcessor processor : extensionPostProcessors) {
            instance = (T) processor.postProcessAfterInitialization(instance, name);
        }
    }
    return instance;
}

2.6 Wrapper 的包裝

2.6.1 Wrapper緩存

在講該部分之前,我們先來看 cachedWrapperClasses 這個緩存的來歷:

在我們上面解析文件配置時,會進行 loadClass,這里不僅會解析正常的類,也會解析 Wrapper 類,方便后面的包裝

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,boolean overridden) {
    if (isWrapperClass(clazz)) {
        cacheWrapperClass(clazz);
    }
}

從這里我們可以看到,最關鍵的當屬判斷當前的 Class 是不是屬于 WrapperClass

protected boolean isWrapperClass(Class<?> clazz) {
    // 1、獲取構造方法
    Constructor<?>[] constructors = clazz.getConstructors();
    // 2、從構造方法中取出參數為 1 且類型等于當前接口的
    for (Constructor<?> constructor : constructors) {
        if (constructor.getParameterTypes().length == 1 && constructor.getParameterTypes()[0] == type) {
            return true;
        }
    }
    return false;
}

而具體的實現如下:

public class CityWrapper implements City{


    private City city;
    // 怎樣判斷擴展點還是aop切面呢?
    // 通過是否有這樣的一個構造方法來判斷
    public CityWrapper(City city) {
        this.city = city;
    }


    @Override
    public String getCityName() {
        return "文明城市" + city.getCityName();
    }
}


了解這個之后,我們再來看看 Dubbo 如何處理這些類似 AOP 的包裝

2.6.2 Wrapper實現
if (wrap) {
    List<Class<?>> wrapperClassesList = new ArrayList<>();
  // 1、判斷是否有Wrapper緩存
    // 1.1 將緩存放入當前
    // 1.2 排序 + 翻轉
    if (cachedWrapperClasses != null) {
        wrapperClassesList.addAll(cachedWrapperClasses);
        wrapperClassesList.sort(WrapperComparator.COMPARATOR);
        Collections.reverse(wrapperClassesList);
    }
  
    // 2、當前的wrapper緩存不為空
    if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
        // 循環包裝
        for (Class<?> wrapperClass : wrapperClassesList) {
            // 3、獲取Wrapper注解,是否需要包裝(正常都是包裝的)
            Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
            // 4、判斷下是否包裝條件
            boolean match = (wrapper == null) ||
                ((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) &&
                    !ArrayUtils.contains(wrapper.mismatches(), name));
            // 5、符合包裝
            // 5.1 將當前類封裝至wrapper中
            // 5.2 做一些后置處理
            if (match) {
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                instance = postProcessAfterInitialization(instance, name);
            }
        }
    }
}

通過這種方式,我們可以創建不同的 wrapper,實現 AOP 的作用

讀過 從源碼全面解析 dubbo 服務端服務調用的來龍去脈 和 從源碼全面解析 dubbo 消費端服務調用的來龍去脈 的文章,這時候應該理解最后的那些 過濾器 怎么實現的了

比如,我們現在有兩個 wrapper 類,分別是 CityWrapper 和 CityWrapper2,實現類是 TianjinCity

那么,我們最終 TianjinCity 返回的實例如下:

  • CityWrapper

TianjinCity

CityWrapper2

不得不說,這個包裝還是有點秀秀的

2.7 生命周期管理

  • 實現 Lifecycle 的接口
initExtension(instance);

六、流程圖

高清圖片私聊博主獲取

七、總結

魯迅先生曾說:獨行難,眾行易,和志同道合的人一起進步。彼此毫無保留的分享經驗,才是對抗互聯網寒冬的最佳選擇。

其實很多時候,并不是我們不夠努力,很可能就是自己努力的方向不對,如果有一個人能稍微指點你一下,你真的可能會少走幾年彎路。

責任編輯:武曉燕 來源: 今日頭條
相關推薦

2024-10-29 08:34:55

SPI機制接口

2018-07-06 15:30:14

DubboSPIJDK

2021-12-05 23:17:18

iOS蘋果系統

2025-05-08 03:25:00

DubboSPI機制

2025-03-04 09:02:25

JavaSPI機制

2021-09-10 08:31:19

DubboSPI框架

2020-12-14 11:35:22

SPI Java機制

2022-05-12 12:47:07

SPI主設備通信

2022-05-15 22:34:32

SPI 控制器SPI 子系統

2022-05-26 10:28:59

Ubuntu桌面

2022-11-02 21:45:54

SPIJava

2025-05-20 05:53:07

DubboSPI機制

2023-04-28 08:42:08

Linux內核SPI驅動

2020-02-18 07:22:48

微軟WindowsWindows 10

2020-08-18 08:04:16

DubboSPI框架

2020-10-21 09:19:27

Flutter開源項目

2011-11-30 14:35:19

JavaSPI

2021-12-03 10:46:49

ELKGraylog運維

2020-11-20 07:51:02

JavaSPI機制

2021-12-02 06:34:34

GraylogELK日志
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久久久久综合 | 成人三级电影 | 中文字幕免费视频 | 欧美久久视频 | 成人黄色在线观看 | a国产一区二区免费入口 | 国产蜜臀97一区二区三区 | 涩涩鲁亚洲精品一区二区 | 久久久久中文字幕 | 欧美激情一区二区 | 一级免费视频 | 在线一区视频 | 老头搡老女人毛片视频在线看 | 国产一区中文 | 国产小u女发育末成年 | 自拍视频国产 | 久久久久国产精品一区二区 | 欧美区日韩区 | 全部免费毛片在线播放网站 | 综合网中文字幕 | 麻豆久久久久 | 欧美色视频免费 | 国产一区二区三区四 | 亚洲视频在线看 | 91在线综合 | 精品中文字幕一区二区三区 | 欧美一级视频免费看 | 一级免费看片 | 国产精品亚洲一区二区三区在线 | 久久国产婷婷国产香蕉 | 91精品麻豆日日躁夜夜躁 | 狠狠躁18三区二区一区 | 国产成人aⅴ | 成人欧美一区二区三区色青冈 | 国产午夜精品一区二区三区四区 | 91久久精品日日躁夜夜躁欧美 | 国产激情 | h网站在线观看 | 在线观看免费国产 | 伊人一区 | 亚洲啪啪一区 |