• 修改现有消息类让.net core项目支持Protobuf


    前言

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

    又接无上老板的一个需求,需要让.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的配置,但我们又不可能费时费力的给项目的每个消息实体类添加这些代码,那么这里就想到了使用反射找出项目中所有消息实体类,然后一个一个的操作

    先看看我们的消息基类:

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

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

           
            static bool isInit = false; // 避免重复初始化
    
            /// <summary>
            /// 初始化,消息发送跟处理程序在启动后就需要调用
            /// </summary>
            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;
                }
            }
    
    
            /// <summary>
            /// 添加类型以及字段到模型中
            /// </summary>
            /// <param name="type"></param>
            /// <param name="typeModel"></param>
            /// <returns></returns>
            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);
                    }
                }
            }
    
    
            /// <summary>
            /// 是否为简单类型
            /// </summary>
            /// <param name="type"></param>
            /// <returns></returns>
            private static bool IsSimpleType(Type type)
            {
                var underlyingType = Nullable.GetUnderlyingType(type);
                var newType = underlyingType ?? type;
                var simpleTypes = new List<Type>
                                   {
                                       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年身体健康,过得顺心!!!

    联系作者请添加微信号:sutong324,并注明:来自cnblogs
  • 相关阅读:
    大型网站的数据库分割问题。
    大型网站的数据库分割问题。
    分布式集群的Session问题
    大型网站架构设计摘要
    大型网站的架构概要
    公司产品的优势
    java.util.concurrent 学习笔记(2) 线程池基础
    《Java 7 并发编程指南》学习概要 (6) Fork/Join
    《Java 7 并发编程指南》学习概要 (5) 线程池
    《Java 7 并发编程指南》学习概要 (4) 并发集合
  • 原文地址:https://www.cnblogs.com/sutong/p/12222646.html
Copyright © 2020-2023  润新知