更新,第一次看的小伙伴直接跳到下面的"直入正文", 再回来看吧
4、成功将该工具类完整应用到小游戏上, 终于抽空把代码逻辑小小梳理了一哈,代码如下(保留了ProtoField.java类)
小游戏服务端: https://github.com/kdYY/sqGameSvr
觉得喜欢的来个star吧
public class ProtoBufUtil {
public static List<String> baseTypeList =
Arrays.asList("int", "Integer", "float", "Float", "long", "Long", "double", "Double", "String", "Boolean", "boolean");
public static <T,K> Object transformProtoReturnBean(T goalBuilder, K sourceBean) {
try{
transformProtoReturnBuilder(goalBuilder, sourceBean);
Method build = goalBuilder.getClass().getDeclaredMethod("build");
return build.invoke(goalBuilder);
}catch (Exception e) {
e.printStackTrace();
return null;
}
}
//
public static <T,K> T transformProtoReturnBuilder(T goalBuilder, K sourceBean) {
Method[] goalBuilderMethod = goalBuilder.getClass().getDeclaredMethods();
Map<Field, Class> listClassMap = new HashMap<>();
Map<Field, Method> listGetMethodMap = new HashMap<>();
Map<Field, Method> goalBuilderAddMethodMap = new HashMap<>();
Map<Field, Method> goalBuildBaseTypeAddMethodMap = new HashMap<>();
Map<Field, Method> sourceBeanGetMethodMap = new HashMap<>();
Map<Field, Method> goalBuilderSetMethodMap = new HashMap<>();
Map<Field, Method> sourceBeanFunctionMap = new HashMap<>();
//获取K中的所有属性名称,排除@ProtoField(ignore=false)的属性, 获取需要注入List的属性
getAllNeedMethod(goalBuilder, sourceBean, goalBuilderMethod, listClassMap, listGetMethodMap,
goalBuilderAddMethodMap, sourceBeanGetMethodMap, goalBuilderSetMethodMap, sourceBeanFunctionMap);
//进行get set方法执行
invokeGetAndSet(goalBuilder, sourceBean, sourceBeanGetMethodMap, goalBuilderSetMethodMap);
//function执行
sourceBeanFunctionMap.values().forEach(m -> invokeMethod(m, sourceBean, goalBuilder));
for (Map.Entry<Field, Class> entry : listClassMap.entrySet()) {
Field field = entry.getKey();
Class listClass = entry.getValue();
//get方法
Method getListMethod = listGetMethodMap.get(field);
List invoke = (List) invokeMethod(getListMethod, sourceBean);
if(invoke == null) {
continue;
}
if(baseTypeList.contains(listClass.getSimpleName())) {
for (Object o : invoke) {
Optional.ofNullable(goalBuildBaseTypeAddMethodMap.get(field)).ifPresent(m -> invokeMethod(m, goalBuilder, o));
}
} else {
for (int i = 0; i < invoke.size(); i++) {
try {
Object newBuilder = getMethod(getNewInstance(listClass), "newBuilder").invoke(null);
invokeMethod(goalBuilderAddMethodMap.get(field), goalBuilder, transformProtoReturnBean(newBuilder, invoke.get(i)));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
}
return goalBuilder;
}
private static Object getNewInstance(Class listClass) {
Constructor cellConstruct = null;
try {
cellConstruct = listClass.getDeclaredConstructor();
cellConstruct.setAccessible(true);
return cellConstruct.newInstance();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
private static <T, K> void invokeGetAndSet(T goalBuilder, K sourceBean, Map<Field, Method> sourceBeanGetMethodMap,
Map<Field, Method> goalBuilderSetMethodMap) {
for (Map.Entry<Field, Method> getMethodEntry: sourceBeanGetMethodMap.entrySet()) {
Field field = getMethodEntry.getKey();
field.setAccessible(true);
Method getMethod = getMethodEntry.getValue();
Method setMethod = goalBuilderSetMethodMap.get(field);
Optional.ofNullable(invokeMethod(getMethod, sourceBean)).ifPresent(val -> invokeMethod(setMethod, goalBuilder, val));
}
}
private static Object invokeMethod(Method method, Object obj, Object... args) {
try {
return method.invoke(obj, args);
} catch (IllegalArgumentException e) {
log.info(method.getName() + "参数类型匹配异常");
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
private static <T, K> void getAllNeedMethod(T goalBuilder, K sourceBean, Method[] goalBuilderMethod,
Map<Field, Class> listClassMap, Map<Field, Method> listGetMethodMap,
Map<Field, Method> goalBuilderAddMethodMap, Map<Field, Method> sourceBeanGetMethodMap,
Map<Field, Method> goalBuilderSetMethodMap, Map<Field, Method> sourceBeanFunctionMap) {
List<Field> declaredFields = getClassField(sourceBean.getClass());//.getFields();
for (Field declaredField : declaredFields) {
declaredField.setAccessible(true);
AtomicReference<Boolean> inject = new AtomicReference<>(true);
String fieldName = declaredField.getName();
String getMethodName = "get" + upperCaseFirstLetter(fieldName);
String boolGetMethodName = "is" + upperCaseFirstLetter(fieldName);
String setMethodName = "set" + upperCaseFirstLetter(fieldName.replaceAll("_", ""));
Arrays.stream(declaredField.getAnnotations())
.filter(anno -> anno instanceof ProtoField)
.map(anno -> (ProtoField) anno)
.findFirst()
.ifPresent(protoAnno -> {
//查看ignore屬性
if(protoAnno.Ignore()) {
inject.set(false);
return;
}
Class targetClass = protoAnno.TargetClass();
//如果K中有Function需要执行的,加入执行
if(!protoAnno.Function().isEmpty()) {
protoFieldFunction(sourceBean, sourceBeanFunctionMap, declaredField, protoAnno, targetClass);
inject.set(false);
return;
}
/**如果sourcebean中有List,检查declaredMethods是否有add的方法,没有则跳过,有则加入执行
* 譬如Bag类中的itemList字段
*/
String simpleName = targetClass.getSimpleName();
try {
Method addMethod;
String addMethodName;
if(baseTypeList.contains(simpleName)) {
addMethodName = "add" + upperCaseFirstLetter(protoAnno.TargetName());
} else {
addMethodName = "add" + upperCaseFirstLetter(simpleName);
}
if(!targetClass.equals(Void.class)
&& (addMethod = hasListAddMethond(goalBuilderMethod, addMethodName, targetClass)) != null) {
listClassMap.put(declaredField, targetClass);
//list的get方法
listGetMethodMap.put(declaredField, getMethod(sourceBean, getMethodName));
//add方法
goalBuilderAddMethodMap.put(declaredField, addMethod);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} finally {
inject.set(false);
}
});
//正常需要注入的屬性
if(inject.get()) {
try {
Method getMethod;
Class<?> type = declaredField.getType();
if(type.getName().equals("Boolean") || type.getName().equals("boolean")) {
getMethod = getMethod(sourceBean, boolGetMethodName);
} else {
getMethod = getMethod(sourceBean, getMethodName);
}
if(getMethod.getReturnType().equals(type)) {
sourceBeanGetMethodMap.put(declaredField, getMethod);
goalBuilderSetMethodMap.put(declaredField, getMethod(goalBuilder, setMethodName, type));
} else {
throw new NoSuchMethodException(sourceBean.getClass().getSimpleName() + "中," + getMethodName + "方法返回的不是" + type);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
}
private static <K> void protoFieldFunction(K sourceBean, Map<Field, Method> sourceBeanFunctionMap,
Field declaredField, ProtoField protoAnno, Class targetClass) {
Method method = null;
try {
if(!targetClass.equals(Void.class)
&& !protoAnno.TargetName().isEmpty()) {
method = sourceBean.getClass().getMethod(protoAnno.Function(), targetClass);
} else {
method = sourceBean.getClass().getDeclaredMethod(protoAnno.Function());
}
} catch (NoSuchMethodException e) {
try{
if(!targetClass.equals(Void.class)) {
method = getDeclaredMethod(sourceBean.getClass(), protoAnno.Function(), targetClass);
} else {
method = getDeclaredMethod(sourceBean.getClass(), protoAnno.Function());
}
} catch (NoSuchMethodException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
if(method != null) {
sourceBeanFunctionMap.put(declaredField, method);
}
}
//对java API中的获取方法做一个基本类型的兼容
private static <T> Method getMethod(T goalBuilder, String methodName, Class<?>... type) throws NoSuchMethodException {
if(type == null || type.length == 0) {
return getDeclaredMethod(goalBuilder.getClass(), methodName);
} else if(type.length == 1) {
String typeName = type[0].getName();
try {
Method declaredMethod = getDeclaredMethod(goalBuilder.getClass(), methodName, type[0]);
return declaredMethod;
} catch (NoSuchMethodException e) {
List<Method> declaredMethods = getClassMethod(goalBuilder.getClass(), methodName);
for (Method declaredMethod : declaredMethods) {
Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
if(parameterTypes.length == 1 && baseTypeList.contains(parameterTypes[0].getName())) {
return declaredMethod;
}
}
e.printStackTrace();
throw new NoSuchMethodException("在"+ goalBuilder.getClass().getName() +"中没有"+ methodName +"方法,请检查proto文件是否跟bean定义的字段一致");
}
} else {
return getDeclaredMethod(goalBuilder.getClass(),methodName, type);
}
}
private static Method hasListAddMethond(Method[] declaredMethods, String targetAddMethodName, Class targetClass) {
return Arrays.stream(declaredMethods)
.filter(method -> method.getName().equals(targetAddMethodName))
.filter(method -> method.getGenericParameterTypes().length == 1
&& method.getGenericParameterTypes()[0].getTypeName().equals(targetClass.getName()))
.findFirst()
.orElse(null);
}
private static String upperCaseFirstLetter(String word) {
try {
return String.valueOf(word.charAt(0)).toUpperCase() + word.substring(1);
} catch (StringIndexOutOfBoundsException e) {
System.out.println(word);
throw e;
}
}
static List<Field> getClassField(Class cur_class) {
String class_name = cur_class.getName();
Field[] obj_fields = cur_class.getDeclaredFields();
List<Field> collect = Arrays.stream(obj_fields).collect(Collectors.toList());
//Method[] methods = cur_class.getDeclaredMethods();
if (cur_class.getSuperclass() != null && cur_class.getSuperclass() != Object.class) {
collect.addAll(getClassField(cur_class.getSuperclass()));
}
return collect;
}
static List<Method> getClassMethod(Class cur_class, String methodName) {
Method[] methods = cur_class.getDeclaredMethods();
List<Method> collect = Arrays.stream(methods)
.filter(method -> method.getName().equals(methodName)).collect(Collectors.toList());
if (cur_class.getSuperclass() != null && cur_class.getSuperclass() != Object.class) {
collect.addAll(getClassMethod(cur_class.getSuperclass(), methodName));
}
return collect;
}
static Method getDeclaredMethod(Class cur_class, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException{
Method result = null;
try {
result = cur_class.getDeclaredMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e) {
// e.printStackTrace();
}
if (result == null && (cur_class.getSuperclass() != null && cur_class.getSuperclass() != Object.class)) {
result = getDeclaredMethod(cur_class.getSuperclass(), methodName);
if(result == null) {
String content = null;
if(parameterTypes != null) {
for (Class<?> parameterType : parameterTypes) {
content += parameterType.getSimpleName();
}
}
throw new NoSuchMethodException("在" + cur_class.getName() + "中递归找不到该方法" + methodName + "(" + content + ")");
}
}
return result;
}
}
3、20190725 增加了对继承类的bean转化支持,代码增加之后不怎么优雅,有空之后再重构。
新的工具类代码如下
//----旧代码已删
举个可支持的继承类的栗子
import lombok.Data;
import org.sq.gameDemo.svr.common.protoUtil.ProtoField;
import org.sq.gameDemo.svr.game.skills.model.Skill;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
*场景角色单位
*/
@Data
public class UserEntity {
private Long id;
/**
* 玩家昵称
*/
private String name;
/**
* 从属用户
*/
private Integer userId;
/**
* 角色状态
*/
private Integer state;
/**
* 角色类型
*/
private Integer typeId;
/**
* 从属场景Id
*/
private Integer senceId;
/**
* 经验值
*/
private Integer exp;
// 当前使用技能的集合
@ProtoField(Ignore = true)
private Map<Integer, Skill> skillMap = new ConcurrentHashMap<>();
}
子类 player,也就是要转化的类
package org.sq.gameDemo.svr.game.characterEntity.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.sq.gameDemo.svr.common.protoUtil.ProtoField;
import org.sq.gameDemo.svr.game.roleAttribute.model.RoleAttribute;
import java.nio.channels.Channel;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 玩家类
*/
@Data
@EqualsAndHashCode(callSuper=true)
public class Player extends UserEntity implements Character {
/**
* 等级,根据经验计算得出
*/
private Integer level;
/**
* 根据配置进行计算
*/
private Long hp;
private Long mp;
/**
* 玩家战力,根据base技能属性进行计算
*/
private Long attack;
@ProtoField(Ignore = true)
private Map<Integer,RoleAttribute> roleAttributeMap = new ConcurrentHashMap<>();
/**
* 经验增加, 影响等级
* @param exp 经验
*/
public void addExp(Integer exp) {
this.setExp(this.getExp() + exp);
int newLevel = this.getExp() / 100;
// 如果等级发生变化,进行提示
if (newLevel != this.getLevel()) {
System.out.println("等级改变啦!!!");
this.setLevel(newLevel);
}
}
}
proto文件
syntax = "proto3";
import "EntityType.proto";
option java_package = "org.sq.gameDemo.common.proto";
option java_outer_classname = "PlayerPt";
//用户角色实体
message Player {
uint64 id = 1;
string name = 2;
int32 userId = 3;
int32 state = 4;
int32 typeId = 5;
int32 senceId = 6;
int32 exp = 7;
int32 level =8;
uint64 hp = 9;
uint64 mp = 10;
uint64 attack = 11;
}
//请求
message PlayerReqInfo {
uint64 msg_id = 1; //消息id
uint64 time = 2; //时间戳(单位:毫秒)
int32 typeId = 3;
int32 senceId = 4;
}
//响应
message PlayerRespInfo {
uint64 msg_id = 1; //消息id
int32 result = 2;
uint64 time = 3; //时间戳
string content = 4;
repeated EntityType type = 5;
repeated Player player = 6;
}
调用工具类就可以转化了。
结束
下面的代码为旧版本工具代码,不影响使用
2、20190717 在使用中发现有proto文件中有Enum的使用,故注解增加了Function,意图解决Enum转化问题
1、20190715 有…有bug, 更新一哈,后期版本添加bean内部自定义转化方法,并高优先执行
如果该工具用的很爽,证明你proto文件设计很面向对象,很解耦
在这个项目中,目前这个工具用的很爽
https://github.com/kdYY/sqGameSvr
ps:该文默认读者熟悉protobuf
直入正文, 总共2个类,一个是注解类,一个是工具类
注解类 @ProtoField有两个作用
- 1.标识某个bean的属性忽略转化 Ignore
- 2.标识某个List属性对应proto文件中的哪个属性名,对应proto文件哪个协议类
该类的创建模仿于fastjson中的JSONField, 源码如下:
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface ProtoField {
Boolean Ignore() default false;
Class TargetClass() default Void.class;
String TargetRepeatedName() default ""; //非基础类型可以省略
String Function() default ""; //针对某个特定属性优先执行的方法
}
工具类 ProtobufUtil
- 通过反射调用方法来进行偷懒。 当然了,一些很自定义的协议还是建议不要深度使用,会漏注入一些值,但是基础属性和基础List还是ok的, 简单命令。
直接上代码,注释写的很清晰,不甚了解的地方评论区留言
//----旧代码已删
工具类就此结束,怎么使用呢
1、举第一个大大的栗子
假设你想让SenceConfigMsg类转化为SenceMsg.proto 类,就可以使用了,
- 先给出SenceMsg.proto
syntax = "proto3";
import "EntityType.proto";
import "SenceEntity.proto";
import "UserEntity.proto";
import "Sence.proto";
option java_package = "org.sq.gameDemo******";
option java_outer_classname = "SenceMsgProto";
//请求
message SenceMsgRequestInfo {
uint64 msg_id = 1; //消息id
uint64 time = 2; //时间戳(单位:毫秒)
int32 senceId = 3; //获取全部写-1
}
//响应
message SenceMsgResponseInfo {
uint64 msg_id = 1; //消息id
int32 result = 2;
uint64 time = 3; //时间戳
string content = 4;
repeated EntityType entityType = 5;
repeated SenceEntity SenceEntity = 6;
repeated UserEntity UserEntity = 7;
}
- 给出SenceConfigMsg类
@Data //lombok插件
public class SenceConfigMsg {
@ProtoField(Ignore = true)
private int senceId;
@ProtoField(TargetClass = SenceEntityProto.SenceEntity.class)
private List<SenceEntity> senceEntities;
@ProtoField(TargetClass = UserEntityProto.UserEntity.class)
private List<UserEntity> userEntities;
}
开始使用…
SenceMsgProto.SenceMsgResponseInfo.Builder builder =
SenceMsgProto.SenceMsgResponseInfo.newBuilder();
SenceConfigMsg sence = new SenceConfigMsg();
//初始值自己补充...
sence.set();
//进行转换
ProtoBufUtil.transformProtoReturnBuilder(builder, sence);
SenceMsgProto.SenceMsgResponseInfo instance = builder.build();
2、 举第二个大大的栗子
有个怪物实体类EntityType,proto文件是这样的
syntax = "proto3";
option java_package = "org.sq.gameDemo.common.proto";
option java_outer_classname = "EntityTypeProto";
enum AttackType {
AP = 0;
AD = 1;
}
//实体类型
message EntityType {
int32 id = 1;
string name = 2;
int32 baseMP = 3;
int32 baseHP = 4;
AttackType type = 5;
}
//请求
message EntityTypeRequestInfo {
uint64 msg_id = 1; //消息id
uint64 time = 2; //时间戳(单位:毫秒)
int32 typeId = 3; //获取全部写-1
}
//响应
message EntityTypeResponseInfo {
uint64 msg_id = 1; //消息id
int32 result = 2;
uint64 time = 3; //时间戳
string content = 4;
repeated EntityType entityType = 5;
}
bean类是EntityType
package org.sq.gameDemo.svr.game.entity.model;
import lombok.Data;
import org.sq.gameDemo.common.proto.EntityTypeProto;
import org.sq.gameDemo.svr.common.protoUtil.ProtoObject;
@Data
public class EntityType {
private int id;
private String name;
private int baseMP;
private int baseHP;
@ProtoField ( TargetName = "type", Function = "getType", TargetClass = EntityTypeProto.EntityType.Builder.class)
private String typeName; //AP型, AD型
//这个方法会在bean转protobuf的时候进行执行
public void getType(EntityTypeProto.EntityType.Builder builder) {
builder.setType("AP".equals(this.getTypeName()) ? EntityTypeProto.AttackType.AP : EntityTypeProto.AttackType.AD);
}
}
EntityTypeProto.EntityType.Builder builder = EntityTypeProto.EntityType.newBuilder();
ProtoBufUtil.transformProtoReturnBuilder(builder, sence);
EntityTypeProto.EntityType instance = builder.build();
搞定…