一种java对象转换成protobuf对象通用方法(bean转protobuf 类)

本文围绕Protobuf工具类在小游戏后端的应用展开。介绍了该工具类的更新历程,包括增加对继承类的bean转化支持、解决Enum转化问题等。还阐述了注解类@ProtoField的作用,以及工具类ProtobufUtil的使用方法,并通过实例进行说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

更新,第一次看的小伙伴直接跳到下面的"直入正文", 再回来看吧


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 类,就可以使用了,

  1. 先给出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;
}
  1. 给出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();

搞定…


评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值