書接上回,本回書我們要完成開放接口平臺(tái)核心引擎的多Handler支持機(jī)制。
如圖1所示。
裝飾者模式貌似是一個(gè)實(shí)現(xiàn)的候選,類似Java的I/O實(shí)現(xiàn)。
多“裝飾”一層,就獲得了新的功能,原來的功能還在。
對(duì)我現(xiàn)在的應(yīng)用場(chǎng)景來說,這種實(shí)現(xiàn)方式過于復(fù)雜了。
相對(duì)而言,F(xiàn)ilter更簡(jiǎn)潔。
當(dāng)前的應(yīng)用場(chǎng)景對(duì)性能是有極高要求的,不適合使用哪怕稍微復(fù)雜的模式。
我的Handler接口定義如下。
public interface Handler { public void inWay(HttpServletRequest request,HttpServletResponse response); public void outWay(HttpServletRequest request,HttpServletResponse response);}
更有節(jié)操的童鞋會(huì)自己定義Request、Response0,甚至Context對(duì)象。以便脫離Web容器的限制,進(jìn)一步實(shí)現(xiàn)自己的底層通信協(xié)議。
我這里先偷個(gè)懶,等有時(shí)間了慢慢來。
Handler接口中,inWay方法對(duì)應(yīng)圖1左側(cè)的向下箭頭,outWay對(duì)應(yīng)右側(cè)的向上箭頭。
這樣,在同一個(gè)Handler定義進(jìn)、出的邏輯。
對(duì)于實(shí)現(xiàn)序列化功能的Handler,inWay中實(shí)現(xiàn)反序列化,outWay中實(shí)現(xiàn)序列化。
對(duì)于實(shí)現(xiàn)加密功能的Handler,inWay中實(shí)現(xiàn)解密,outWay中實(shí)現(xiàn)加密。
對(duì)于實(shí)現(xiàn)壓縮功能的Handler,inWay中實(shí)現(xiàn)解壓縮的邏輯,outWay中實(shí)現(xiàn)壓縮的邏輯。
這樣,當(dāng)不需要某個(gè)Handler的時(shí)候,直接去掉就好了。
當(dāng)然,outWay中可以do nothing。
另外,非常重要的,Handler實(shí)現(xiàn)類不可以有自己的屬性。Handler實(shí)例不能有“狀態(tài)”。
我們需要Handler是線程安全的。
多個(gè)Handler是可配置的,每個(gè)Handler鏈可以服務(wù)于一個(gè)或多個(gè)接口。
在Handler無狀態(tài)、線程安全的基礎(chǔ)上,我們可以采用在每個(gè)JVM中Handler單例的方式,避免頻繁創(chuàng)建、回收Handler對(duì)象的損失。
配置信息可以保存在properties文件中,可以保存在xml中,可以保存在數(shù)據(jù)中,范例如下:
openapi.handler.keys=surface,encrypt,authopenapi.handler.surface=cn.hailong.common.openapi.handler.SurfaceHandleropenapi.handler.encrypt=cn.hailong.common.openapi.handler.EncryptHandleropenapi.handler.auth=cn.hailong.common.openapi.handler.AuthHandler
配置系統(tǒng)中可能用到的所有Handler,并在系統(tǒng)啟動(dòng)時(shí)加載。
在上面的配置中,配置了每個(gè)Handler的類名,加載的時(shí)候,可以根據(jù)類名創(chuàng)建類的實(shí)例。
給每個(gè)Handler起了一個(gè)短名稱,便于在配置Handler鏈的時(shí)候引用。
對(duì)應(yīng)的加載代碼為:
public class HandlerManager { /** * 保存系統(tǒng)中的所有Handler,key為handler短名,value為Handler實(shí)例。 */ private static Map<String, Handler> handlersMap = new ConcurrentHashMap<String, Handler>(); static{ reloadHandlers(); } public static synchronized void reloadHandlers(){ handlersMap.clear(); logger.info("Open Api Handlers load start ... "); long begin = System.currentTimeMillis(); Properties props = ConfigManager.getProperties("openapi"); String handlerKeys = props.getProperty("openapi.handler.keys"); logger.debug("loading handlers : "+handlerKeys); Handler handler = null; if(!StringUtils.isEmpty(handlerKeys)){ String[] handlerKeyArray = handlerKeys.split(","); if(handlerKeyArray!=null && handlerKeyArray.length>0){ for (String handlerKey : handlerKeyArray) { String propertiesKey = "openapi.handler."+handlerKey; String handlerClassName = props.getProperty(propertiesKey); if(StringUtils.isEmpty(handlerClassName)){ continue; } handlerClassName = handlerClassName.trim(); Class<?> clz = Class.forName(className); handler = BeanUtil.newInstance(Handler.class,clz); if(handler!=null){ handlersMap.put(handlerKey,handler); logger.debug(String.format("handler[%s] loaded : %s ", handlerKey,handler)); } } } } logger.info("Open Api Handlers load end , time costs "+(System.currentTimeMillis()-begin)); } /** * */ public static Hanlder get(String shortName){ return handlerMap.get(shortName); }}
修改了Handler的配置,需要重新加載的時(shí)候,也無需重啟服務(wù)器(在生產(chǎn)環(huán)境,這非常重要),再次調(diào)用HandlerManager.reloadHandlers()即可。
接下來是Handler鏈的配置,配置范例如下:
openapi.handler.chain.keys=full,idleopenapi.handler.chain.full=surface,encrypt,auth,traffic,config,validateopenapi.handler.chain.idle=idle,idle,idle,idle,idle
加載邏輯與HandlerManager中代碼邏輯類似。
Handler的執(zhí)行過程如圖1所示。
public class HandlerChain { protected List<Handler> handlersList = null; protected List<Handler> handlersReversedList = null; protected Iterator<Handler> inIterator = null; protected Iterator<Handler> outIterator = null; public HandlerChain(List<Handler> handlers) { setHandlers(handlers); reset(); } protected void setHandlers(List<Handler> handlers) { if (handlers == null) { return; } // 正向 this.handlersList = handlers; // 反向 if (handlersReversedList == null) { handlersReversedList = new ArrayList<Handler>(); } else { handlersReversedList.clear(); } for (int idx = handlers.size() - 1; idx > -1; --idx) { handlersReversedList.add(handlers.get(idx)); } } public void reset() { if (handlersList != null) { inIterator = handlersList.iterator(); } if (handlersReversedList != null) { outIterator = handlersReversedList.iterator(); } } public void inWay(HttpServletRequest request, HttpServletResponse response) { Handler nextHandler = null; if (inIterator != null && inIterator.hasNext()) { nextHandler = inIterator.next(); } if (nextHandler != null) { nextHandler.inWay(request, response); this.inWay(request, response);//遞歸調(diào)用 } else { logger.debug(String.format("In End Time:%s.",(System.currentTimeMillis() - this.time))); } } public void outWay(HttpServletRequest request, HttpServletResponse response) { Handler nextHandler = null; if (outIterator != null && outIterator.hasNext()) { nextHandler = outIterator.next(); } if (nextHandler != null) { nextHandler.outWay(request, response); this.outWay(request, response); } else { logger.debug(String.format("Out End Time:%s.",(System.currentTimeMillis() - this.time))); } }}
HandlerChain 是有狀態(tài)的,對(duì)每個(gè)請(qǐng)求創(chuàng)建一個(gè)實(shí)例。
調(diào)用 handlerChainInstance.inWay(req,resp)則執(zhí)行了相應(yīng)Handler鏈的所有inWay方法。
調(diào)用 handlerChainInstance.outWay(req,resp)則執(zhí)行了相應(yīng)Handler鏈的所有outWay方法。
對(duì)接口的調(diào)用類似對(duì)方法的調(diào)用,傳入?yún)?shù)包括:接口名稱、參數(shù)值,傳出參數(shù)可能是返回值,可能是異常消息。
定義如下接口。
interface RpcMessage{ Object getMeta(); /** * @return 調(diào)用接口的名稱。 */ String getMethod(); /** * @return 傳入的參數(shù)值。 */ List<Object> getParams(); /** * @return 返回值。 */ Object getResult(); /** * @return 異常信息。 */ Object getError();}
Meta中包含可能的調(diào)用者信息,授權(quán)信息等。
對(duì)于這樣的數(shù)據(jù)結(jié)構(gòu),json-rpc(http://json-rpc.org/wiki/specification)是一個(gè)簡(jiǎn)單易用的序列化方案。
雖然解析效率不高,但json足夠簡(jiǎn)單,作為參考實(shí)現(xiàn)是個(gè)好選擇。在生產(chǎn)環(huán)境中,需要有更成熟的考慮。
采用json-rpc作為持久化方案的情況下。
請(qǐng)求信息可能如下所示:
{meta:{token:'765959559266'},method:'getUserInfo',params:['32899688']}
返回信息可能如下所示:
{result:{name:'劉海龍',addr:'人民大學(xué)北路'}}
或
{error:{code:'AUTH_ERR',msg:'Token過期'}}
對(duì)于每次請(qǐng)求應(yīng)該創(chuàng)建一個(gè) RpcMessage 的實(shí)例,可以保存在 Request 中,或者 ThreadLocal 中。
現(xiàn)在需要考慮一個(gè)稍復(fù)雜的問題。
觀察圖1,考慮各個(gè)環(huán)節(jié)拋出異常的時(shí)候應(yīng)該如何應(yīng)對(duì)。考慮授權(quán)失敗,或超出流量控制時(shí)應(yīng)該如何應(yīng)對(duì)。
首先明確一個(gè)問題,客戶端是按照outWay的配置定制的。
outWay中如果有序列化和加密,客戶端就會(huì)解密和反序列化。
生產(chǎn)環(huán)境采用的序列化方案一般是二進(jìn)制的,開發(fā)環(huán)境可能采用JSON或XML。
加解密一般會(huì)針對(duì)不同用戶采用不同的Key。
返回的消息如果沒有經(jīng)過特定格式的序列化或者加密,客戶端將無法讀取消息。
所以,我們得到的第一個(gè)結(jié)論是:異常消息也要報(bào)所有的Handler的outWay方法走一遍。
接下來,對(duì)圖1流程中的每個(gè)環(huán)節(jié)逐個(gè)考慮。
通過如下代碼可以捕捉到inWay過程中的異常,并保存到 rpcMessage 實(shí)例中。
try{ handlerChainInstance.inWay(req,resp)}catch(Throwable e){ rpcMessage.addError("PRE_INVOKE_ERR",e.getMessage());}
接下來,如果inWay中出現(xiàn)了異常,則跳過調(diào)用 業(yè)務(wù)邏輯 對(duì)象的代碼。直接執(zhí)行 outWay ,在outWay執(zhí)行過程中,根據(jù)配置,該怎么序列化怎么序列化,該怎么加密怎么加密。
有一種情況是這樣的,假如加密需要明確客戶身份,才知道用哪個(gè)Key,但請(qǐng)求中未包含用戶身份信息或者用戶身份信息無效,這可怎么辦?
為了解決這個(gè)問題,按如下兩點(diǎn)做:
第一,用戶身份標(biāo)識(shí)不要在消息體中傳遞。否則識(shí)別不了身份,用什么Key解密都不知道,讀不出來。
第二,身份無效的錯(cuò)誤消息,定義特殊代碼,明文傳??蛻舳嗽趫?zhí)行所有Handler之前,先判斷是不是這個(gè)錯(cuò)誤。
這個(gè)最簡(jiǎn)單,捕捉住,放到 rpcMessage 就可以了。
然后該怎么走outWay就怎么走。
這個(gè)最難處理。
應(yīng)在開發(fā)過程中極力排除,并避免。
萬一發(fā)生了,比如在加密、壓縮過程中異常了,這時(shí)候也只能返回預(yù)定的消息,告訴調(diào)用方:“服務(wù)器出錯(cuò)了,客官請(qǐng)聯(lián)系店小二”。
預(yù)定義的消息,根據(jù) Handler鏈的配置自動(dòng)生成,代碼如下所示。
private static byte[] outWayErrorMessage = null;outWayErrorMessage = buildResponseErrorMessage(handerList,"POST_ERR","服務(wù)器出錯(cuò)了,客官請(qǐng)聯(lián)系店小二");
聯(lián)系客服