前言
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 支援使用者自定標籤,需要遵守以下規範。
-
編寫 xsd 檔案,就是定義標籤的屬性。
-
繼承抽象類
NamespaceHandlerSupport
並實現 init 方法,此類就是處理名稱空間的類,不僅需要實現 init 方法,你還需要繼承AbstractSingleBeanDefinitionParser
類,自行解析標籤。通常寫法是這樣:
registerBeanDefinitionParser("tagName",new UserBeanDefinitionParser());
-
在做完上面的步驟後,你需要在 META-INF 目錄下編寫
spring.handlers
和spring.schemas
檔案,前者 key 為名稱空間名稱,value 為NamespaceHandlerSupport
的具體實現;後者 key 為 xsd 名稱空間,value 為 xsd 具體檔案(classpath 下)。
完成上面的步驟後,Spring 在載入 xml 配置檔案的時候,會檢查名稱空間,如果是自定義的,則會根據名稱空間的 key 找到對應的解析器,也就是 NamespaceHandlerSupport
對自定義標籤進行解析。
So,我們的目的是看 sofa 標籤是如何解析的,則找到解析 sofa 的NamespaceHandlerSupport
。
尋找解析 sofa 標籤的源頭
通過 IDEA 或者別的工具全域性搜尋,我們找到了原始碼中位於 start 模組下,resources 下的 META-INF 目錄,目錄下有以下幾個檔案:
- rpc.xsd
- sofaboot.xsd
- spring.handlers
- 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 !