前言
时隔几月在博客方面,笔者并没有想写文的冲动,不过最近笔者负责一个新服务的开发,在过程中使用了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()方法的数量,也是挺不错的。 说实话,笔者真羡慕那些能自己造轮子的人...想起自己实现的工具,心里就一阵火大。