springMVC项目中实现Protocol Buffers对象自动转换 – fangzhangsc2006的专栏 – CSDN博客

本文旨在向读者分享springMVC项目中Protocol Buffers的一个使用技巧,前提是需要具备对springMVC和Protocol Buffers的基本了解。Protocol Buffers(以下简称PB)是谷歌推出的一种数据交换格式,高效、易扩展、跨语言(C++, Java, Python)。将它用于网络传输是一个不错的选择。我们服务器与客户端的通讯就是使用它来做序列化与反序

来源: springMVC项目中实现Protocol Buffers对象自动转换 – fangzhangsc2006的专栏 – CSDN博客

本文旨在向读者分享springMVC项目中Protocol Buffers的一个使用技巧,前提是需要具备对springMVC和Protocol Buffers的基本了解。

Protocol Buffers(以下简称PB)是谷歌推出的一种数据交换格式,高效、易扩展、跨语言(C++, JavaPython)。将它用于网络传输是一个不错的选择。我们服务器与客户端的通讯就是使用它来做序列化与反序列化的。

PB的使用方法大致是这样的:

1.服务器与客户端约定要交换的数据格式,然后用PB的语法将其定义为.proto文件。

2.用PB自带的编译器编译.proto文件,生成对应语言的代码文件,比如java的类文件。

3.生成好的代码包含了对象定义、对象的序列化与反序列化等功能。

PB的详细使用方法不是本文的讨论范畴,如需学习请移步【PB官网】【博客A】

我曾使用过一款与PB类似的开源项目Hessian,spring中集成了Hessian的服务出口类,相当于一个特殊的servlet,需要配置Web.xml和对应的-servlet.xml文件,它可以完成方法参数和返回值的自动转换。客户端通过代理工厂HessianProxyFactory访问服务器上的接口,就像访问本地方法一样方便,是货真价实的RPC(远程过程调用)。详细参考【Hessian官网】【博客B】

受惯性思想影响,我在初次使用PB时到处寻找其与springMVC结合的配置方法。几经折腾后才发现,PB的用法与Hessian很不一样。PB的用法更像是使用工具类,我们需要调用它的parseFrom、writeTo、toByteArray等方法实现对象与流的转换。因此有这些方法就够了,我们无需任何配置。在controller中的使用举例如下:

  1.         InputStream in = request.getInputStream();
  2.         UseClientInfo useClientInfo = UseClientInfo.parseFrom(in);

说明:这是从输入流中反序列化一个PB对象UseClientInfo

  1. byte[] content = useClientInfoBuilder.build().toByteArray();
  2. response.setContentType(“application/x-protobuf”);
  3. response.setContentLength(content.length);
  4. response.setCharacterEncoding(“utf-8”);
  5. OutputStream out = response.getOutputStream();
  6. out.write(content);
  7. out.flush();
  8. out.close();

说明:这是往输出流中写序列化后的对象

客户端也是类似的用法,从这里可以看出PB算不上是一个完整的RPC,它只是提供了序列化与反序列化的方法。与Hessian相比的优势是无需配置、易用、易扩展,劣势是集成度不够高,需要用户参与序列化/反序列化工作,在各个controller的各个接口中频繁重复这个工作着实让人厌烦。有没有一种方式可以让controller从这些工作中脱离,接口的入参和返回值直接使用PB对象就可以了呢?

答案是肯定的,接下来讲解通过扩展springMVC的AbstractHttpMessageConverter完成PB对象和流之间的自动转换。(ps:终于到重点了,我也知道有点啰嗦,不过我希望讲清楚一件事的来龙去脉^_^)。

