java踩雷系列3-通过反射实现 序列化,反序列化

先说一下反射:
反射这东西C#和java都有,据说是C#首先提出这个概念的(好像以前不知道哪看到过)
但是不需要纠结,现在两个语言的反射都差不多,都这样用就行了
C#中应用反射的场合,没有想象中的那么多,其实本来是一个挺有意思的东西
而这个东西在java中被赋予了不同的含义和活力

Java反射说的是在运行状态中,对于任何一个类,我们都能够知道这个类有哪些方法和属性。对于任何一个对象,我们都能够对它的方法和属性进行调用。我们把这种动态获取对象信息和调用对象方法的功能称之为反射机制。
在java中,反射应用的地方很多,比如在Spring的IOC中(控制反转)
通过定义javaBean,并且写好相应的xml,然后就会通过读取这个xml配置得到反射相关信息,然后进行相关类实例化等操作,通过第三方的一个容器管理所有的类,从而降低类之间的耦合程度(虽然还是一定程度上牺牲了效率,毕竟反射始终比直接引用的效率要低一点)

在hibernate中,也有用到反射,建立表和类的字段-属性映射关系,再使用特定语法(可以是hql,也可以是原生sql语句,存储过程之类),与数据库进行交互,同时通过反射将结果转换为对象,完完全全的面向对象思想,直接操作对象就好

再说一下字节对齐:
在上一篇已经提及过了
先说一下在C#中的字节对齐是什么样的:


        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)]
        public struct struct1
        {
            public UInt16 u16;
            public Int32 int32;
            public Byte b;
        }
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)]
        public struct struct2
        {
            public UInt16 u16;
            public Byte b;
            public Int32 int32;
        }
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        /// 
        [STAThread]
        static void Main()
        {
            int size1 = Marshal.SizeOf(typeof(struct1));
            int size2= Marshal.SizeOf(typeof(struct2));
            Console.WriteLine(size1);
            Console.WriteLine(size2);
            Console.Read();
        }

其中

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)]

表示按照属性出现的顺序布局,以多字节字符串的形式封送字符穿,对齐方式为4字节
输出:
在这里插入图片描述
除了C#,c++,c中也有这样的字节对齐方式
不清楚java中有没有(反正我好像没找到相关方面的资料)
然后我们需手动对其进行补齐,则 对齐后转换为java如下:
(注:java中没有struct结构体,并且没有像其他很多语言都有的无符号整形类型)

        public class struct2
        {
            public short u16;
            public byte b;
            public byte reserved;//4字节对齐时补齐用
            public int int32;
        }

在讲接下来代码之前,先强调一下:接下来代码都是针对小端序的,如果是大端绪需要对ByteBuf.readXXLE/ByteBuf.writeXXLE这些方法统统去掉LE

1.通过反射获取java对象中的所有成员属性类型:

Class<?> clazz = t.getClass();
Field[] fields;
for (Field field : fields) {
	field.setAccessible(true);
	String name = field.getGenericType().getTypeName();
}

再贴一下上一篇地址:
https://blog.youkuaiyun.com/weixin_40683787/article/details/94197296
在上篇我用了比较长的篇幅写了Netty Client相关组件的使用
其中
public static void ObjtoBytes(T t, ByteBuf buf)
public static void BytetoObj(T t, ByteBuf buf)
这两个是主要的方法

2.将ByteBuf的可读内容转换成我们想要的对象(反序列化)

2.1反序列化得到简单的对象

原理就是通过上面那一段,获取所有public修饰的成员属性的类型,通过不同的类型执行
buf.ReadXX()/buf.ReadXXLE()方法
然后通过field.set对其进行赋值

