歡迎光臨
每天分享高質量文章

【剖析 | SOFARPC 框架】系列之 SOFARPC 註解支持剖析

SOFA

Scalable Open Financial Architecture

是螞蟻金服自主研發的金融級分佈式中間件,包含了構建金融級雲原生架構所需的各個組件,是在金融場景里錘煉出來的最佳實踐。


本文為《剖析 | SOFARPC 框架》第十一篇,作者敏古

《剖析 | SOFARPC 框架》系列由 SOFA 團隊和原始碼愛好者們出品,

專案代號:,官方目錄目前已經全部認領完畢,文末提供了已完成的文章目錄。


  1、前言

在 SOFABoot 環境下,SOFARPC 提供三種方式給開發人員發佈和取用 RPC 服務:

  1. XML 方式(配置)

  2. Annotation 方式(註解)

  3. 編程 API 方式(動態)


編程 API 方式與Spring 的 ApplicationContextAware 類似。XML的方式依賴於在xml中引入 SOFA 命名空間,利用 Bean 的生命周期管理,進行 Bean 的註入。相比這兩種方式,通過 Annotation 方式發佈 JVM 服務更加靈活方便,只需要在實現類上加 @SofaService@SofaRefernce 註解即可進行服務的發佈和取用。


本文針對 SOFARPC 在註解的支持和使用原理、原始碼兩部分進行一一介紹。

  2、註解原理解析

2.1、註解是什麼

註解又稱為元資料,可以對代碼中添加信息,這是一種形式化的方法,可以在稍後的某個時刻非常方便地使用這些資料。這個時刻可能是編譯時,也可能是運行時。


註解是 JDK1.5 版本開始引入的一個特性,用於對代碼進行說明,可以對包、類、接口、欄位、方法引數、區域性變數等進行註解。註解的本質就是一個繼承了 Annotation 接口的接口。一個註解準確意義上來說,只不過是一種特殊的註釋而已,如果沒有解析它的代碼,它可能連註釋都不如。


一般常用的註解可以分為三類:

  1. Java自帶的標準註解,包括@Override(標明重寫某個方法)、@Deprecated(標明某個類或方法過時)和@SuppressWarnings(標明要忽略的警告);

  2. 元註解,元註解是用於定義註解的註解;

  3. 自定義註解,可以根據自己的需求定義註解;

2.2、元註解

