博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
修改现有消息类让.net core项目支持Protobuf - 【无需使用 [ProtoBuf.ProtoContract] 的方法】
阅读量:4094 次
发布时间:2019-05-25

本文共 6205 字,大约阅读时间需要 20 分钟。

前言

小说搜索引擎 198200.com

第二次发博客,希望大家多多鼓励!!!

又接无上老板的一个需求,需要让.net core消息发送端跟消息接收端通信的消息是protobuf格式的(基于protobuf比json小一倍数据量,独特的编码、没有fieldname等),但现有项目的消息类数量巨多,按照网上的方案是安装protobuf.net 这个nuget包,然后需要给消息类一个一个添加[ProtoBuf.ProtoContract]、[ProtoBuf.ProtoMember(index)]等Attributes,更可悲的是,还得处理继承的问题,也就是要有类似如下这种代码:

[ProtoContract][ProtoInclude(10, typeof(Male))]public class Person {   [ProtoMember(1)]   public int Id { get; set; }   [ProtoMember(2)]   public string Name { get; set; }   [ProtoMember(3)]   public Address Address { get; set;}}[ProtoContract]public class Male : Person{       } [ProtoContract]public class Address {   [ProtoMember(1)]   public string Line1 {get;set;}   [ProtoMember(2)]   public string Line2 {get;set;}}

关于为什么要设置上面这些attributes,跟protobuf的原理息息相关,有兴趣的朋友可以看看这篇文章,而关于protobuf.net的基本用法,可以参考这里

找解决方案,咱们不干体力活

对于项目存在巨多消息类,显然这么一个一个的加attributes既费时又容易出错。我拿着这个需求,怀着忐忑的心,一通操作,终于找到了想要的方案,也就是找到了without attributes的方法,顺便悄悄的告诉您,貌似国内还没谁发现这个方法

使用RuntimeTypeModel.Default进行类型及其Properties的配置

动动脑筋,上面的代码,如果不用attributes而是用RuntimeTypeModel.Default进行类型及其Properties的配置的话,代码就是的:

var personMetaType = RuntimeTypeModel.Default.Add(typeof (Person), false);personMetaType.Add(1, "Id");personMetaType.Add(2, "Name");personMetaType.Add(3, "Address"); var addressMetaType = RuntimeTypeModel.Default.Add(typeof(Address), false);addressMetaType.Add(1, "Line1");addressMetaType.Add(2, "Line2");// 给父类metaType添加子类型personMetaType.AddSubType(10, typeof (Male)); // 然后添加子类型RuntimeTypeModel.Default.Add(typeof(Male), false);RuntimeTypeModel.Default.Add(typeof(Female), false);

但是仔细想想其实原理跟添加attributes是一个道理,

具体实现

有了上面这个方法,我们就会自然而然想到对所有消息类使用RuntimeTypeModel.Default进行类型及其Properties的配置,但我们又不可能费时费力的给项目的每个消息实体类添加这些代码,那么这里就想到了使用反射找出项目中所有消息实体类,然后一个一个的操作

先看看我们的消息基类:

///     /// 使用MQ队列的消息基类    ///     public  class MsgBase    {        ///         /// 消息编码、接入系统编码        ///         public string MessageCode { get; set; }        ///         /// 消息类型 (业务相关的一个枚举)        ///         public  MessageTypeCode MessageType { get; set; }    }

很简单吧,然后看看我们给类动态添加“[ProtoBuf.*]”这些attributes的核心代码:

