從 標籤開始看 SOFA-Boot 如何融入 Spring

莫那·魯道發表於2019-02-15
從  標籤開始看 SOFA-Boot 如何融入 Spring

前言

SOFA-Boot 現階段支援 XML 的方式在 Spring 中定義 Bean,通過這些標籤,我們就能從 Spring 容器中取出 RPC 中的引用,並進行呼叫,那麼他是如何處理這些自定義標籤的呢?一起來看看。

如何使用?

官方例子:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:sofa="http://sofastack.io/schema/sofaboot"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://sofastack.io/schema/sofaboot   http://sofastack.io/schema/sofaboot.xsd"
       default-autowire="byName">

    <bean id="personServiceImpl" class="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonServiceImpl"/>

    <sofa:service ref="personServiceImpl" interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService">
        <sofa:binding.bolt/>
        <sofa:binding.rest/>
    </sofa:service>


    <sofa:reference id="personReferenceBolt" interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService">
        <sofa:binding.bolt/>
    </sofa:reference>

    <sofa:reference id="personReferenceRest" interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService">
        <sofa:binding.rest/>
    </sofa:reference>

    <bean id="personFilter" class="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonServiceFilter"/>

</beans>
複製程式碼

顯眼的 sofa 標籤。那麼如何知道他是怎麼處理這些標籤的內容的呢?

答:從上面的 xmlns 名稱空間可以找到。如果寫過自定義標籤的話,一定很熟悉了。

如果沒有寫過的話,就簡單介紹一下。Spring 支援使用者自定標籤,需要遵守以下規範。

  1. 編寫 xsd 檔案,就是定義標籤的屬性。

  2. 繼承抽象類 NamespaceHandlerSupport 並實現 init 方法,此類就是處理名稱空間的類,不僅需要實現 init 方法,你還需要繼承 AbstractSingleBeanDefinitionParser 類,自行解析標籤。通常寫法是這樣:
    registerBeanDefinitionParser("tagName",new UserBeanDefinitionParser());

  3. 在做完上面的步驟後,你需要在 META-INF 目錄下編寫 spring.handlersspring.schemas 檔案,前者 key 為名稱空間名稱,value 為 NamespaceHandlerSupport 的具體實現;後者 key 為 xsd 名稱空間,value 為 xsd 具體檔案(classpath 下)。

完成上面的步驟後,Spring 在載入 xml 配置檔案的時候,會檢查名稱空間,如果是自定義的,則會根據名稱空間的 key 找到對應的解析器,也就是 NamespaceHandlerSupport 對自定義標籤進行解析。

So,我們的目的是看 sofa 標籤是如何解析的,則找到解析 sofa 的NamespaceHandlerSupport

尋找解析 sofa 標籤的源頭

通過 IDEA 或者別的工具全域性搜尋,我們找到了原始碼中位於 start 模組下,resources 下的 META-INF 目錄,目錄下有以下幾個檔案:

  1. rpc.xsd
  2. sofaboot.xsd
  3. spring.handlers
  4. spring.schemas

這幾個檔案我們是比較關注的,重點看 handlers 檔案。內容如下:

http://sofastack.io/schema/sofaboot=com.alipay.sofa.infra.config.spring.namespace.handler.SofaBootNamespaceHandler
複製程式碼

SofaBootNamespaceHandler 明顯就是明明空間處理類,繼承了 Spring 的 NamespaceHandlerSupport

該類的 init 方法通過 Java 的 SPI 進行擴充套件,找到 SofaBootTagNameSupport 的標籤支援類,目前有 2 個實現: ServiceDefinitionParser 和 ReferenceDefinitionParser。

兩個類支援不同的 element。一個是引用服務,一個是釋出服務。

這樣就比較清晰了。在得到兩個類之後,註冊到 Spring 的 parsers map 中。key 是 element 名字,value 是解析器。

具體的解析上面說了,service 和 reference。

Sofa 在這個設計上使用了模板模式,使用一個抽象類 AbstractContractDefinitionParser,並定義一個 doParseInternal 抽象方法讓子類去實現。

抽象父類將一些公用的屬性進行解析,下圖中都是公用的屬性:

image.png

你咋確定是公用的屬性呢?首先,xml 解析後,肯定是要注入到 Bean 裡面去的,那麼這個 Bean 是什麼呢?實際上,在 AbstractSingleBeanDefinitionParser 抽象類中,會有一個返回 Bean 型別的方法,對應著一個 tag,而他們的父類就是AbstractContractFactoryBean

