Netty + ProtoBuf使用反射實現多種類型的傳輸方式
本文是該文章的後續(https://www.cnblogs.com/Binhua-Liu/p/5577622.html),該文章介紹了在ProtoBuf上再加一層Header從來歸避ProtoBuf在Netty框架下只能有一有對象的局限。 在蘑菇街開源的TeamTalk中,也是加了一個Header,根據Header中的操作類型,做出相應的邏輯處理操作。
下面是我們的配置文件,用於實現ServerCustomDecodeHandler中的decodeBody的邏輯。ProtoBuf通過反射生成對象需要使用“包$類”的形式。
<?xml version="1.0" encoding="UTF-8"?> <decodetypes> <decodetype type="com.zywj.protobuf.out.IMBase$MessageCmdID" val="CID_MSG_DATA_VALUE" class="com.zywj.protobuf.out.IMMessage$MsgData" /> <decodetype type="com.zywj.protobuf.out.IMBase$OtherCmdID" val="CID_OTHER_HEARTBEAT_VALUE" class="com.zywj.protobuf.out.IMOther$HeartBeat"/> </decodetypes>
讀取XML的工作放在Server中,聲明為private static。這裏是根據我自己的邏輯生成的一個Map變量。
private static Map parseXmlData(String xmlFilePath){ SAXReader reader = new SAXReader(); Document doc = null; try { doc = reader.read(new File(xmlFilePath)); } catch (DocumentException e) { e.printStackTrace(); } Element root= doc.getRootElement(); Map<String,Map> map = new HashMap<>(); for(Iterator i_action=root.elementIterator();i_action.hasNext();){ Element e_action = (Element)i_action.next(); Iterator it = e_action.attributeIterator(); Attribute key = (Attribute) it.next(); String type = key.getValue(); Map<String, String> inner = new HashMap<>(); while(it.hasNext()) { Attribute attribute = (Attribute) it.next(); inner.put(attribute.getName(), attribute.getValue()); } map.put(type, inner); } return map; }
ServerCustomDecodeHandler的decodeBody的工作是根據Map中的數據通過反射生成對象,這裏需要判斷當前對象的enum值是否是與commandId相等,如果找到,就生成相對應的ProtoBuf對象。
public MessageLite decodeBody(short commandId, byte[] array, int offset, int length) throws Exception { Map<String, Map> decodeMap = Server.decodeMap; for (Map.Entry<String, Map> entry : decodeMap.entrySet()) { Map<String, String> m = decodeMap.get(entry.getKey()); Class clazz = Class.forName(entry.getKey()); Field f = clazz.getField(m.get("val")); if ((int)f.get(clazz) == commandId) { Class cls = Class.forName(m.get("class")); Method method = cls.getMethod("getDefaultInstance"); Object obj = method.invoke(cls, null); method = cls.getMethod("getParserForType"); obj = method.invoke(obj, null); return ((Parser<MessageLite>) obj).parseFrom(array, offset, length); } } return null; }
到這裏,還沒完,因為ServerHandler裏面傳進來的是Object msg,所以還要用instanceof判斷該Object到底是ProtoBuf的對象。所以又是一個XML,再反射。
<?xml version="1.0" encoding="UTF-8"?> <msgtypes> <msgtype type="com.zywj.protobuf.out.IMMessage$MsgData" class="com.zywj.server.LogicHandler" method="handleMsgData" /> <msgtype type="com.zywj.protobuf.out.IMOther$HeartBeat" class="com.zywj.server.LogicHandler" method="handleHeartBeat" /> </msgtypes>
ServerHanlder中的channelRead0改成
@Override public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { Map<String, Map> dispatchMap = Server.dispatchMap; for (Map.Entry<String, Map> entry : dispatchMap.entrySet()) { Class clazz = Class.forName(entry.getKey()); if (msg.getClass().isAssignableFrom(clazz)) { Map<String, String> m = dispatchMap.get(entry.getKey()); Class clazzLogic = Class.forName(m.get("class")); Method handle = clazzLogic.getDeclaredMethod(m.get("method"), ChannelHandlerContext.class, Object.class); handle.invoke(clazzLogic.newInstance(), ctx, msg); break; } } }
SeverCustomEncodeHandler也是相同方法,不再贅述。
到這裏,我們利用反射,把上面文章中過多的if...else...歸避了。
最後,我覺得這樣實現會有過多的反射(循環反射再判斷,時間復雜度O(n)),還有就是代碼量其實並沒有減少,只是轉移到XML中去,最大的優點是降低耦合吧。
Netty + ProtoBuf使用反射實現多種類型的傳輸方式