2020久久超碰欧美精品最新亚洲欧美日韩久久精品,国产福利电影一区二区三区,亚洲欧美日韩一区在线观看,亚洲国产欧美日韩欧美特级,亚洲欧美日韩成人一区久久,欧美日韩精品一区二区三区不卡,国产欧美日韩va另类影音先锋,亚洲欧美日韩久久精品,亚洲欧美日韩国产成人精品影院,亚洲国产欧美日韩精品一区二区三区,欧美日韩国产成人高清视频,日韩久久精品国产免费观看频道,久久人人爽人人爽从片av高清,国产精品综合一区二区

首頁技術文章正文

Java培訓:dubbo源碼解析-SPI機制

更新時間:2022-09-15 來源:黑馬程序員 瀏覽量:

  架構體系

  框架介紹

  概述

  Dubbo是阿里巴巴公司開源的一個高性能優(yōu)秀的服務框架,使得應用可通過高性能的 RPC 實現服務的輸出和輸入功能,可以和 Spring框架無縫集成。

  Dubbo是一款高性能、輕量級的開源Java RPC框架,它提供了三大核心能力:面向接口的遠程方法調用,智能容錯和負載均衡,以及服務自動注冊和發(fā)現。

  相關概念

  dubbo運行架構如下圖示

1663221925031_1.png

  節(jié)點角色說明

  | 節(jié)點 | 角色說明 |

  | ----------- | -------------------------------------- |

  | `Provider` | 暴露服務的服務提供方 |

  | `Consumer` | 調用遠程服務的服務消費方 |

  | `Registry` | 服務注冊與發(fā)現的注冊中心 |

  | `Monitor` | 統(tǒng)計服務的調用次數和調用時間的監(jiān)控中心 |

  | `Container` | 服務運行容器 |

  調用關系說明

  1. 服務容器負責啟動,加載,運行服務提供者。

  2. 服務提供者在啟動時,向注冊中心注冊自己提供的服務。

  3. 服務消費者在啟動時,向注冊中心訂閱自己所需的服務。

  4. 注冊中心返回服務提供者地址列表給消費者,如果有變更,注冊中心將基于長連接推送變更數據給消費者。

  5. 服務消費者,從提供者地址列表中,基于軟負載均衡算法,選一臺提供者進行調用,如果調用失敗,再選另一臺調用。

  6. 服務消費者和提供者,在內存中累計調用次數和調用時間,定時每分鐘發(fā)送一次統(tǒng)計數據到監(jiān)控中心。

  關于dubbo 的特點分別有連通性、健壯性、伸縮性、以及向未來架構的升級性。特點的詳細介紹也可以參考[官方文檔](http://dubbo.apache.org/zh-cn/docs/user/preface/architecture.html)。

  環(huán)境搭建

  接下來逐步對dubbo各個模塊的源碼以及原理進行解析,目前dubbo框架已經交由Apache基金會進行孵化,被在github開源。

  Dubbo 社區(qū)目前主力維護的有 2.6.x 和 2.7.x 兩大版本,其中,

  - 2.6.x 主要以 bugfix 和少量 enhancements 為主,因此能完全保證穩(wěn)定性

  - 2.7.x 作為社區(qū)的主要開發(fā)版本,得到持續(xù)更新并增加了大量新 feature 和優(yōu)化,同時也帶來了一些穩(wěn)定性挑戰(zhàn)

  源碼拉取

  通過以下的這個命令簽出最新的dubbo項目源碼,并導入到IDEA中

git clone https://github.com/apache/dubbo.git dubbo

1663220480525_2.jpg

  可以看到Dubbo被拆分成很多的Maven項目,在后續(xù)課程中會介紹左邊每個模塊的大致作用。

  環(huán)境導入

  在本次課程中,不僅講解dubbo源碼還會涉及到相關的基礎知識,為了方便學員快速理解并掌握各個內容,已經準備好了相關工程,只需導入到IDEA中即可。對于工程中代碼的具體作用,在后續(xù)課程會依次講解。

  測試

  (1) 安裝zookeeper

  (2) 修改官網案例,配置zookeeper地址

  (3) 啟動服務提供者,啟動服務消費者

  架構體系

  源碼結構

  通過如下圖形可以大致的了解到,dubbo源碼各個模塊的相關作用:

1663220559257_3.jpg

  模塊說明:

  - dubbo-common 公共邏輯模塊:包括 Util 類和通用模型。

  - dubbo-remoting 遠程通訊模塊:相當于 Dubbo 協(xié)議的實現,如果 RPC 用 RMI協(xié)議則不需要使用此包。

  - dubbo-rpc 遠程調用模塊:抽象各種協(xié)議,以及動態(tài)代理,只包含一對一的調用,不關心集群的管理。

  - dubbo-cluster 集群模塊:將多個服務提供方偽裝為一個提供方,包括:負載均衡, 容錯,路由等,集群的地址列表可以是靜態(tài)配置的,也可以是由注冊中心下發(fā)。

  - dubbo-registry 注冊中心模塊:基于注冊中心下發(fā)地址的集群方式,以及對各種注冊中心的抽象。

  - dubbo-monitor 監(jiān)控模塊:統(tǒng)計服務調用次數,調用時間的,調用鏈跟蹤的服務。

  - dubbo-config 配置模塊:是 Dubbo 對外的 API,用戶通過 Config 使用Dubbo,隱藏 Dubbo 所有細節(jié)。

  - dubbo-container 容器模塊:是一個 Standlone 的容器,以簡單的 Main 加載 Spring 啟動,因為服務通常不需要 Tomcat/JBoss 等 Web 容器的特性,沒必要用 Web 容器去加載服務。

  整體設計

1663220600455_4.jpg

  圖例說明:

  - 圖中左邊淡藍背景的為服務消費方使用的接口,右邊淡綠色背景的為服務提供方使用的接口,位于中軸線上的為雙方都用到的接口。

  - 圖中從下至上分為十層,各層均為單向依賴,右邊的黑色箭頭代表層之間的依賴關系,每一層都可以剝離上層被復用,其中,Service 和 Config 層為 API,其它各層均為 SPI。

  - 圖中綠色小塊的為擴展接口,藍色小塊為實現類,圖中只顯示用于關聯各層的實現類。

  - 圖中藍色虛線為初始化過程,即啟動時組裝鏈,紅色實線為方法調用過程,即運行時調時鏈,紫色三角箭頭為繼承,可以把子類看作父類的同一個節(jié)點,線上的文字為調用的方法。

  各層說明

  - config 配置層:對外配置接口,以 `ServiceConfig`, `ReferenceConfig` 為中心,可以直接初始化配置類,也可以通過 spring 解析配置生成配置類。

  - proxy 服務代理層:服務接口透明代理,生成服務的客戶端 Stub 和服務器端 Skeleton, 以 `ServiceProxy` 為中心,擴展接口為 `ProxyFactory`。

  - registry 注冊中心層:封裝服務地址的注冊與發(fā)現,以服務 URL 為中心,擴展接口為 `RegistryFactory`, `Registry`, `RegistryService`。

  - cluster 路由層:封裝多個提供者的路由及負載均衡,并橋接注冊中心,以 `Invoker` 為中心,擴展接口為 `Cluster`, `Directory`, `Router`, `LoadBalance`。

  - monitor 監(jiān)控層:RPC 調用次數和調用時間監(jiān)控,以 `Statistics` 為中心,擴展接口為 `MonitorFactory`, `Monitor`, `MonitorService`。

  - protocol 遠程調用層:封裝 RPC 調用,以 `Invocation`, `Result` 為中心,擴展接口為 `Protocol`, `Invoker`, `Exporter`。

  - exchange 信息交換層:封裝請求響應模式,同步轉異步,以 `Request`, `Response` 為中心,擴展接口為 `Exchanger`, `ExchangeChannel`, `ExchangeClient`, `ExchangeServer`。

  - transport 網絡傳輸層:抽象 mina 和 netty 為統(tǒng)一接口,以 `Message` 為中心,擴展接口為 `Channel`, `Transporter`, `Client`, `Server`, `Codec`。

  - serialize 數據序列化層:可復用的一些工具,擴展接口為 `Serialization`, `ObjectInput`, `ObjectOutput`, `ThreadPool`。

  SPI機制

  在 Dubbo 中,SPI 是一個非常重要的模塊。基于 SPI,我們可以很容易的對 Dubbo 進行拓展。如果大家想要學習 Dubbo 的源碼,SPI 機制務必弄懂。接下來,我們先來了解一下 Java SPI 與 Dubbo SPI 的用法,然后再來分析 Dubbo SPI 的源碼。

  SPI的概述

  SPI的主要作用

  SPI 全稱為 Service Provider Interface,是一種服務發(fā)現機制。SPI 的本質是將接口實現類的全限定名配置在文件中,并由服務加載器讀取配置文件,加載實現類。這樣可以在運行時,動態(tài)為接口替換實現類。正因此特性,我們可以很容易的通過 SPI 機制為我們的程序提供拓展功能。

1663220669266_5.jpg

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

  入門案例

  首先,我們定義一個接口,名稱為 Robot。

public interface Robot {
    void sayHello();
}

  接下來定義兩個實現類,分別為 OptimusPrime 和 Bumblebee。

public class OptimusPrime implements Robot {
   
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Optimus Prime.");
    }
}