該類公用屬性就對應上面圖片中的定義。

有了這些基礎的資訊,還是不能夠直接使用的,畢竟都是字串。我們要看看 SOFA 是如何將自己和 Spring 容器融合在一起的。

如何融入 Spring?

來看看他們的子類, ReferenceDefinitionParser 類(ServiceFactoryBean 類似)。對應的 Bean 是 ReferenceFactoryBean,該類單獨定義了負載均衡(loadBalance)的屬性。

啊,從這個類的名字看,很熟悉,之前在分析 Spring 原始碼的時候,就看見過 FactoryBean。可以通過 getObject 方法修改返回的 Bean。

來看看這個類的 類圖

image.png

如我們所料,繼承了 Spring 中關鍵的擴充套件點。而他的 getObject 方法將返回一個代理:

   @Override
    public Object getObject() throws Exception {
        return proxy;
    }
複製程式碼

具體建立代理的方法是 com.alipay.sofa.runtime.service.component.ReferenceComponent 類的 createProxy 方法,然後呼叫介面卡。介面卡的作用就是膠水的作用,將 Spring 容器和第三方框架粘合起來,Spring 提供的介面則是 FactoryBean 等介面,而第三方框架可以在 getObject 方法中大有作為。

SOFA-Boot 的介面卡在 om.alipay.sofa.rpc.boot.runtime.adapter 包中,具體實現則是 RpcBindingAdapter 類,其中 outBinding 方法用於釋出服務,inBinding 方法用於引用服務,這裡直接使用的就是 SOFA-RPC 的 consumerConfig 和 providerConfig。這就應該很熟悉了吧。哈哈。

來點程式碼看看(已去除異常處理):

@Override
public Object outBinding(Object contract, RpcBinding binding, Object target, SofaRuntimeContext sofaRuntimeContext) {

    String uniqueName = ProviderConfigContainer.createUniqueName((Contract) contract, binding);
    ProviderConfig providerConfig = ProviderConfigContainer.getProviderConfig(uniqueName);

    providerConfig.export();

    if (ProviderConfigContainer.isAllowPublish()) {
        Registry registry = RegistryConfigContainer.getRegistry();
        providerConfig.setRegister(true);
        registry.register(providerConfig);
    }
    return Boolean.TRUE;
}
複製程式碼
@Override
public Object inBinding(Object contract, RpcBinding binding, SofaRuntimeContext sofaRuntimeContext) {
    ConsumerConfig consumerConfig = ConsumerConfigHelper.getConsumerConfig((Contract) contract, binding);
    ConsumerConfigContainer.addConsumerConfig(binding, consumerConfig);

    Object result = consumerConfig.refer();
    binding.setConsumerConfig(consumerConfig);
    return result;
}
複製程式碼

So,當 Spring 容器使用 getObject 方法的時候,獲取的就是這個代理物件啦。是不是很簡單?

有一點需要注意一下,ServiceFactoryBean 的設計和 ReferenceFactoryBean 確實是類似,但是!但是!他的 getObject 方法就是實現類本身,這是 RPC 框架本身的設計決定的,因為它不需要代理,只需要釋出服務就行,當收到了客戶端傳來的資訊,就直接呼叫實現類的指定方法就好了,沒有客戶端這麼複雜。

當然,SOFA 中還有一個元件的概念,我們有時間會好好看看這塊的設計。

總結

這次我們從 SOFA-Boot 配置檔案的 xml 標籤開始,對他如何融入 Spring 進行了分析,實際上,整體和我們聯想的類似,使用 Spring 的 FactoryBean 的 getObject 方法返回代理,如果是客戶端的話,在 getObject 方法中,會建立一個動態代理,這就要使用 SOFA-RPC 了,所以,融合 RPC 和 Spring 的任務肯定是個介面卡。SOFA 的實現就是 RpcBindingAdapter 類。在該類中,將 RPC 和 Spring 適配。

而釋出服務相比較引用服務就簡單一點了,整體上就是通過註解將服務釋出,當然也是使用的 RpcBindingAdapter 進行適配。但是沒有使用代理(不需要)。

好了,這次研究 SOFA 和 Spring 融合的過程就到這裡啦。

bye !

相關文章