public static <T> void BytetoObj(T t, ByteBuf buf) {
		Class<?> clazz = t.getClass();
		Field[] fields;
		if (FieldMap.containsKey(clazz))
			fields = FieldMap.get(clazz);
		else
			fields = clazz.getFields();
		for (Field field : fields) {
			field.setAccessible(true);
			String name = field.getGenericType().getTypeName().toLowerCase();
			try {
				switch (name) {
				case "integer":				
				case "int":
					field.set(t, buf.readIntLE());
					continue;
				case "short":
					field.set(t, buf.readShortLE());
					continue;
				case "byte":
					field.set(t, buf.readByte());
					continue;
				case "long":
					field.set(t, buf.readLongLE());
					continue;
				default:
					continue;
				}
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return;
	}

2.2补充:反序列化包含数组的复杂对象

上面这一段,只支持了byte,int,short,long这四种类型,其实double,float这些也是支持的(只不过因为实际定的协议中,不存在这些类型,所以就没写,使用时可以根据实际情况这样写)

以为这样就完了吗?其实在实际使用中,由于协议的不同,可能会定义一些固定长度的数组,或者可变长的数组,而数组的元素可以是简单的基类,或者是自定义类

为了应对这种情况,可以新增一个GetArray方法:

				default:
					if (name.contains("[]")) {
						int length = Array.getLength(field.get(t));
						GetArray(field.get(t), length, buf);
					}
					continue;
private static void GetArray(Object obj, int Length, ByteBuf buf) {
		String itemName = Array.get(obj, 0).getClass().getSimpleName().toLowerCase();
		if (!itemName.contains("[]")) {
			for (int i = 0; i < Length; i++) {
				switch (itemName) {
				case "integer":				
				case "int":
					Array.set(obj, i, buf.readIntLE());
					continue;
				case "short":
					Array.set(obj, i, buf.readShortLE());
					continue;
				case "byte":
					Array.set(obj, i, buf.readByte());
					continue;
				case "long":
					Array.set(obj, i, buf.readLongLE());
					continue;
				case "MyClass1"://自定义类1
					MyClass1 class1=new MyClass1();
					util.BytetoObj(class1,buf);
					Array.set(obj,i,class1);
					break;
				case "自定义类2":
				......同上
				default:
					continue;
				}
			}
		} else {
			int length = Array.getLength(Array.get(obj, 0));
			for (int i = 0; i < Length; i++)
				GetArray(Array.get(obj, i), length, buf);
		}
	}

通过判断属性中是否包含中括号而判断这个属性是否为数组,如果是数组则通过这个方法对其进行赋值,这个方法通过递归的方式,可以对二维甚至多维的数组进行处理
如果是自定义类的数组时,还需要对此方法进行改进,即:加上case 自定义类名,然后再调用回上面的BytetoObj即可,同时通过Array.set(obj, i,T);方法设置该数组中的值

因为实际使用中并没有这些自定义类,所以就没写上

3.将对象写入ByteBuff(序列化)

原理其实和上面那一段差不多,就在上面的基础上直接修修补补就可以得到了。具体就不详细说了,直接贴代码:

	public static <T> void ObjtoBytes(T t, ByteBuf buf) {
		Class<?> clazz = t.getClass();
		Field[] fields;
		if (FieldMap.containsKey(clazz))
			fields = FieldMap.get(clazz);
		else
			fields = clazz.getFields();

		for (Field field : fields) {
			field.setAccessible(true);
			String name = field.getGenericType().getTypeName().toLowerCase();
			try {
				switch (name) {
				case "integer":				
				case "int":
					buf.writeIntLE((int) field.get(t));
					continue;
				case "short":
					buf.writeShortLE((short) field.get(t));
					continue;
				case "byte":
					buf.writeByte((byte) field.get(t));
					continue;
				case "long":
					buf.writeLongLE((long) field.get(t));
					continue;
				default:
					if (name.contains("[]")) {
						int length = Array.getLength(field.get(t));
						SetArray(field.get(t), length, buf);
					}
					continue;
				}
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	private static void SetArray(Object obj, int Length, ByteBuf buf) {
		String itemName = Array.get(obj, 0).getClass().getSimpleName().toLowerCase();
		if (!itemName.contains("[]")) {
			for (int i = 0; i < Length; i++) {
				switch (itemName) {
				case "integer":				
				case "int":
					buf.writeIntLE(Array.getInt(obj, i));
					continue;
				case "short":
					buf.writeShortLE(Array.getShort(obj, i));
					continue;
				case "byte":
					buf.writeByte(Array.getByte(obj, i));
					continue;
				case "long":
					buf.writeLongLE(Array.getLong(obj, i));
					continue;
				default:
					continue;
				}
			}
		} else {
			int length = Array.getLength(Array.get(obj, 0));
			for (int i = 0; i < Length; i++)
				SetArray(Array.get(obj, i), length, buf);
		}
	}

4. 获取对象长度:

	private static int GetArrayLen(Object obj, int Length) {
		int result = 0;
		String itemName = Array.get(obj, 0).getClass().getSimpleName().toLowerCase();
		if (!itemName.contains("[]")) {
			for (int i = 0; i < Length; i++) {
				switch (itemName) {
				case "integer":				
				case "int":
					result += 4;
					continue;
				case "short":
					result += 2;
					continue;
				case "byte":
					result += 1;
					continue;
				case "long":
					result += 8;
					continue;
				default:
					continue;
				}
			}
		} else {
			int length = Array.getLength(Array.get(obj, 0));
			for (int i = 0; i < Length; i++)
				result += GetArrayLen(Array.get(obj, i), length);
		}
		return result;
	}
	private static int GetArrayLen(Object obj, int Length) {
		int result = 0;
		String itemName = Array.get(obj, 0).getClass().getSimpleName().toLowerCase();
		if (!itemName.contains("[]")) {
			for (int i = 0; i < Length; i++) {
				switch (itemName) {
				case "integer":
				case "int":
					result += 4;
					continue;
				case "short":
					result += 2;
					continue;
				case "byte":
					result += 1;
					continue;
				case "long":
					result += 8;
					continue;
				default:
					continue;
				}
			}
		} else {
			int length = Array.getLength(Array.get(obj, 0));
			for (int i = 0; i < Length; i++)
				result += GetArrayLen(Array.get(obj, i), length);
		}
		return result;
	}

另外,由于频繁需要通过反射进行创建,所以这边就使用了HashMap保存下了类和Field[]的映射,以此提高效率

在做项目的时候,没有找到这一块比较详细的解决方法,花了一点时间将这些写出来,绝对原创!
上面的代码还有可优化的地方,欢迎讨论,如有不对欢迎指出

原创不易,转载务必标明出处!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值