public class Bumblebee implements Robot {

    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee.");
    }
}

  接下來 META-INF/services 文件夾下創(chuàng)建一個文件,名稱為 Robot 的全限定名 com.itheima.java.spi.Robot。文件內容為實現類的全限定的類名,如下:

  ```properties

  com.itheima.java.spi.impl.Bumblebee

  com.itheima.java.spi.impl.OptimusPrime

  ```

  做好所需的準備工作,接下來編寫代碼進行測試。

public class JavaSPITest {

    @Test
    public void sayHello() throws Exception {
        ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
        System.out.println("Java SPI");
        serviceLoader.forEach(Robot::sayHello);
    }
}

  最后來看一下測試結果,如下:

1663220848723_6.jpg

  從測試結果可以看出,我們的兩個實現類被成功的加載,并輸出了相應的內容。

  總結

  調用過程

  應用程序調用ServiceLoader.load方法,創(chuàng)建一個新的ServiceLoader,并實例化該類中的成員變量

  應用程序通過迭代器接口獲取對象實例,ServiceLoader先判斷成員變量providers對象中(LinkedHashMap類型)是否有緩存實例對象,如果有緩存,直接返回。

  如果沒有緩存,執(zhí)行類的裝載,

  優(yōu)點

  使用 Java SPI 機制的優(yōu)勢是實現解耦,使得接口的定義與具體業(yè)務實現分離,而不是耦合在一起。應用進程可以根據實際業(yè)務情況啟用或替換具體組件。

  缺點

  - 不能按需加載。雖然 ServiceLoader 做了延遲載入,但是基本只能通過遍歷全部獲取,也就是接口的實現類得全部載入并實例化一遍。如果你并不想用某些實現類,或者某些類實例化很耗時,它也被載入并實例化了,這就造成了浪費。

  - 獲取某個實現類的方式不夠靈活,只能通過 Iterator 形式獲取,不能根據某個參數來獲取對應的實現類。

  - 多個并發(fā)多線程使用 ServiceLoader 類的實例是不安全的。

  - 加載不到實現類時拋出并不是真正原因的異常,錯誤很難定位。

  Dubbo中的SPI

  概述

  Dubbo 并未使用 Java SPI,而是重新實現了一套功能更強的 SPI 機制。Dubbo SPI 的相關邏輯被封裝在了 ExtensionLoader 類中,通過 ExtensionLoader,我們可以加載指定的實現類。

  入門案例

  與 Java SPI 實現類配置不同,Dubbo SPI 是通過鍵值對的方式進行配置,這樣我們可以按需加載指定的實現類。下面來演示 Dubbo SPI 的用法:

  Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路徑下,與 Java SPI 實現類配置不同,Dubbo SPI 是通過鍵值對的方式進行配置,配置內容如下。

  ```properties

  optimusPrime = org.apache.spi.OptimusPrime

  bumblebee = org.apache.spi.Bumblebee

  ```

  在使用Dubbo SPI 時,需要在接口上標注 @SPI 注解。

