struct-codec增加MapCodec以支持字典字段

This commit is contained in:
kingston-csj
2025-11-09 15:48:21 +08:00
parent fcdc6d86eb
commit ec4efbbdbb
14 changed files with 309 additions and 88 deletions
+7
View File
@@ -254,11 +254,18 @@
Container#inject()方法增加主键检查
Container增加#validate(DataRepository dataRepository)方法,允许容器进行关联数据校验
### jforgame-codec-struct
增加MapCodec,以支持字典字段
## V4.0.0 api变动!!(计划)
Container去掉#validate()无参方法
### jforgame-codec-struct
StructMessageCodec更名为BeanAutoCodec
ProtobufMessageCodec更名为ProtobufCodec
ArrayCodec/CollectionCodec容器元素支持继承关系
@@ -7,13 +7,16 @@ import java.nio.ByteBuffer;
* 数组属性序列化
* 注:由于数组元素bean没有像Message一样注册id,
* 因此数组的元素不能是父类或抽象类
* 数组长度不能超过Short.MAX_VALUE,即最多65535
*/
public class ArrayCodec extends Codec {
@Override
public Object decode(ByteBuffer in, Class<?> type, Class<?> wrapper) {
int size = ByteBuffUtil.readShort(in);
if (size < 0) {
throw new RuntimeException("Array size less than zero!");
}
Object array = ReflectUtil.newArray(type, wrapper, size);
for (int i = 0; i < size; i++) {
@@ -32,6 +35,9 @@ public class ArrayCodec extends Codec {
return;
}
int size = Array.getLength(value);
if (size > Short.MAX_VALUE) {
throw new RuntimeException("Collection size less than zero or exceed max short value!");
}
ByteBuffUtil.writeShort(out, (short) size);
for (int i = 0; i < size; i++) {
Object elem = Array.get(value, i);
@@ -37,6 +37,7 @@ public abstract class Codec {
register(String.class, new StringCodec());
register(List.class, new CollectionCodec());
register(Set.class, new CollectionCodec());
register(Map.class, new MapCodec());
register(Object[].class, new ArrayCodec());
}
@@ -9,58 +9,64 @@ import java.util.List;
import java.util.Set;
/**
*
* 集合属性序列化
* 注:由于集合元素bean没有像Message一样注册id,
* 因此集合的元素不能是父类或抽象类
*/
* 集合属性序列化
* 注:由于集合元素bean没有像Message一样注册id,
* 因此集合的元素不能是父类或抽象类
* 集合长度不能超过Short.MAX_VALUE,即最多65535
*/
public class CollectionCodec extends Codec {
@Override
@SuppressWarnings("all")
public Object decode(ByteBuffer in, Class<?> type, Class<?> wrapper) {
int size = ByteBuffUtil.readShort(in);
int modifier = type.getModifiers();
Collection<Object> result = null;
@Override
@SuppressWarnings("all")
public Object decode(ByteBuffer in, Class<?> type, Class<?> wrapper) {
int size = ByteBuffUtil.readShort(in);
if (size < 0) {
throw new RuntimeException("Collection size less than zero!");
}
int modifier = type.getModifiers();
Collection<Object> result = null;
if (Modifier.isAbstract(modifier) || Modifier.isInterface(modifier)) {
if (List.class.isAssignableFrom(type)) {
result = new ArrayList<>();
} else if (Set.class.isAssignableFrom(type)) {
result = new HashSet<>();
}
} else {
try {
result = (Collection)type.newInstance();
}catch(Exception e) {
result = new ArrayList<>();
}
}
if (Modifier.isAbstract(modifier) || Modifier.isInterface(modifier)) {
if (List.class.isAssignableFrom(type)) {
result = new ArrayList<>();
} else if (Set.class.isAssignableFrom(type)) {
result = new HashSet<>();
}
} else {
try {
result = (Collection) type.newInstance();
} catch (Exception e) {
result = new ArrayList<>();
}
}
for (int i=0; i<size; i++) {
Codec fieldCodec = getSerializer(wrapper);
Object eleValue = fieldCodec.decode(in, wrapper, null);
result.add(eleValue);
}
for (int i = 0; i < size; i++) {
Codec fieldCodec = getSerializer(wrapper);
Object eleValue = fieldCodec.decode(in, wrapper, null);
result.add(eleValue);
}
return result;
}
return result;
}
@Override
public void encode(ByteBuffer out, Object value, Class<?> wrapper) {
if (value == null) {
ByteBuffUtil.writeShort(out, (short)0);
return;
}
Collection<Object> collection = (Collection)value;
int size = collection.size();
ByteBuffUtil.writeShort(out, (short)size);
@Override
public void encode(ByteBuffer out, Object value, Class<?> wrapper) {
if (value == null) {
ByteBuffUtil.writeShort(out, (short) 0);
return;
}
Collection<Object> collection = (Collection) value;
int size = collection.size();
if (size > Short.MAX_VALUE) {
throw new RuntimeException("Collection size less than zero or exceed max short value!");
}
ByteBuffUtil.writeShort(out, (short) size);
for (Object elem:collection) {
Class<?> clazz = elem.getClass();
Codec fieldCodec = getSerializer(clazz);
fieldCodec.encode(out, elem, wrapper);
}
}
for (Object elem : collection) {
Class<?> clazz = elem.getClass();
Codec fieldCodec = getSerializer(clazz);
fieldCodec.encode(out, elem, wrapper);
}
}
}
@@ -2,53 +2,63 @@ package jforgame.codec.struct;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
public class FieldCodecMeta {
private Field field;
private Class<?> type;
private Codec codec;
/** collection类型里的元素 */
private Class<?> wrapper;
public static FieldCodecMeta valueOf(Field field, Codec codec) {
FieldCodecMeta meta = new FieldCodecMeta();
meta.field = field;
Class<?> type = field.getType();
meta.type = type;
meta.codec = codec;
if (Collection.class.isAssignableFrom(type)) {
meta.wrapper = (Class<?>)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0];
} else if (type.isArray()) {
meta.wrapper = type.getComponentType();
}
return meta;
}
public Field getField() {
return field;
}
private Field field;
public Class<?> getType() {
return type;
}
private Class<?> type;
public Codec getCodec() {
return codec;
}
private Codec codec;
/**
* collection类型里的元素
*/
private Class<?> wrapper;
public Class<?> getWrapper() {
return wrapper;
}
public static FieldCodecMeta valueOf(Field field, Codec codec) {
FieldCodecMeta meta = new FieldCodecMeta();
meta.field = field;
Class<?> type = field.getType();
meta.type = type;
meta.codec = codec;
// 处理 Collection 类型(存储元素类型)
if (Collection.class.isAssignableFrom(type)) {
meta.wrapper = (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
} else if (type.isArray()) {
// 处理数组类型(存储元素类型)
meta.wrapper = type.getComponentType();
} // 处理 Map 类型(Key 固定为 String,只存储 Value 类型)
else if (Map.class.isAssignableFrom(type)) {
// 泛型参数:[0]是Key(忽略,强制为String),[1]是Value类型
Type[] actualTypes = ((ParameterizedType) field.getGenericType()).getActualTypeArguments();
meta.wrapper = (Class<?>) actualTypes[1]; // 只存 Value 类型
}
return meta;
}
public Field getField() {
return field;
}
public Class<?> getType() {
return type;
}
public Codec getCodec() {
return codec;
}
public Class<?> getWrapper() {
return wrapper;
}
@Override
public String toString() {
return "FieldCodecMeta [field=" + field + ", type=" + type + ", serializer=" + codec + ", wrapper="
+ wrapper + "]";
}
@Override
public String toString() {
return "FieldCodecMeta [field=" + field + ", type=" + type + ", serializer=" + codec + ", wrapper="
+ wrapper + "]";
}
}
@@ -0,0 +1,64 @@
package jforgame.codec.struct;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
/**
* Map 编解码器(Key 强制为 String 类型,后续大版本升级再做改造)
*/
public class MapCodec extends Codec {
@Override
@SuppressWarnings("all")
public Object decode(ByteBuffer in, Class<?> type, Class<?> valueType) {
// 读取 Map 大小(short 类型)
int size = ByteBuffUtil.readShort(in);
Map<String, Object> result = new HashMap<>(size);
if (valueType == null) {
throw new IllegalArgumentException("MapCodec: valueType is null");
}
// Value 类型:优先用 wrapper 传入的类型,否则默认 Object
Class<?> actualValueType = valueType != null ? valueType : Object.class;
Codec valueCodec = getSerializer(actualValueType);
// 循环解码键值对(Key 固定为 String)
for (int i = 0; i < size; i++) {
// 解码 Key(强制用 StringCodec)
String key = (String) getSerializer(String.class).decode(in, String.class, null);
// 解码 Value(用指定的 Value 类型编解码器)
Object value = valueCodec.decode(in, actualValueType, null);
result.put(key, value);
}
return result;
}
@Override
public void encode(ByteBuffer out, Object target, Class<?> wrapper) {
if (target == null) {
// 空 Map 写入 size=0
ByteBuffUtil.writeShort(out, (short) 0);
return;
}
Map<String, Object> map = (Map<String, Object>) target;
int size = map.size();
ByteBuffUtil.writeShort(out, (short) size);
// 循环编码键值对(Key 强制为 String)
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
// 编码 Key
getSerializer(String.class).encode(out, key, null);
// 编码 Value(根据实际类型获取编解码器)
Class<?> valueType = value != null ? value.getClass() : Object.class;
Codec valueCodec = getSerializer(valueType);
valueCodec.encode(out, value, null);
}
}
}
@@ -0,0 +1,26 @@
import jforgame.codec.MessageCodec;
import jforgame.codec.struct.StructMessageCodec;
import message.ItemVo;
import message.PlayerBackpack;
import org.junit.Test;
public class TestArrayMessage {
private MessageCodec messageCodec = new StructMessageCodec();
@Test
public void test() throws Exception {
PlayerBackpack backpack = new PlayerBackpack();
for (int i = 0; i < 10; i++) {
ItemVo vo = new ItemVo();
vo.setItemId(i);
vo.setCount(i);
backpack.getItems().add(vo);
}
byte[] encode = messageCodec.encode(backpack);
PlayerBackpack newMsg = (PlayerBackpack) messageCodec.decode(PlayerBackpack.class, encode);
System.out.println(newMsg);
}
}
@@ -1,5 +1,6 @@
import jforgame.codec.MessageCodec;
import jforgame.codec.struct.StructMessageCodec;
import message.PlayerLevelUpMessage;
import org.junit.Assert;
import org.junit.Test;
@@ -0,0 +1,27 @@
import jforgame.codec.MessageCodec;
import jforgame.codec.struct.StructMessageCodec;
import message.ItemVo;
import message.PlayerBackpack2;
import org.junit.Test;
public class TestMapMessage {
private MessageCodec messageCodec = new StructMessageCodec();
@Test
public void test() throws Exception {
PlayerBackpack2 backpack = new PlayerBackpack2();
for (int i = 0; i < 10; i++) {
ItemVo vo = new ItemVo();
vo.setUid("uid_" + i);
vo.setItemId(i);
vo.setCount(i);
backpack.getItems().put(vo.getUid(), vo);
}
byte[] encode = messageCodec.encode(backpack);
PlayerBackpack2 newMsg = (PlayerBackpack2) messageCodec.decode(PlayerBackpack2.class, encode);
System.out.println(newMsg);
}
}
@@ -0,0 +1,34 @@
package message;
public class ItemVo {
private String uid;
private int itemId;
private int count;
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
@@ -0,0 +1,17 @@
package message;
import java.util.LinkedList;
import java.util.List;
public class PlayerBackpack {
private List<ItemVo> items = new LinkedList<>();
public List<ItemVo> getItems() {
return items;
}
public void setItems(List<ItemVo> items) {
this.items = items;
}
}
@@ -0,0 +1,18 @@
package message;
import java.util.HashMap;
import java.util.Map;
public class PlayerBackpack2 {
private Map<String, ItemVo> items = new HashMap<>();
public Map<String, ItemVo> getItems() {
return items;
}
public void setItems(Map<String, ItemVo> items) {
this.items = items;
}
}
@@ -1,3 +1,5 @@
package message;
public class PlayerLevelUpMessage extends PlayerMessage {
private int afterLevel;
@@ -1,3 +1,5 @@
package message;
public class PlayerMessage {
private long playerId;