static bool isInit = false; // 避免重复初始化        ///         /// 初始化,消息发送跟处理程序在启动后就需要调用        ///         public static void Init()        {            if (!isInit)            {                var msgAssemblyName = "Msg Model 所在的 assemly long name";                // 需要处理MsgBase本身跟继承它的所有消息类型                var msgTypes = (from t in Assembly.Load(msgAssemblyName).GetTypes()                                where (t.BaseType == typeof(MsgBase) || t.Name == "MsgBase")                                select t).OrderBy(t=>t.Name).ToList();                foreach (var msgType in msgTypes)                {                    AddTypeToModel(msgType, RuntimeTypeModel.Default);                }                isInit = true;            }        }        ///         /// 添加类型以及字段到模型中        ///         ///         ///         /// 
private static void AddTypeToModel(Type type, RuntimeTypeModel typeModel) { if (typeModel.IsDefined(type)) { return; } typeModel.IncludeDateTimeKind = true; // 1. 进行类型配置 var metaType = typeModel.Add(type, true); // Protobuf的顺序很重要,在序列化跟反序列化都需要保持一致的顺序,否则反序列化的时候就会出错 var publicProperties = type.GetProperties().Where(h => h.SetMethod != null).OrderBy(h => h.Name); var complexPropertiesInfo = publicProperties.Where(f => !IsSimpleType(f.PropertyType)).OrderBy(h=>h.Name); // 2. 进行此类型的Properties的配置 foreach (var simplePropertyInfo in publicProperties) { metaType.Add(simplePropertyInfo.Name); } // 复杂类型需要处理里面的每个简单类型,使用了递归操作 foreach (var complexPropertyInfo in complexPropertiesInfo) { if (complexPropertyInfo.PropertyType.IsGenericType) { // Protobuf的顺序很重要,在序列化跟反序列化都需要保持一致的顺序,否则反序列化的时候就会出错 foreach (var genericArgumentType in complexPropertyInfo.PropertyType.GetGenericArguments().OrderBy(h=>h.Name)) { if (!IsSimpleType(genericArgumentType)) { AddTypeToModel(genericArgumentType, typeModel); } } } else { AddTypeToModel(complexPropertyInfo.PropertyType, typeModel); } } } /// /// 是否为简单类型 /// /// ///
private static bool IsSimpleType(Type type) { var underlyingType = Nullable.GetUnderlyingType(type); var newType = underlyingType ?? type; var simpleTypes = new List
{ typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal), typeof(bool), typeof(string), typeof(char), typeof(Guid), typeof(DateTime), typeof(DateTimeOffset), typeof(byte[]), typeof(string[]) }; return simpleTypes.Contains(newType) || newType.GetTypeInfo().IsEnum; }

其实上面就是所有代码了,使用的话,就是在消息发送跟消息接收程序启动后,就调用上面的Init方法,仅需要调用一次额。当然聪明的你,肯定已经想到将它封装成一个工具类了,哈哈。

注意事项

细心的朋友可以注意到,我并没有调用AddSubType(其实我消息类的某些property确实是复杂类型且有父子关系的)以及可能你也发现了在上面的“想办法解决,咱们不干体力活”章节中父子类型注册到RuntimeTypeModel中有一个先后顺序,但上面的代码在实际使用过程中也就是消息接收端反序列化protobuf消息时并没出现问题。如果你的项目使用了上面的代码,结果发现反序列化不了,特别是抛了不能识别类型的错误,那么很可能就是我所说的两点要处理下。

希望大家多多评论,2020年身体健康,过得顺心!!!

转载地址:http://zwaii.baihongyu.com/

你可能感兴趣的文章
react-native-wechat
查看>>
基于云信的react-native聊天系统
查看>>
网易云音乐移动客户端Vue.js
查看>>
ES7 await/async
查看>>
ES7的Async/Await
查看>>
React Native WebView组件实现的BarCode(条形码)、(QRCode)二维码
查看>>
每个人都能做的网易云音乐[vue全家桶]
查看>>
Vue2.0全家桶仿腾讯课堂(移动端)
查看>>
React+Redux系列教程
查看>>
19 个 JavaScript 常用的简写技术
查看>>
iOS应用间相互跳转
查看>>
iOS开发之支付宝集成
查看>>
iOS开发 支付之银联支付集成
查看>>
iOS开发支付集成之微信支付
查看>>
浅谈JavaScript--声明提升
查看>>
React非嵌套组件通信
查看>>
Websocket 使用指南
查看>>
浏览器兼容性问题解决方案 · 总结
查看>>
一个很棒的Flutter学习资源列表
查看>>
为什么你应该放弃React老的Context API用新的Context API
查看>>