@SPI
public interface Robot {
    void sayHello();
}

  通過 ExtensionLoader,我們可以加載指定的實現類,下面來演示 Dubbo SPI :

public class DubboSPITest {

    @Test
    public void sayHello() throws Exception {
        ExtensionLoader<Robot> extensionLoader =
            ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        bumblebee.sayHello();
    }
}

  測試結果如下:

1663221037401_7.jpg

  Dubbo SPI 除了支持按需加載接口實現類,還增加了 IOC 和 AOP 等特性,這些特性將會在接下來的源碼分析章節(jié)中一一進行介紹。

  源碼分析

  上一章簡單演示了 Dubbo SPI 的使用方法,首先通過 ExtensionLoader 的 getExtensionLoader 方法獲取一個 ExtensionLoader 實例,然后再通過 ExtensionLoader 的 getExtension 方法獲取拓展類對象。下面我們從 ExtensionLoader 的 getExtension 方法作為入口,對拓展類對象的獲取過程進行詳細的分析。

public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
             // 獲取默認的拓展實現類
            return getDefaultExtension();
        }
        // Holder,顧名思義,用于持有目標對象
        Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
         // 雙重檢查
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    // 創(chuàng)建拓展實例
                    instance = createExtension(name);
                    // 設置實例到 holder 中
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

  上面代碼的邏輯比較簡單,首先檢查緩存,緩存未命中則創(chuàng)建拓展對象。下面我們來看一下創(chuàng)建拓展對象的過程是怎樣的。