【首先】扩展AbstractHttpMessageConverter,实现我们自己的转换器PBMessageConverter。

  1. package ……保密……;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.io.OutputStream;
  5. import java.lang.reflect.InvocationTargetException;
  6. import java.lang.reflect.Method;
  7. import java.nio.charset.Charset;
  8. import org.springframework.http.HttpInputMessage;
  9. import org.springframework.http.HttpOutputMessage;
  10. import org.springframework.http.MediaType;
  11. import org.springframework.http.converter.AbstractHttpMessageConverter;
  12. import org.springframework.http.converter.HttpMessageNotReadableException;
  13. import org.springframework.http.converter.HttpMessageNotWritableException;
  14. import com.google.protobuf.GeneratedMessage;
  15. /**
  16.  * pb对象转换,自动完成pb对象的序列化与反序列化,让controller的接口脱离这些重复工作
  17.  * @author shannon
  18.  * @time Dec 6, 2012 12:16:11 PM
  19.  * @email shannonchou@126.com
  20.  */
  21. public class PBMessageConverter extends AbstractHttpMessageConverter<GeneratedMessage> {
  22.     public PBMessageConverter() {
  23.         //设置该转换器支持的媒体类型
  24.         super(new MediaType(“application”“x-protobuf”, Charset.forName(“UTF-8”)));
  25.     }
  26.     @Override
  27.     protected GeneratedMessage readInternal(
  28.             Class<? extends GeneratedMessage> arg0, HttpInputMessage arg1)
  29.             throws IOException, HttpMessageNotReadableException {
  30.         Method parseMethod;
  31.         try {
  32.             //利用反射机制获得parseFrom方法
  33.             parseMethod = arg0.getDeclaredMethod(“parseFrom”, InputStream.class);
  34.         } catch (SecurityException e) {
  35.             e.printStackTrace();
  36.             return null;
  37.         } catch (NoSuchMethodException e) {
  38.             e.printStackTrace();
  39.             return null;
  40.         }
  41.         try {
  42.             //调用parseFrom方法从InputStream中反序列化PB对象
  43.             return (GeneratedMessage) parseMethod.invoke(arg0, arg1.getBody());
  44.         } catch (IllegalArgumentException e) {
  45.             e.printStackTrace();
  46.             return null;
  47.         } catch (IllegalAccessException e) {
  48.             e.printStackTrace();
  49.             return null;
  50.         } catch (InvocationTargetException e) {
  51.             e.printStackTrace();
  52.             return null;
  53.         }
  54.     }
  55.     @Override
  56.     protected boolean supports(Class<?> arg0) {
  57.         //如果是GeneratedMessage的子类则支持
  58.         return GeneratedMessage.class.isAssignableFrom(arg0);
  59.     }
  60.     @Override
  61.     protected void writeInternal(GeneratedMessage arg0, HttpOutputMessage arg1)
  62.             throws IOException, HttpMessageNotWritableException {
  63.         //不用自己设置type,父类会根据构造函数中给的type设置
  64.         //arg1.getHeaders().setContentType(new MediaType(“application”,”x-protobuf”, Charset.forName(“UTF-8”)));
  65.         OutputStream outputStream = arg1.getBody();
  66.         //自己直接设置contentLength会导致异常,覆盖父类的getContentLength()方法才是标准做法
  67.         //arg1.getHeaders().setContentLength(bytes.length);
  68.         arg0.writeTo(outputStream);
  69.         outputStream.flush();
  70.         outputStream.close();
  71.     }
  72.     @Override
  73.     protected Long getContentLength(GeneratedMessage t, MediaType contentType) {
  74.         return Long.valueOf(t.toByteArray().length);
  75.     }
  76. }

说明:

1.我们给AbstractHttpMessageConverter的泛型变量是GeneratedMessage,而它是所有生成的PB对象的父类,我们可以通过它来识别PB对象。

2.覆盖父类构造函数的同时需要给出支持的媒体类型,我这里设定的是”application/x-protobuf”,这要求客户端在建立连接时设置同样的类型。如下:

  1. URL target = new URL(url);
  2. HttpURLConnection conn = (HttpURLConnection) target.openConnection();
  3. conn.setRequestMethod(“GET”);
  4. conn.setRequestProperty(“Content-Type”“application/x-protobuf”);
  5. conn.setRequestProperty(“Accept”“application/x-protobuf”);

3.我的PBMessageConverter利用了泛型和反射机制,已经实现了所有PB对象的转换,没有与项目耦合的任何代码,如果需要的话可以直接copy去用,不用重写。

【其次】配置*-servlet.xml文件

  1. <mvc:annotation-driven>
  2.     <mvc:message-converters>
  3.         <bean class=“com.example.…保密….common.converter.PBMessageConverter” />
  4.     </mvc:message-converters>
  5. </mvc:annotation-driven>

说明:这样就将PBMessageConverter注册到了springMVC的转换器列表中。注意这里需要引入springMVC的“mvc”命名空间。

【最后】controller中通过如下方式使用

  1. @RequestMapping(“connected”)
  2. public void connected(@RequestBody UseClientInfo useClientInfo, HttpServletRequest request, HttpServletResponse response) throws IOException {
  3.     log.info(useClientInfo);
  4. }
  5. @ResponseBody
  6. @RequestMapping(“get_useclientinfo”)
  7. public UseClientInfo getUseClientInfo(HttpServletResponse response) throws IOException{
  8.     UseClientInfo.Builder useClientInfoBuilder = UseClientInfo.newBuilder();
  9.     ……业务逻辑略……
  10.     return useClientInfoBuilder.build();
  11. }

说明:

1.PB参数前加上@RequestBody注解,如果返回值是PB对象则加上@ResponseBody注解。

至此,我们的controller直接操作对象,再也不需要重复的进行流与对象的转换了。

【PB官网】:http://code.google.com/p/protobuf/

【博客A】:http://blog.csdn.NET/farmmer/article/details/2689049

【Hessian官网】:http://hessian.caucho.com/

【博客B】:http://blog.csdn.net/chenweitang123/article/details/6334097

分享到: 更多 (0)