新聞中心
1 背景介紹
架構(gòu)組方案如下所示:
右上圖展示了要轉(zhuǎn)換代碼需要填寫(xiě)的信息,左上圖展示了整個(gè)接口調(diào)用所需要的步驟,依次總共需要8步。

架構(gòu)組這套方案是有缺陷的:
1.步驟繁瑣,耗時(shí),溝通成本高。
2.無(wú)法解析泛型,需另開(kāi)發(fā)一個(gè)不含有泛型的接口,比如XXXforPHP。
3.一個(gè)接口涉及多個(gè)服務(wù)調(diào)用,本地調(diào)試時(shí)無(wú)法同時(shí)調(diào)試線上和沙箱服務(wù)。
安居客方案如下:
右上圖展示了代碼轉(zhuǎn)化工具的使用方法,左上圖展示了實(shí)現(xiàn)接口調(diào)用的整個(gè)步驟,共需7步。
從架構(gòu)組方案迭代到安居客方案,溝通成本相對(duì)減少,但是也是有其缺陷的:
1.步驟還是相對(duì)比較復(fù)雜,比較耗時(shí)。
2.遇到泛型需要去詢問(wèn)服務(wù)方是何種泛型,不能自動(dòng)解析。
3.一個(gè)接口涉及多個(gè)服務(wù)調(diào)用,本地調(diào)試時(shí)無(wú)法同時(shí)調(diào)試線上和沙箱服務(wù)。
4.轉(zhuǎn)換代碼是一種離線模式,同時(shí)不能下載多個(gè)服務(wù)。
現(xiàn)在存在的問(wèn)題有以下幾點(diǎn):
1.步驟太多,既耗時(shí)開(kāi)發(fā)效率也低。
2.代碼轉(zhuǎn)換完以后需要自己將代碼拷貝到項(xiàng)目中的指定目錄(有很多同學(xué)因?yàn)槟夸浛截愬e(cuò)誤導(dǎo)致接口調(diào)不通)。
3.泛型不能自動(dòng)解析。
4.本地接口涉及多個(gè)服務(wù)時(shí),不能同時(shí)調(diào)試線上和沙箱服務(wù)。
2 重構(gòu)思想
從現(xiàn)有方案可以看出,現(xiàn)在PHP調(diào)用Java接口步驟是很繁瑣的,開(kāi)發(fā)效率偏低。 而且還有兩個(gè)問(wèn)題: 泛型無(wú)法解析、不便本地調(diào)試。 所以要進(jìn)行重構(gòu)來(lái)解決這些問(wèn)題。 重構(gòu)要圍繞下面三個(gè)方向進(jìn)行:
3 流程解析
圍繞現(xiàn)有問(wèn)題進(jìn)行了重構(gòu):
具體實(shí)現(xiàn)流程如下。
第一步:實(shí)現(xiàn)代碼工程化管理。
- 開(kāi)發(fā)PHPbase服務(wù)解決Java代碼下載問(wèn)題。
- 解析pom坐標(biāo)信息。根據(jù)坐標(biāo)拼接要下載jar包的URL,通過(guò)copyURLToFile將URL的信息下載到指定目錄下,也就是將服務(wù)的jar包下載下來(lái)。
- 解析下載的jar包。循環(huán)讀取jar包信息,碰到pom.xml文件讀取依賴信息,并下載。有的依賴version寫(xiě)在了父pom的dependency內(nèi),需要通過(guò)父pom讀取,還要兼顧version寫(xiě)在屬性中的情況。父pom的依賴信息也要下載下來(lái)。獲取到所有的jar包,放到一個(gè)URL數(shù)組里,然后通過(guò)URLClassLoader解析,碰到以.class結(jié)尾的文件進(jìn)行判斷是contract?entity?enum? 流程圖如下:
- 解析contract。對(duì)于注解中有serverContract的就是contract文件。通過(guò)反射拿到類中含有的方法名字,以及入?yún)⒑统鰠?。參?shù)的類型包括一些基本類型string、int、bool等還會(huì)有一些復(fù)雜類型map、list、set等,還可能是泛型。對(duì)于復(fù)雜類型map、list等類型會(huì)繼續(xù)分析子元素的類型信息。假如是map類型,那它的子元素信息就是key_type:XXX,value_type:XXX。如果子元素中key_type是個(gè)object,那么它的key_class就是這個(gè)object的包名。同樣,如果子元素的value_type是個(gè)object,那么它的value_class就是這個(gè)object的包名。對(duì)于復(fù)雜類型list、set等也是如此分析。如果是泛型,它的elem_type賦值為object,elem_class賦值為java.lang.object。將方法名字拼接一個(gè)hash值(方法名+入?yún)?出參)再拼接一個(gè)字符串params代表入?yún)?,以這個(gè)字段為key,以入?yún)⒌念愋托畔関alue放到變量paramVar中。同樣方法名字拼接一個(gè)hash值(方法名+入?yún)?出參)再拼接一個(gè)字符串return代表出參,以這個(gè)字段為key,以出參的類型信息為value也放到變量paramVar中。入?yún)⒑统鰠⒌木唧w信息可以看下圖。最后以contract類的包名信息為key,paramVar為value放入變量contractMap中。
- 解析entity。如果注解SCFSerializable=true and SCFMember=true,就是一個(gè)entity文件。通過(guò)SCFMember獲取到字段的信息:var、orderID、generic,將這些信息放入變量TSPEC中,key是orderId的值,value就是這些字段的信息。 對(duì)于字段類型分析同contract中類型分析是一致的,在此不贅述了。如果這個(gè)實(shí)體有繼承關(guān)系,要將繼承信息也放入變量TSPEC中。key是999,value是繼承信息。如果注解SCFSerializable.name不為空,將這個(gè)值也放入變量TSPEC中。key是998,value是注解SCFSerializable.name的值。最后以entity類的包名信息為key,TSPCE為value放入entityMap中。
- 解析enum。如果isEnum=true的就是枚舉文件。通過(guò)反射獲取字段信息也就是constant信息,放入enumMap中,key是枚舉類的包名,value是各個(gè)常量值。
- 返回結(jié)果。將contract、entity、enum這三個(gè)信息組裝在一起放入Json中作為結(jié)果返回,如下圖所示也就是最下面的這個(gè)圖。
- 開(kāi)發(fā)build.php來(lái)實(shí)現(xiàn)Java代碼到PHP代碼的轉(zhuǎn)化,解決現(xiàn)在代碼需要人工拷貝的問(wèn)題。流程圖如下:
從PHPbase拿到解析坐標(biāo)后的數(shù)據(jù),也就是那個(gè)JSON數(shù)據(jù),反解析獲取到三個(gè)包含contract、entity、enum的數(shù)組。
下面三個(gè)圖展示了三個(gè)數(shù)組的部分具體內(nèi)容:
第一個(gè)圖是截取的contract數(shù)組中的部分信息。key是一個(gè)完整的包名,value就是這個(gè)類中的所有函數(shù)信息。我們看下第一個(gè)方法,key是由方法名字+hash值+params字符串組成。value就是入?yún)⒌念愋托畔ⅲ梢钥吹接衧tring,有object。類型是object 的就有這個(gè)對(duì)象對(duì)應(yīng)的class信息。params是入?yún)ⅲ瑤в衦eturn的就是出參信息。
第二個(gè)圖是截取的entity數(shù)組中的部分信息。我們看到key是一個(gè)完整的包名,value是實(shí)體的字段信息。比如第一個(gè)字段排序是第一個(gè),不是泛型,類型是list,子元素類型是object,elem_class就是這個(gè)object的包名信息。key為998對(duì)應(yīng)的value就是Java中注解SCFSerializer.name的值
第三個(gè)圖是截取的enum數(shù)組中的部分信息。key是完整包名,value就是各個(gè)常量的值。
數(shù)組信息分析完,看下具體解析過(guò)程。contract數(shù)組解析流程圖如下所示:
拿到contract數(shù)組循環(huán)遍歷,對(duì)于類文件的目錄我們用的是包名+固定路徑“Libs/wscfcore/package”,為了防止和項(xiàng)目中已有的代碼相區(qū)別,我們將代碼放到了libs/wscfcore/package下。命名空間也是包名信息+剛才那個(gè)固定路徑,大家可以看下右上方這個(gè)圖。對(duì)于類文件的依賴,也是包括兩部分,一個(gè)是固定的依賴 “use libswscfcoreclient”,這個(gè)是因?yàn)轭愔械臉?gòu)造方法用到了scf client的實(shí)例化。依賴的另一部分就是參數(shù)中用到的引用。生成方法的時(shí)候要考慮兩個(gè)問(wèn)題,一個(gè)是重名問(wèn)題,一個(gè)是序列化問(wèn)題。
當(dāng)方法重名的時(shí)候我們用0,1來(lái)進(jìn)行區(qū)分,比如getInfo0,getInfo1。這里面有一個(gè)坑,就是要考慮下每次生成這個(gè)方法時(shí)的順序問(wèn)題,因?yàn)榉椒ㄖ孛锩娴膮?shù)個(gè)數(shù)是不一樣的,所以名字絕對(duì)不能變換順序。我們?yōu)榱朔乐拱l(fā)生順序變更問(wèn)題,在Java代碼生成的時(shí)候用到了一個(gè)hash值。就是將方法名、入?yún)ⅰ⒊鰠⒆隽艘粋€(gè)hash值,這個(gè)肯定是唯一的,通過(guò)這個(gè)來(lái)保證順序不變。
生成方法的時(shí)候還要考慮第二個(gè)問(wèn)題那就是序列化的問(wèn)題,我們看下右上方這個(gè)圖,對(duì)于第四個(gè)參數(shù)的類型是非基礎(chǔ)類型,所以這個(gè)類型序列化時(shí)對(duì)應(yīng)的typeID也就是hash值就從這個(gè)類的包名來(lái)獲取,通過(guò)在這建立全局?jǐn)?shù)組,在后邊生成typeID的時(shí)候獲取這個(gè)參數(shù)的包名信息。$params這個(gè)參數(shù)里面包含了lookup,methodName,以及入?yún)⑿畔ⅰ?/p>
entity數(shù)組解析流程圖如下所示:
生成實(shí)體的過(guò)程中也要生成目錄,命名空間,依賴。這些同contract的分析是一樣的,就不贅述了。實(shí)體的構(gòu)造方法我們看右上圖可以看出是對(duì)靜態(tài)變量TSPEC賦值的過(guò)程。這個(gè)變量是一個(gè)數(shù)組,里面包含了各個(gè)字段的信息,名字var,順序orderID,是不是泛型isGeneric,以及類型type。對(duì)于復(fù)雜類型list也要生成它的子元素的類型。這個(gè)類型信息在從服務(wù)器拿到二進(jìn)制流數(shù)據(jù)進(jìn)行反序列化的時(shí)候要用到,類型必須和其相同才能正確的解析出來(lái)。如果這個(gè)實(shí)體類有繼承,那么也要生成這個(gè)類的繼承類。
enum數(shù)組解析如下圖所示:
Enum的解析就簡(jiǎn)單些了。要生成目錄,命名空間,const信息。const信息遍歷enum數(shù)組即可。到這Java代碼到PHP代碼的轉(zhuǎn)化就完成了,并生成在了指定位置。我們?cè)僖膊挥檬謩?dòng)拷貝代碼了。避免了因?yàn)榭截惔a路徑出錯(cuò)導(dǎo)致的接口調(diào)不通問(wèn)題。
- 引入一個(gè)service.xml文件來(lái)管理服務(wù)版本信息,所有要下載的服務(wù)都寫(xiě)在這里,采用XML格式的文件,開(kāi)發(fā)同學(xué)直接將坐標(biāo)信息復(fù)制到這里即可,方便開(kāi)發(fā)同學(xué)使用。
第二步:解決泛型解析問(wèn)題。
Java同學(xué)是知道泛型對(duì)應(yīng)的具體是何種類型,就是他們開(kāi)發(fā)的實(shí)體中的某一個(gè),不過(guò)PHP同學(xué)是不知道的。但是這個(gè)泛型序列化以后的typeID值也就是hashcode值我們可以拿到,那么我們將所有實(shí)體做一個(gè)hash計(jì)算,然后我們反推是不是就可以知道泛型是哪個(gè)實(shí)體了呢?
所以優(yōu)化build.php文件,收集服務(wù)的實(shí)體和枚舉信息,進(jìn)行hash計(jì)算。我們現(xiàn)在有scfv1、scfv3兩種協(xié)議,不同協(xié)議對(duì)應(yīng)的typeID是不同的,所以針對(duì)這兩種協(xié)議進(jìn)行hash計(jì)算。對(duì)于scfv1協(xié)議,若實(shí)體中有scheme字段(998),那么對(duì)scheme字段進(jìn)行。若沒(méi)有,則對(duì)類名進(jìn)行hash計(jì)算。而對(duì)于scfv3協(xié)議是對(duì)整個(gè)包名進(jìn)行計(jì)算。有了這個(gè)hash文件就可以解析泛型了。
第三步:解決本地?zé)o法同時(shí)調(diào)試線上和沙箱服務(wù)的問(wèn)題。增加沙箱配置文件,方便開(kāi)發(fā)者進(jìn)行調(diào)試。文件如下圖所示:
服務(wù)名字和沙箱IP要準(zhǔn)確對(duì)應(yīng)上。底層獲取服務(wù)信息時(shí),判斷有此文件,拿到服務(wù)的信息就可以直接走沙箱服務(wù)了。對(duì)于不存在于此文件的服務(wù)是走線上的。如果將代碼放到沙箱環(huán)境,灰度的申請(qǐng)就可以用這個(gè)文件代替了,省去了申請(qǐng)的操作。
第四步:將步驟由8步減少為3步。
- 將Java的pom坐標(biāo)信息放入service.xml文件中。
- 執(zhí)行build文件,響應(yīng)信息如下圖所示。從service.xml中獲取坐標(biāo)信息,檢查是否已經(jīng)下載過(guò)該服務(wù)。若沒(méi)有下載過(guò),掉PHPbase拉取Java代碼,然后轉(zhuǎn)化為PHP代碼,并放到項(xiàng)目中的指定位置。
- 接口調(diào)用。運(yùn)用反射機(jī)制實(shí)例化調(diào)用類,然后再調(diào)用類中的方法。
下面這個(gè)圖是IHouseService類的內(nèi)容,可以看到實(shí)例化此類要傳入服務(wù)名serviceName,查找類lookup,分別對(duì)應(yīng)上圖中的hmc、HouseService
4.無(wú)痕調(diào)用
從集團(tuán)方案迭代到租房方案,調(diào)用步驟由原來(lái)的八步減少到了現(xiàn)在的三步。
租房方案的實(shí)現(xiàn)代碼的目錄結(jié)構(gòu)展示如下所示:
wscfcore這個(gè)目錄是包括了所有scf相關(guān)的文件。Hashdata這個(gè)目錄里面放的是hash文件。package.lock里面存儲(chǔ)了所有項(xiàng)目中目前已經(jīng)下載的服務(wù)坐標(biāo)信息,存的是字符串。package目錄里面存的是所有下載的代碼。Build文件是整個(gè)代碼的核心,根據(jù)坐標(biāo)拉取Java代碼然后再轉(zhuǎn)化為PHP代碼放在指定位置。service.xml文件是存放我們要下載的服務(wù)的pom坐標(biāo),采用xml格式方便大家直接將坐標(biāo)信息復(fù)制粘貼即可。wscfcore目錄會(huì)放到composer中供大家下載使用。
PHP同學(xué)用這套代碼,調(diào)用某個(gè)服務(wù)的接口時(shí),實(shí)現(xiàn)了PHP同學(xué)調(diào)Java接口就像Java同學(xué)調(diào)Java接口是一樣的,是一種無(wú)痕式的調(diào)用。
分享文章:一種支持泛型解析的PHPScf無(wú)痕化技術(shù)方案
URL分享:http://www.dlmjj.cn/article/dppoips.html


咨詢
建站咨詢