private T createExtension(String name) {
    // 從配置文件中加載所有的拓展類,可得到“配置項名稱”到“配置類”的映射關系表
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            // 通過反射創(chuàng)建實例
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        // 向實例中注入依賴
        injectExtension(instance);
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (CollectionUtils.isNotEmpty(wrapperClasses)) {
              // 循環(huán)創(chuàng)建 Wrapper 實例
            for (Class<?> wrapperClass : wrapperClasses) {
                // 將當前 instance 作為參數傳給 Wrapper 的構造方法,并通過反射創(chuàng)建 Wrapper 實例。
                // 然后向 Wrapper 實例中注入依賴,最后將 Wrapper 實例再次賦值給 instance 變量
                instance = injectExtension(
                    (T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("...");
    }
}

  createExtension 方法的邏輯稍復雜一下,包含了如下的步驟:

  1. 通過 getExtensionClasses 獲取所有的拓展類

  2. 通過反射創(chuàng)建拓展對象

  3. 向拓展對象中注入依賴

  4. 將拓展對象包裹在相應的 Wrapper 對象中

  以上步驟中,第一個步驟是加載拓展類的關鍵,第三和第四個步驟是 Dubbo IOC 與 AOP 的具體實現。由于此類設計源碼較多,這里簡單的總結下ExtensionLoader整個執(zhí)行邏輯:

  ```tex

  getExtension(String name) #根據key獲取拓展對象

  -->createExtension(String name) #創(chuàng)建拓展實例

  -->getExtensionClasses #根據路徑獲取所有的拓展類

  -->loadExtensionClasses #加載拓展類

  -->cacheDefaultExtensionName #解析@SPI注解

  -->loadDirectory #方法加載指定文件夾配置文件

  -->loadResource #加載資源

  -->loadClass #加載類,并通過 loadClass 方法對類進行緩存

  ```

  SPI中的IOC和AOP

  依賴注入

  Dubbo IOC 是通過 setter 方法注入依賴。Dubbo 首先會通過反射獲取到實例的所有方法,然后再遍歷方法列表,檢測方法名是否具有 setter 方法特征。若有,則通過 ObjectFactory 獲取依賴對象,最后通過反射調用 setter 方法將依賴設置到目標對象中。整個過程對應的代碼如下:

private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            // 遍歷目標類的所有方法
            for (Method method : instance.getClass().getMethods()) {
                // 檢測方法是否以 set 開頭,且方法僅有一個參數,且方法訪問級別為 public
                if (method.getName().startsWith("set")
                    && method.getParameterTypes().length == 1
                    && Modifier.isPublic(method.getModifiers())) {
                    // 獲取 setter 方法參數類型
                    Class<?> pt = method.getParameterTypes()[0];
                    try {
                        // 獲取屬性名,比如 setName 方法對應屬性名 name
                        String property = method.getName().length() > 3 ? 
                            method.getName().substring(3, 4).toLowerCase() +
                                method.getName().substring(4) : "";
                        // 從 ObjectFactory 中獲取依賴對象
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            // 通過反射調用 setter 方法設置依賴
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("fail to inject via method...");
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

  在上面代碼中,objectFactory 變量的類型為 AdaptiveExtensionFactory,AdaptiveExtensionFactory 內部維護了一個 ExtensionFactory 列表,用于存儲其他類型的 ExtensionFactory。Dubbo 目前提供了兩種 ExtensionFactory,分別是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于創(chuàng)建自適應的拓展,后者是用于從 Spring 的 IOC 容器中獲取所需的拓展。這兩個類的類的代碼不是很復雜,這里就不一一分析了。

  Dubbo IOC 目前僅支持 setter 方式注入,總的來說,邏輯比較簡單易懂。

  動態(tài)增強

  在用Spring的時候,我們經常會用到AOP功能。在目標類的方法前后插入其他邏輯。比如通常使用Spring AOP來實現日志,監(jiān)控和鑒權等功能。 Dubbo的擴展機制,是否也支持類似的功能呢?答案是yes。在Dubbo中,有一種特殊的類,被稱為Wrapper類。通過裝飾者模式,使用包裝類包裝原始的擴展點實例。在原始擴展點實現前后插入其他邏輯,實現AOP功能。

  裝飾者模式

  裝飾者模式:在不改變原類文件以及不使用繼承的情況下,動態(tài)地將責任附加到對象上,從而實現動態(tài)拓展一個對象的功能。它是通過創(chuàng)建一個包裝對象,也就是裝飾來包裹真實的對象。

1663221244675_8.jpg

  一般來說裝飾者模式有下面幾個參與者:

  - Component:裝飾者和被裝飾者共同的父類,是一個接口或者抽象類,用來定義基本行為

  - ConcreteComponent:定義具體對象,即被裝飾者

  - Decorator:抽象裝飾者,繼承自Component,從外類來擴展ConcreteComponent。對于ConcreteComponent來說,不需要知道Decorator的存在,Decorator是一個接口或抽象類

  - ConcreteDecorator:具體裝飾者,用于擴展ConcreteComponent

  注:裝飾者和被裝飾者對象有相同的超類型,因為裝飾者和被裝飾者必須是一樣的類型,這里利用繼承是為了達到類型匹配,而不是利用繼承獲得行為。

  dubbo中的AOP

  Dubbo AOP 是通過裝飾者模式完成的,接下來通過一個簡單的案例來學習dubbo中AOP的實現方式。

  首先定義一個接口

package com.itheima.dubbo;

import org.apache.dubbo.common.extension.SPI;

@SPI
public interface Phone {
    void call();
}

  定義接口的實現類,也就是被裝飾者

package com.itheima.dubbo;

public class IphoneX implements Phone {

    @Override
    public void call() {
        System.out.println("iphone正在撥打電話");
    }
}

  為了簡單,這里省略了裝飾者接口。僅僅定義一個裝飾者,實現phone接口,內部配置增強邏輯方法

package com.itheima.dubbo;

public class MusicPhone implements Phone {

    private Phone phone;

    public MusicPhone(Phone phone) {
        this.phone = phone;
    }

    @Override
    public void call() {
        System.out.println("播放彩鈴");
        this.phone.call();
    }
}

  ```

  添加拓展點配置文件META-INF/dubbo/com.itheima.dubbo.Phone,內容如下

  ```

  iphone = com.itheima.dubbo.IphoneX

  filter = com.itheima.dubbo.MusicPhone

  ```

  配置測試方法

public static void main(String[] args) {
        ExtensionLoader<Phone> extensionLoader =
                ExtensionLoader.getExtensionLoader(Phone.class);
        Phone phone = extensionLoader.getExtension("iphone");
        phone.call();
    }

  具體執(zhí)行效果如下

1663221390449_9.jpg

  先調用裝飾者增強,再調用目標方法完成業(yè)務邏輯。

  通過測試案例,可以看到在Dubbo SPI中具有增強AOP的功能,我們只需要關注dubbo源碼中這樣一行代碼就夠了。

//檢查是否具有裝飾者類,如果有調用裝飾者類的構造方法,并返回實例對象
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
    for (Class<?> wrapperClass : wrapperClasses) {
        instance = injectExtension(
            (T) wrapperClass.getConstructor(type).newInstance(instance));
    }
}

  動態(tài)編譯

  SPI中的自適應

  我們知道在 Dubbo 中,很多拓展都是通過 SPI 機制 進行加載的,比如 Protocol、Cluster、LoadBalance、ProxyFactory 等。有時,有些拓展并不想在框架啟動階段被加載,而是希望在拓展方法被調用時,根據運行時參數進行加載,即根據參數動態(tài)加載實現類。如下所示:

  

1663221441258_10.jpg

  這種在運行時,根據方法參數才動態(tài)決定使用具體的拓展,在dubbo中就叫做擴展點自適應實例。其實是一個擴展點的代理,將擴展的選擇從Dubbo啟動時,延遲到RPC調用時。Dubbo中每一個擴展點都有一個自適應類,如果沒有顯式提供,Dubbo會自動為我們創(chuàng)建一個,默認使用Javaassist。

  自適應拓展機制的實現邏輯是這樣的

  1. 首先 Dubbo 會為拓展接口生成具有代理功能的代碼;

  2. 通過 javassist 或 jdk 編譯這段代碼,得到 Class 類;

  3. 通過反射創(chuàng)建代理類;

  4. 在代理類中,通過URL對象的參數來確定到底調用哪個實現類;

  javassist入門

  Javassist是一個開源的分析、編輯和創(chuàng)建Java字節(jié)碼的類庫。是由東京工業(yè)大學的數學和計算機科學系的 Shigeru Chiba (千葉滋)所創(chuàng)建的。它已加入了開放源代碼JBoss 應用服務器項目,通過使用Javassist對字節(jié)碼操作為JBoss實現動態(tài)AOP框架。javassist是jboss的一個子項目,其主要的優(yōu)點,在于簡單,而且快速。直接使用java編碼的形式,而不需要了解虛擬機指令,就能動態(tài)改變類的結構,或者動態(tài)生成類。為了方便更好的理解dubbo中的自適應,這里通過案例的形式來熟悉下Javassist的基本使用。

package com.itheima.compiler;

import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;

/**
 *  Javassist是一個開源的分析、編輯和創(chuàng)建Java字節(jié)碼的類庫
 *  能動態(tài)改變類的結構,或者動態(tài)生成類
 */
public class CompilerByJavassist {

    public static void main(String[] args) throws Exception {

        // ClassPool:class對象容器
        ClassPool pool = ClassPool.getDefault();

        // 通過ClassPool生成一個User類
        CtClass ctClass = pool.makeClass("com.itheima.domain.User");

        // 添加屬性     -- private String username
        CtField enameField = new CtField(pool.getCtClass("java.lang.String"),
                "username", ctClass);

        enameField.setModifiers(Modifier.PRIVATE);

        ctClass.addField(enameField);

        // 添加屬性    -- private int age
        CtField enoField = new CtField(pool.getCtClass("int"), "age", ctClass);

        enoField.setModifiers(Modifier.PRIVATE);

        ctClass.addField(enoField);

        //添加方法
        ctClass.addMethod(CtNewMethod.getter("getUsername", enameField));
        ctClass.addMethod(CtNewMethod.setter("setUsername", enameField));
        ctClass.addMethod(CtNewMethod.getter("getAge", enoField));
        ctClass.addMethod(CtNewMethod.setter("setAge", enoField));


        // 無參構造器
        CtConstructor constructor = new CtConstructor(null, ctClass);
        constructor.setBody("{}");
        ctClass.addConstructor(constructor);

        // 添加構造函數
        //ctClass.addConstructor(new CtConstructor(new CtClass[] {}, ctClass));

        CtConstructor ctConstructor = new CtConstructor(new CtClass[] {pool.get(String.class.getName()),CtClass.intType}, ctClass);
        ctConstructor.setBody("{\n this.username=$1; \n this.age=$2;\n}");
        ctClass.addConstructor(ctConstructor);

        // 添加自定義方法
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "printUser",new CtClass[] {}, ctClass);
        // 為自定義方法設置修飾符
        ctMethod.setModifiers(Modifier.PUBLIC);
        // 為自定義方法設置函數體
        StringBuffer buffer2 = new StringBuffer();
        buffer2.append("{\nSystem.out.println(\"用戶信息如下\");\n")
                .append("System.out.println(\"用戶名=\"+username);\n")
                .append("System.out.println(\"年齡=\"+age);\n").append("}");
        ctMethod.setBody(buffer2.toString());
        ctClass.addMethod(ctMethod);

        //生成一個class
        Class<?> clazz = ctClass.toClass();

        Constructor cons2 = clazz.getDeclaredConstructor(String.class,Integer.TYPE);

        Object obj = cons2.newInstance("itheima",20);

        //反射 執(zhí)行方法
        obj.getClass().getMethod("printUser", new Class[] {})
                .invoke(obj, new Object[] {});

        // 把生成的class文件寫入文件
        byte[] byteArr = ctClass.toBytecode();
        FileOutputStream fos = new FileOutputStream(new File("D://User.class"));
        fos.write(byteArr);
        fos.close();
    }
}

  通過以上代碼,我們可以知道使用javassist可以方便的在運行時,按需動態(tài)的創(chuàng)建java對象,并執(zhí)行內部方法。而這也是dubbo中動態(tài)編譯的核心。

  源碼分析

  Adaptive注解

  在開始之前,我們有必要先看一下與自適應拓展息息相關的一個注解,即 Adaptive 注解。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    String[] value() default {};
}

  從上面的代碼中可知,Adaptive 可注解在類或方法上。

  - 標注在類上:Dubbo 不會為該類生成代理類。

  - 標注在方法上:Dubbo 則會為該方法生成代理邏輯,表示當前方法需要根據 參數URL 調用對應的擴展點實現。

  獲取自適應拓展類

  dubbo中每一個擴展點都有一個自適應類,如果沒有顯式提供,Dubbo會自動為我們創(chuàng)建一個,默認使用Javaassist。 先來看下創(chuàng)建自適應擴展類的代碼:

public T getAdaptiveExtension() {
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                      instance = createAdaptiveExtension();
                      cachedAdaptiveInstance.set(instance);
                }
            }        
    }

    return (T) instance;
}

  繼續(xù)看createAdaptiveExtension方法

```java
private T createAdaptiveExtension() {        
    return injectExtension((T) getAdaptiveExtensionClass().newInstance());
}
```

  繼續(xù)看getAdaptiveExtensionClass方法

private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

  繼續(xù)看createAdaptiveExtensionClass方法,繞了一大圈,終于來到了具體的實現了。看這個createAdaptiveExtensionClass方法,它首先會生成自適應類的Java源碼,然后再將源碼編譯成Java的字節(jié)碼,加載到JVM中。

private Class<?> createAdaptiveExtensionClass() {
        String code = createAdaptiveExtensionClassCode();
        ClassLoader classLoader = findClassLoader();
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }
```

  Compiler的代碼,默認實現是javassist。

@SPI("javassist")
public interface Compiler {
    Class<?> compile(String code, ClassLoader classLoader);
}

  createAdaptiveExtensionClassCode()方法中使用一個StringBuilder來構建自適應類的Java源碼。方法實現比較長,這里就不貼代碼了。這種生成字節(jié)碼的方式也挺有意思的,先生成Java源代碼,然后編譯,加載到jvm中。通過這種方式,可以更好的控制生成的Java類。而且這樣也不用care各個字節(jié)碼生成框架的api等。因為xxx.java文件是Java通用的,也是我們最熟悉的。只是代碼的可讀性不強,需要一點一點構建xx.java的內容。

分享到:
在線咨詢 我要報名
和我們在線交談!