1. 程式人生 > >從 <sofa:XXX> 標簽開始看 SOFA-Boot 如何融入 Spring

從 <sofa:XXX> 標簽開始看 SOFA-Boot 如何融入 Spring

均衡 init 抽象類 uniq registry 但是 imageview 工具 註解

技術分享圖片

前言

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 抽象方法讓子類去實現。

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

技術分享圖片

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

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

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

如何融入 Spring?

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

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

來看看這個類的 類圖

技術分享圖片

如我們所料,繼承了 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 !

從 <sofa:XXX> 標簽開始看 SOFA-Boot 如何融入 Spring