元註解是用於修飾註解的註解,通常用在註解的定義上。JAVA 中有以下幾個元註解:

  1. @Target:註解的作用標的,也就是指明,你的註解到底是用來修飾方法的?修飾類的?還是用來修飾欄位屬性的,有以下幾種型別:

    ElementType.TYPE:允許被修飾的註解作用在類、接口和列舉上

  • ElementType.FIELD:允許作用在屬性欄位上

  • ElementType.METHOD:允許作用在方法上

  • ElementType.PARAMETER:允許作用在方法引數上

  • ElementType.CONSTRUCTOR:允許作用在建構式上

  • ElementType.LOCAL_VARIABLE:允許作用在本地區域性變數上

  • ElementType.ANNOTATION_TYPE:允許作用在註解上

  • ElementType.PACKAGE:允許作用在包上

  • @Retention:指定了被修飾的註解的生命周期,分以下三種型別:

    • RetentionPolicy.SOURCE:該註解只保留在一個源檔案當中,當編譯器將源檔案編譯成class檔案時,它不會將源檔案中定義的註解保留在class檔案中。

    • RetentionPolicy.CLASS:該註解只保留在一個class檔案當中,當加載class檔案到記憶體時,虛擬機會將註解去掉,從而在程式中不能訪問。

    • RetentionPolicy.RUNTIME:該註解在程式運行期間都會存在記憶體當中。此時,我們可以通過反射來獲得定義在某個類上的所有註解。

  • @Documented:當我們執行 JavaDoc 文件打包時會被儲存進 doc 文件,反之將在打包時丟棄。

  • @Inherited:解修飾的註解是具有可繼承性的,也就說我們的註解修飾了一個類,而該類的子類將自動繼承父類的該註解。


  • @Override 為例子:


    當編譯器檢測到某個方法被修飾了 @Override 註解,編譯器就會檢查當前方法的方法簽名是否真正重寫了父類的某個方法,也就是比較父類中是否具有一個同樣的方法簽名。

    @Override僅被編譯器可知,編譯器在對 java 檔案進行編譯成位元組碼的過程中,一旦檢測到某個方法上被修飾了該註解,就會去匹對父類中是否具有一個同樣方法簽名的函式,否則不能通過編譯。

    2.3、註解解析方式

    解析一個類或者方法的註解通常有兩種形式,一種是編譯期直接的掃描,一種是運行期反射。

    2.3.1、編譯器的掃描

    指的是編譯器在對 java 代碼編譯位元組碼的過程中會檢測到某個類或者方法被一些註解修飾,這時它就會對於這些註解進行某些處理。典型的就是註解 @Override,一旦編譯器檢測到某個方法被修飾了 @Override 註解,編譯器就會檢查當前方法的方法簽名是否真正重寫了父類的某個方法,也就是比較父類中是否具有一個同樣的方法簽名。

    這一種情況只適用於那些編譯器已經熟知的註解類,比如 JDK 內置的幾個註解,而你自定義的註解,編譯器是不知道你這個註解的作用的。


    2.3.1、運行期反射

    首先對虛擬機的幾個註解相關的屬性表進行介紹,先大體瞭解註解在位元組碼檔案中是如何儲存的。虛擬機規範定義了一系列和註解相關的屬性表,也就是說,無論是欄位、方法或是類本身,如果被註解修飾了,就可以被寫進位元組碼檔案。屬性表有以下幾種:

    • RuntimeVisibleAnnotations:運行時可見的註解

    • RuntimeInVisibleAnnotations:運行時不可見的註解

    • RuntimeVisibleParameterAnnotations:運行時可見的方法引數註解

    • RuntimeInVisibleParameterAnnotations:運行時不可見的方法引數註解

    • AnnotationDefault:註解類元素的預設值

    java.lang.reflect.AnnotatedElement 接口是所有程式元素(Class、Method和Constructor)的父接口,程式通過反射獲取了某個類的 AnnotatedElemen t物件之後,利用 Java 的反射機獲取程式代碼中的註解,然後根據預先設定的處理規則解析處理相關註解以達到主機本身設定的功能標的。

    本質上來說,反射機制就是註解使用的核心,程式可以呼叫該物件的以下方法來訪問 Annotation信息:

    • getAnnotation:傳回指定的註解

    • isAnnotationPresent:判定當前元素是否被指定註解修飾

    • getAnnotations:傳回所有的註解

    • getDeclaredAnnotation:傳回本元素的指定註解

    • getDeclaredAnnotations:傳回本元素的所有註解,不包含父類繼承而來的

      3、SOFARPC 原始碼解析

    3.1、註解說明

    com.alipay.sofa.runtime.api.annotation.SofaReference 為例子(SofaService 類似),原始碼如下:


    基於元註解的含義,可以瞭解到:

    1. @SofaReference 生命周期為 RetentionPolicy.RUNTIME,代表永久儲存,可以反射獲取;

    2. 註解的作用標的 ElementType.FIELD,ElementType.METHOD,說明允許作用在方法和屬性欄位上;

    3. RPC 的系結方式有 JVM、BOLT、REST 三種;

    4. 預設服務系結關係為 JVM 方式;

    3.2、服務發佈與取用解析

    通過 ServiceAnnotationBeanPostProcesso 類中postProcessAfterInitializationpostProcessBeforeInitialization 方法分別進行服務的發佈和取用,其中通過反射對於註解的解析步驟大體相似,主要包含:

    1. 獲取 SofaService.class、SofaReference.class 指定註解

    2. 獲取的 SOFA 取用的型別,預設為 void

    3. 獲取的 SOFA 取用的 uniqueId

    3.2.1、總體流程

    首先看下服務發佈和取用整體流程圖,主要包含註解解析、組件生成、組件註冊幾個步驟,後面對每個步驟進行更加詳細的解釋。

    3.2.2、服務發佈

    @SofaService的標的是將一個類註冊到 SOFA Context 中。發佈到 SofaRuntimeContext 的過程其實就是把服務組件物件塞到 ConcurrentMap registry 物件中,當有其他地方需要查找服務組件的時候,可以通過 registry 進行查找。主要包含以下幾個步驟:

    1. 會遍歷 SOFA 系結關係,通過 handleSofaServiceBinding 方法進行不同型別的 RPC Binding。

    2. 生成 ServiceComponent 服務組件物件。

    3. 呼叫 ServiceComponent 服務組件的 register、resolve、activate方法,逐一呼叫對應 BindingAdapter 對外暴露服務。

    4. 不同的 BindingAdapter,對應的 outBinding 服務處理策略不一樣。對於 JvmBindingAdapter 直接傳回空,因為服務不需要暴露給外部,當其他模塊呼叫該服務,直接通過 registry 物件進行查找。其他 RPC BindingAdapter 則將服務信息推送到註冊中心 Confreg。

    5. 將 ServiceComponent 註冊到 sofa 的背景關係sofaRuntimeContext 中。

    3.2.3、服務取用

    @SofaReference的標的則是將 SOFA Context 中的一個服務註冊成為 Spring 中的一個bean。基於以上註解解析基礎上,主要通過 ReferenceRegisterHelper.registerReference() 方法從SOFA背景關係中,拿到服務對應的代理物件。在 registerReference() 方法內部,主要包含以下操作:

    1. 當註解的 jvmFirst() 為 true 時,會為服務自動再添加一個本地 JVM 的 binding,這樣能夠做到優先本地呼叫,避免跨機呼叫。

    2. 生成 ReferenceComponent 服務組件物件。

    3. 與 ServiceComponent 處理方式類似,ReferenceComponent 也會添加到 ConcurrentMap registry物件中,分別執行組件的register、resolve、activate 三個方法。其中 register、resolve 方法主要是改變組件的生命周期,代理物件的生成就是在 activate 方法中完成的。

    4. ReferenceComponent 組件通過不同型別的 binding 生成不同型別的代理物件。如果只有一個binding,使用當前 binding 生成代理物件。如果有多個 binding,優先使用 jvm binding 來生成本地呼叫的代理物件,若本地代理物件不存在,使用遠程代理物件。

    5. 對於JvmBindingAdapter 的 inBinding 方法,直接借助於動態代理技術進行生成代理物件,對於 RpcBindingAdapter 的 inBinding,在構造的過程存在向註冊中心訂閱的邏輯。

      4、總結

    通過 XML 的方式去配置 SOFA 的 JVM 服務和取用非常簡潔,但是多了一定的編碼工作量。

    因此,除了通過 XML 方式發佈 JVM 服務和取用之外,SOFA 還提供了 Annotation 的方式來發佈和取用 JVM 服務。@SofaService 註解省去了 宣告,但 bean 的定義還是必須要有的。

    SOFA 實際上是註冊了一個BeanPostProcessor 來處理@SofaService@SofaReference註解。需要發佈取用的物件屬於當前 bean 的實體變數,使用 xml 的方式進行服務發佈和取用,可以直接通過 Bean 生命周期的 InitializingBean#afterPropertiesSet 方法進行擴展。在工程中註解掃描是一個對所有 bean 的操作,只能通過實現 spring 的 beanpostprocessor 這個接口,另外有些屬性可能在發佈時需要用到。

    因此使用註解的方式進行服務發佈和取用,分別基於 Bean 生命周期的 BeanPostProcessor#postProcessAfterInitialization#postProcessBeforeInitialization方法進行擴展。


    對比服務的發佈和取用的兩種常用方式,XML 是一種集中式的元資料,與原始碼無系結,註解是一種分散式的元資料,與原始碼緊系結。SOFARPC 初始的版本,並不支持通過註解進行 RPC 服務的發佈和取用,需要使用 XML 的方式進行配置。後來在開源 SOFARPC 版本中增加這個功能的註解支持,對服務發佈和取用做了一個使用方式的補充,而對於 XML 與註解的優劣取捨,大家可以根據團隊的規範和個人的評估進行相應的使用。

      5、參考文件

    • Java annotation:

      https://en.wikipedia.org/wiki/Java_annotation

    • SOFASTACK 服務發佈/服務取用:

      http://www.sofastack.tech/sofa-rpc/docs/Publish-And-Reference


    相關鏈接

    SOFA 文件: http://www.sofastack.tech/

    SOFA: https://github.com/alipay

    SOFARPC: https://github.com/alipay/sofa-rpc

    SOFABolt: https://github.com/alipay/sofa-bolt

      《剖析 | SOFARPC 框架》系列歷史文章



    長按關註,獲取分佈式架構乾貨

    歡迎大家共同打造 SOFAStack https://github.com/alipay

    點擊閱讀原文,加入我們

    赞(0)

    分享創造快樂