如何对protobuf生成的Java对象进行参数校验

3,184 阅读3分钟

前言


时隔几月在博客方面,笔者并没有想写文的冲动,不过最近笔者负责一个新服务的开发,在过程中使用了protobuf相关技术。开发完成之后,由于在参数校验这块使用了大量的if-else进行校验,造成code review过程中被疯狂的diss。在此,咱们就话不多说,直接引出问题:如何对protobuf生成的java对象进行参数校验?

名词解释:

  • protobuf生成的java对象统称为pb对象。普通的java对象统称为pojo对象
  • hibernate validator统称为validator

正文


比如protobuf直接生成的对象request,一开始笔者是这样进行校验的:

if(!request.hasXXX()) {
  // do....
  throw new Exception();
} else if(request.hasXXX) {
  .....
  throw new Exception();
}
....以下省略多个if-else

为什么会使用这种复杂校验方式?一开始笔者也曾想过使用hibernate validator进行自动校验,但在实现过程中,发现pb对象体积较大,内部属性繁复无法对其加上validator校验注解。后来,笔者便想方设法让pb对象转成普通的java pojo对象,一旦实现无缝转换,那么就可以使用hibernate validator进行自动校验了。

所以,首先想到的就是spring或者apache中的beanutils工具,利用该工具可以实现相同属性名称的值转换,不过这里有一个缺陷:当基础数据类型的属性的值为null的时候,beanutils会进行赋值操作。

比如:Integer i = null,复制出来的属性会变成,Integer i = 0。此时,如果用转换后的pojo对象进行@NotNull校验,校验结果会直接通过,并不会抛出异常信息。在开发过程中,很多时候仅仅是需要null,而不是具有默认值的属性,那么此时spring或apache中的beanutils工具就不再适合了。

当然,apache的beanutils工具通过注册代理的方式可以解决null值变默认值问题。但最终笔者并没有采取beanutils工具,原因是apache的beanutils的copyProperties方法效率较低,并不推荐使用。beanutils注册方案

工具介绍:复制即可使用
    /**
     * 将ProtoBean对象转化为POJO对象
     *
     * @param targetClass 目标POJO对象的类类型
     * @param sourceMessage 含有数据的ProtoBean对象实例
     * @param <PojoType> 目标POJO对象的类类型范型
     * @return
     * @throws IOException
     */
    public static <PojoType> PojoType toBean(Message sourceMessage, Class<PojoType> targetClass) throws Exception {
        if (targetClass == null) {
            throw new IllegalArgumentException("No destination pojo class specified");
        }
        if (sourceMessage == null) {
            throw new IllegalArgumentException("protobuf message is no specified");
        }
        String json = JsonFormat.printer().print(sourceMessage);
        return new Gson().fromJson(json, targetClass);
    }


    /**
     * 将POJO对象转化为ProtoBean对象
     *
     * @param destBuilder 目标Message对象的Builder类
     * @param sourcePojoBean 含有数据的POJO对象
     * @return
     * @throws IOException
     */
    public static void toProto(Message.Builder destBuilder, Object sourcePojoBean) throws Exception {
        if (destBuilder == null) {
            throw new IllegalArgumentException
                    ("No destination message builder specified");
        }
        if (sourcePojoBean == null) {
            throw new IllegalArgumentException("No source pojo specified");
        }
        String json = new Gson().toJson(sourcePojoBean);
        JsonFormat.parser().merge(json, destBuilder);
    }

在使用该工具的时候,需要添加依赖:

<dependency>
	<groupId>com.google.protobuf</groupId>
	<artifactId>protobuf-java-util</artifactId>
	// 版本省略
</dependency>
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
</dependency>

由于该工具类并不是笔者所写,所以直接贴上原作链接

注意事项:pojo的对象的基本数据类型必须是包装类

最后,笔者还想说说关于pojo转pb对象问题,使用上面的工具中的toProto方法进行转换的时候,属性名称和属性数量要一致,不然会抛出异常。

结束语

经过前后好几天的探索,发现直接对pb对象直接进行校验是行不通的,一般会利用中介进行转换,这里就使用了pb->json->pojo的策略。笔者认为工具中的toBean方法最为重要,利用该方法可以实现必填项参数校验,这样极大减少if-else语句的数量。当然,toProto方法可以极大省略pb.setXX()方法的数量,也是挺不错的。 说实话,笔者真羡慕那些能自己造轮子的人...想起自己实现的工具,心里就一阵火大。