最近又重新開始看jupiter的源碼弥搞。這個(gè)開源項(xiàng)目是阿里的一位大神寫的荣堰,比起現(xiàn)在較為流行的dubbo阔挠、motan等生產(chǎn)上的開源軟件來說輕量很多牛隅,也比較容易入門學(xué)習(xí)扰路。本來想看看dubbo的源碼的扛点,無奈第一步都沒賣出去税灌,被extension機(jī)制給難住了厌漂。雖說目前dubbo已經(jīng)成為apache的孵化項(xiàng)目了券盅,對(duì)于研究源碼的渣渣我來說還是有一定的難度的盆驹。于是退而求其次承耿,jupiter就是一個(gè)比較容易入手的選擇呀狼。為什么說這個(gè)jupiter比較容易入門呢锁摔?首先代碼比較少巡验,不是很多际插,對(duì)閱讀來說不會(huì)有很多繞的地方。其次這個(gè)項(xiàng)目有很多熱心的網(wǎng)友也在一起讀显设,可以有很多交流的地方框弛,有一個(gè)專門討論jupiter的交流群,可以很方便的和各路大神交流學(xué)習(xí)捕捂。
因?yàn)閖upiter源碼我沒有完全讀完瑟枫,只能看一點(diǎn)寫一點(diǎn)斗搞。說不定等看完源碼后再重新整理一下行文結(jié)構(gòu)呢,也說不定放棄了呢慷妙,誰知道呢僻焚?
按照常規(guī)思路來說肯定是從一個(gè)demo來入門,但是我不決定這么做膝擂,因?yàn)槿绻麑?duì)rpc熟悉的伙計(jì)一定知道怎么去玩虑啤,不知道怎么去玩的現(xiàn)在可以關(guān)掉瀏覽器打lol或者吃雞去了,因?yàn)槟悴慌浼懿觥]錯(cuò)狞山,就是這么傲嬌。
看了這么多java rpc的框架比如motan叉寂、dubbo和jupiter萍启,都有一個(gè)共同的地方,他們都使用spring作為容器來集成办绝。這樣也是情有可原伊约,我相信java應(yīng)用中沒有不使用spring的吧姚淆。因此都選擇這樣去做大概是因?yàn)檫@樣很容易去集成到自己的項(xiàng)目中孕蝉。當(dāng)然,這類rpc框架并不是一定得和spring集成腌逢。把他們稱為“框架”其實(shí)并不是很準(zhǔn)確降淮。更準(zhǔn)確的應(yīng)該稱為“中間件”。我的理解是因?yàn)樗麄冸m然是集成到自己的項(xiàng)目代碼中搏讶,但是他們卻占用獨(dú)立的端口佳鳖。
spring目前在java開發(fā)中的地位很高,使用spring來管理bean是非常流行的做法媒惕。更重要的是非常方便系吩。對(duì)于中間件來說,通過寥寥幾行xml的描述就能將一個(gè)復(fù)雜的bean實(shí)例化出來妒蔚,而且耦合度很低穿挨,何樂而不為呢?看一個(gè)sonsumer的配置:
<bean id="globalInterceptor1" class="org.jupiter.example.spring.interceptor.consumer.MyGlobalConsumerInterceptor1" />
<bean id="globalInterceptor2" class="org.jupiter.example.spring.interceptor.consumer.MyGlobalConsumerInterceptor2" />
<jupiter:client id="jupiterClient" registryType="default">
<jupiter:property registryServerAddresses="127.0.0.1:20001" />
<jupiter:property globalConsumerInterceptors="globalInterceptor1,globalInterceptor2" />
<!-- 可選配置 -->
<!--
String registryServerAddresses // 注冊(cè)中心地址 [host1:port1,host2:port2....]
String providerServerAddresses // IP直連到providers [host1:port1,host2:port2....]
ConsumerInterceptor[] globalConsumerInterceptors; // 全局?jǐn)r截器
-->
<!-- 網(wǎng)絡(luò)層配置選項(xiàng) -->
<jupiter:netOptions>
<jupiter:childOption SO_RCVBUF="8192" />
<jupiter:childOption SO_SNDBUF="8192" />
<jupiter:childOption ALLOW_HALF_CLOSURE="false" />
</jupiter:netOptions>
</jupiter:client>
<bean id="interceptor1" class="org.jupiter.example.spring.interceptor.consumer.MyConsumerInterceptor1" />
<bean id="interceptor2" class="org.jupiter.example.spring.interceptor.consumer.MyConsumerInterceptor2" />
<!-- consumer -->
<jupiter:consumer id="serviceTest" client="jupiterClient" interfaceClass="org.jupiter.example.ServiceTest">
<!-- 以下都選項(xiàng)可不填 -->
<!-- 服務(wù)版本號(hào), 通常在接口不兼容時(shí)版本號(hào)才需要升級(jí) -->
<jupiter:property version="1.0.0.daily" />
<!-- 序列化/反序列化類型: (proto_stuff, hessian, kryo, java)可選, 默認(rèn)proto_stuff -->
<jupiter:property serializerType="proto_stuff" />
<!-- 軟負(fù)載均衡類型[random, round_robin] -->
<jupiter:property loadBalancerType="round_robin" />
<!-- 派發(fā)方式: (round, broadcast)可選, 默認(rèn)round(單播) -->
<jupiter:property dispatchType="round" />
<!-- 調(diào)用方式: (sync, async)可選, 默認(rèn)sync(同步調(diào)用) -->
<jupiter:property invokeType="sync" />
<!-- 集群容錯(cuò)策略: (fail_fast, fail_over, fail_safe)可選, 默認(rèn)fail_fast(快速失敗) -->
<jupiter:property clusterStrategy="fail_over" />
<!-- 在fail_over策略下的失敗重試次數(shù) -->
<jupiter:property failoverRetries="2" />
<!-- 超時(shí)時(shí)間設(shè)置 -->
<jupiter:property timeoutMillis="3000" />
<jupiter:methodSpecials>
<!-- 方法的單獨(dú)配置 -->
<jupiter:methodSpecial methodName="sayHello" timeoutMillis="5000" clusterStrategy="fail_fast" />
</jupiter:methodSpecials>
<jupiter:property consumerInterceptors="interceptor1,interceptor2" />
<!-- 可選配置 -->
<!--
SerializerType serializerType // 序列化/反序列化方式
LoadBalancerType loadBalancerType // 軟負(fù)載均衡類型[random, round_robin]
long waitForAvailableTimeoutMillis = -1 // 如果大于0, 表示阻塞等待直到連接可用并且該值為等待時(shí)間
InvokeType invokeType // 調(diào)用方式 [同步, 異步]
DispatchType dispatchType // 派發(fā)方式 [單播, 廣播]
long timeoutMillis // 調(diào)用超時(shí)時(shí)間設(shè)置
List<MethodSpecialConfig> methodSpecialConfigs; // 指定方法的單獨(dú)配置, 方法參數(shù)類型不做區(qū)別對(duì)待
ConsumerInterceptor[] consumerInterceptors // 消費(fèi)者端攔截器
String providerAddresses // provider地址列表, 逗號(hào)分隔(IP直連)
ClusterInvoker.Strategy clusterStrategy; // 集群容錯(cuò)策略
int failoverRetries // fail_over的重試次數(shù)
-->
</jupiter:consumer>
對(duì)于一個(gè)相對(duì)比較成熟的rpc中間件來說肴盏,核心的bean配置是比較復(fù)雜的科盛。你看看其中的參數(shù)就知道。通過spring的這種xml描述文件起碼能夠稍微容易地理解到一個(gè)bean需要哪些參數(shù)菜皂,哪些可以不要贞绵,同時(shí)根據(jù)xsd的約束能夠讓開發(fā)者更清楚的知道自己的配置有什么問題。如果不給api文檔的情況下干巴巴的給你一個(gè)類恍飘,讓你去實(shí)例化這個(gè)復(fù)雜的class榨崩,我相信很多人都會(huì)抓狂谴垫。在這個(gè)配置文件中很容易的看出要有2個(gè)節(jié)點(diǎn):client和consumer。子節(jié)點(diǎn)的內(nèi)容就是參數(shù)蜡饵。consumer會(huì)去引用client去執(zhí)行一個(gè)請(qǐng)求弹渔。而我們的業(yè)務(wù)中直接去調(diào)用consumer就完事了。如此而已溯祸,簡(jiǎn)單直觀肢专。
這里的spring xml配置使用的是自定義的標(biāo)簽,算是對(duì)spring的拓展焦辅。不僅是jupiter博杖,基本上大多數(shù)rpc中間件都實(shí)現(xiàn)了自己的一套標(biāo)簽,似乎不去自己實(shí)現(xiàn)一套自定義標(biāo)簽都不好意思開源筷登。比如dubbo的自定義標(biāo)簽就是<dubbo:xxx>
剃根,motan類似如此。然而實(shí)際上也不是必須得實(shí)現(xiàn)自定義標(biāo)簽前方,使用spring的bean也是可以的狈醉,只不過顯得很臃腫,不是那么直觀罷了惠险。
對(duì)于一個(gè)新手來講苗傅,這些東西顯得格外的高大上。其實(shí)里面沒有什么黑魔法班巩,在spring的reference中對(duì)自定義標(biāo)簽有介紹渣慕。感興趣的去看看這個(gè)官方文檔:spring xml extension.
要實(shí)現(xiàn)一個(gè)自定義的spring xml標(biāo)簽需要做一下幾個(gè)步驟:
- 定義一個(gè)約束文件,用來規(guī)范xml的內(nèi)容”Щ牛現(xiàn)在都流行使用xsd去編寫約束文件逊桦,dtd已經(jīng)成為老古董了。xsd了解一下.
- 自定義一個(gè)
NamespaceHandler
的實(shí)現(xiàn)抑进。實(shí)際上是去實(shí)現(xiàn)這個(gè)接口强经。非常容易,復(fù)制粘貼一把梭寺渗。 - 寫一個(gè)或者多個(gè)
BeanDefinitionParser
的實(shí)現(xiàn)匿情。也是去實(shí)現(xiàn)接口,當(dāng)然繼承抽象類也是ok的户秤。這個(gè)是最核心的內(nèi)容码秉。 - 將上面所定義的全部注冊(cè)到spring中,讓spring知道有這些玩意兒鸡号。也就是在
META-INF
文件夾下新增兩個(gè)配置文件:spring.handlers
和spring.schemas
下面就結(jié)合jupiter中自定義的spring標(biāo)簽來談?wù)勊侨绾螌?shí)現(xiàn)的转砖。
首先得定義xsd約束文件,完整的定義在這里.這個(gè)沒什么好說的,枯燥的xml定義罷了。無非就是定義有哪些元素府蔗,哪些元素下有哪些屬性晋控,其中有沒有子元素,屬性類型是什么姓赤,是不是必填的等等赡译。
接下來就是配置一個(gè)handler
。這個(gè)handler用來解析自定義的標(biāo)簽不铆。用過spring都知道蝌焚,除了最常見的bean標(biāo)簽還有很多其他的標(biāo)簽,比如<context:component-scan>
誓斥、<aop:aspectj-autoproxy proxy-target-class="true" />
以及<mvc:annotation-driven/>
等只洒。這些標(biāo)簽和bean標(biāo)簽的不同之處在于都有一個(gè)前綴。我們稱這個(gè)叫做命名空間劳坑。然而自定義的當(dāng)然也得加上命名空間毕谴。雖說不能和bean平起平坐,但是和aop距芬、context這樣的標(biāo)簽還是可以一視同仁的涝开。
基于這種思路,那就很容易來自定義自己的標(biāo)簽了框仔。難怪文檔中對(duì)這個(gè)步驟加了一個(gè)說明:
Coding a custom NamespaceHandler implementation (this is an easy step, don’t worry).
的確如此舀武,常人的思路就是照著spring的實(shí)現(xiàn)抄一把。如此簡(jiǎn)單存和!
而比較復(fù)雜的就是對(duì)BeanDefinitionParser
的實(shí)現(xiàn)了奕剃。這個(gè)是最核心的步驟衷旅。根據(jù)文檔中的描述捐腿,這個(gè)可以有一個(gè)或者多個(gè)。但是在jupiter中只定義了一個(gè)實(shí)現(xiàn)柿顶。
public class JupiterNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("server", new JupiterBeanDefinitionParser(JupiterSpringServer.class));
registerBeanDefinitionParser("client", new JupiterBeanDefinitionParser(JupiterSpringClient.class));
registerBeanDefinitionParser("provider", new JupiterBeanDefinitionParser(JupiterSpringProviderBean.class));
registerBeanDefinitionParser("consumer", new JupiterBeanDefinitionParser(JupiterSpringConsumerBean.class));
}
}
spring的TaskNamespaceHandler
中就使用了多個(gè)paser:
public class TaskNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
this.registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
this.registerBeanDefinitionParser("executor", new ExecutorBeanDefinitionParser());
this.registerBeanDefinitionParser("scheduled-tasks", new ScheduledTasksBeanDefinitionParser());
this.registerBeanDefinitionParser("scheduler", new SchedulerBeanDefinitionParser());
}
}
這個(gè)paser用通俗的話來解釋就是將在xml的配置參數(shù)給set到相應(yīng)的實(shí)例中去茄袖。舉個(gè)栗子:
public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
protected Class getBeanClass(Element element) {
return SimpleDateFormat.class;
}
protected void doParse(Element element, BeanDefinitionBuilder bean) {
// this will never be null since the schema explicitly requires that a value be supplied
String pattern = element.getAttribute("pattern");
bean.addConstructorArg(pattern);
// this however is an optional property
String lenient = element.getAttribute("lenient");
if (StringUtils.hasText(lenient)) {
bean.addPropertyValue("lenient", Boolean.valueOf(lenient));
}
}
}
這個(gè)栗子是繼承自AbstractSingleBeanDefinitionParser
并沒有去實(shí)現(xiàn)BeanDefinitionParser
接口。道理都知道嘁锯,沒有必要去實(shí)現(xiàn)一個(gè)要啥沒啥的接口宪祥,吃現(xiàn)成的就好。重寫父類的getBeanClass
方法家乘,將需要納入spring管理的對(duì)象返回掉蝗羊。這里不僅僅可以重寫這個(gè)方法,還有其他例如getBeanClassName
也行仁锯。值得注意的是如果采用繼承抽象類的方式耀找,這兩個(gè)方法必須選擇一個(gè)來重寫。這個(gè)也非常容易理解,因?yàn)檫@個(gè)方法返回的class實(shí)例或者類的全路徑名就是用來實(shí)例化的對(duì)象野芒。如果通過實(shí)現(xiàn)接口的方式來定義paser就不需要考慮這個(gè)規(guī)則了蓄愁,只需要?jiǎng)?chuàng)建出BeanDefinition
的實(shí)例即可。jupiter中就是采用實(shí)現(xiàn)接口的方式狞悲,因?yàn)槔^承抽象類有一定的局限性撮抓,實(shí)現(xiàn)接口會(huì)有更多的靈活性。
有了要煮飯的鍋摇锋,就差下鍋的米了丹拯。這個(gè)栗子中重寫父類的doParser
方法。從代碼的表現(xiàn)上來看實(shí)際上就是將xml配置文件中的屬性獲取到荸恕,然后做一下檢查放到實(shí)例化的對(duì)象中去咽笼。當(dāng)然這里沒有那么直接,這里使用的是BeanDefinitionBuilder
來操作的戚炫。這只是最簡(jiǎn)單的實(shí)現(xiàn)剑刑。
復(fù)雜的parser都是自己去實(shí)現(xiàn)接口的。比如jupiter:
public class JupiterBeanDefinitionParser implements BeanDefinitionParser {
private final Class<?> beanClass;
public JupiterBeanDefinitionParser(Class<?> beanClass) {
this.beanClass = beanClass;
}
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
if (beanClass == JupiterSpringServer.class) {
return parseJupiterServer(element, parserContext);
} else if (beanClass == JupiterSpringClient.class) {
return parseJupiterClient(element, parserContext);
} else if (beanClass == JupiterSpringProviderBean.class) {
return parseJupiterProvider(element, parserContext);
} else if (beanClass == JupiterSpringConsumerBean.class) {
return parseJupiterConsumer(element, parserContext);
} else {
throw new BeanDefinitionValidationException("Unknown class to definition: " + beanClass.getName());
}
}
}
jupiter的自定義parser中需要納入spring管理的bean class對(duì)象是通過構(gòu)造器傳進(jìn)來的双肤。根據(jù)不同的class來作不同的處理施掏。其中具體的邏輯很枯燥無味,就不再細(xì)細(xì)探討了茅糜。不過我在看源碼的過程中發(fā)現(xiàn)了一個(gè)細(xì)節(jié)的地方七芭,也是值得注意的地方。
JupiterSpringConsumerBean
不僅僅和其他(如JupiterSpringServer
等)實(shí)現(xiàn)InitializingBean
蔑赘,還實(shí)現(xiàn)了一個(gè)叫做FactoryBean
的接口狸驳。這說明了一個(gè)問題,這個(gè)bean不是普通的bean缩赛,而是一個(gè)factory bean耙箍。相信很多人都會(huì)疑惑factory bean 和bean factory有什么區(qū)別。要我說兩者都沒有直接的聯(lián)系酥馍,如果在面試的時(shí)候有人問我這個(gè)問題辩昆,我一定直接懟回去:雷鋒和雷峰塔有什么區(qū)別?言歸正傳旨袒,這個(gè)factory bean本質(zhì)上也是bean汁针,但是與其他bean不同的是這個(gè)bean在spring容器中獲取的方式和別的不一樣。通常在spring中獲取一個(gè)bean采用ctx.getBean(xxx.class)
方法砚尽。通過這個(gè)方法獲取的factory bean并不是他自己施无,而是它的某個(gè)成員”毓拢可以看看這個(gè)接口的定義:
public interface FactoryBean<T> {
T getObject() throws Exception;
Class<?> getObjectType();
boolean isSingleton();
}
也就是說返回的對(duì)象是getObject()
返回值猾骡。那么這個(gè)接口存在的意義是什么呢?我也不復(fù)制粘貼了,覺著這篇文章寫得很不錯(cuò)卓练,淺顯易懂隘蝎。那么如何獲取這個(gè)bean本身呢?干嘛想著獲取它本身襟企,簡(jiǎn)直是無聊嘱么!也有方法,加個(gè)前綴"&"就行了(ctx.getBean("&sb")
)顽悼。
最后呢曼振,就是照著spring的官方文檔抄一下配置文件。依葫蘆畫瓢蔚龙,非常簡(jiǎn)單冰评。
完成了以上的幾個(gè)步驟,自定義的spring xml標(biāo)簽就大功告成了木羹。接下來要做的就是去使用自定義的標(biāo)簽甲雅。
<?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:myns="http://www.mycompany.com/schema/myns"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.mycompany.com/schema/myns http://www.mycompany.com/schema/myns/myns.xsd">
<!-- as a top-level bean -->
<myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/>
<bean id="jobDetailTemplate" abstract="true">
<property name="dateFormat">
<!-- as an inner bean -->
<myns:dateformat pattern="HH:mm MM-dd-yyyy"/>
</property>
</bean>
</beans>
這里是抄的官方文檔的栗子。標(biāo)簽myns:dateformat
實(shí)際上定義了一個(gè)SimpleDateFormat
的bean實(shí)例坑填。在spring容器加載的時(shí)候這個(gè)實(shí)例就回被初始化抛人。在使用自定義的標(biāo)簽的時(shí)候,需要注意的是得聲明好命名空間和指定location脐瑰,不然會(huì)報(bào)無法找到這個(gè)標(biāo)簽的錯(cuò)誤妖枚。其實(shí)這些東西照著抄就行了,只是不要忘記了或者抄錯(cuò)了苍在。
自定義spring xml標(biāo)簽如此簡(jiǎn)單绝页。無非就是照著文檔抄一把,自己再改吧改吧萬事就大吉了寂恬。對(duì)于其中核心的東西實(shí)際上還是一知半解续誉,比方說BeanDefinition
的具體實(shí)現(xiàn)原理等。上層的封裝太抽象了掠剑,留給開發(fā)者的僅僅只是一個(gè)需要實(shí)現(xiàn)的方法屈芜。要想知道為什么要這樣做郊愧,還得去研究spring的源碼朴译。
rpc中的最簡(jiǎn)單的一個(gè)可選模塊就這樣簡(jiǎn)單的實(shí)現(xiàn)了。這是一小步属铁,也是一大步眠寿。接下來會(huì)繼續(xù)探索稍微核心一點(diǎn)的jupiter實(shí)現(xiàn)。