`
xinklabi
  • 浏览: 1560412 次
  • 性别: Icon_minigender_1
  • 来自: 吉林
文章分类
社区版块
存档分类
最新评论

从源代码角度看Struts2返回JSON数据的原理(转)

 
阅读更多

前面一篇文章其实只是介绍了如何在Struts2中返回JSON数据到客户端的具体范例而无关其原理,内容与标题不符惹来标题党嫌疑确实是笔者发文 不够严谨,目前已修改标题,与内容匹配。本文将从struts2-json插件的源码角度出发,结合之前的应用范例来说明struts2-json插件返 回JSON数据的原理。

 

用winrar打开struts2-json-plugin-xx.jar(笔者使用版本为2.1.8.1),根目录下有一个struts-plugin.xml,这个文件想必大家都很了解,不做过多介绍了。打开该文件,内容非常简答,如下:

 

Xml代码  收藏代码
  1. <? xml   version = "1.0"   encoding = "UTF-8"   ?>   
  2.   
  3. <!DOCTYPE struts PUBLIC  
  4.         "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"  
  5.         "http://struts.apache.org/dtds/struts-2.0.dtd">   
  6.   
  7. < struts >   
  8.     < package   name = "json-default"   extends = "struts-default" >   
  9.         < result-types >   
  10.             < result-type   name = "json"   class = "org.apache.struts2.json.JSONResult" />   
  11.         </ result-types >   
  12.         < interceptors >   
  13.             < interceptor   name = "json"   class = "org.apache.struts2.json.JSONInterceptor" />   
  14.         </ interceptors >   
  15.     </ package >   
  16. </ struts >   

 

 

前文提到,如果要使用Struts2返回JSON数据到客户端,那么action所在的package必须继承自json-default包,原因 就在上边的配置文件中:这里的配置文件指定了该插件的包名为json-default,所以要使用该插件的功能,就必须继承自该包——json- default。

 

上面的配置文件中,配置了两个类:org.apache.struts2.json.JSONResult和 org.apache.struts2.json.JSONInterceptor,前者是结果类型,后者是一个拦截器。简单说一 下,org.apache.struts2.json.JSONResult负责将action中的“某些”(通过相关参数可以指定,前文已有详述)或 action中所有"可获取"(有getter方法的属性或一个有返回值的getter方法的返回值)数据序列化成JSON字符串,然后发送给客户 端;org.apache.struts2.json.JSONInterceptor负责拦截客户端到json-default包下的所有请求,并检查 客户端提交的数据是否是JSON类型,如果是则根据指定配置来反序列化JSON数据到action中的bean中(说的有点简单,其实该拦截器内部对数据 做了很多判断),拦截器不是本文的重点,介绍到此为止。看一张图,或许能够更加清晰明了的说明JSON插件执行的流程:

 

 

JSON插件执行时序图

 

 

下面重点说说org.apache.struts2.json.JSONResult。

 

首先看一下org.apache.struts2.json.JSONResult源码的核心部分:

 

部分属性

 

Java代码  收藏代码
  1. private  String defaultEncoding =  "ISO-8859-1" ; //默认的编码   
  2. private  List<Pattern> includeProperties; //被包含的属性的正则表达式,这些属性的值将被序列化为JSON字符串,传送到客户端   
  3. private  List<Pattern> excludeProperties; //被排除的属性的正则表达式,这些属性的值在对象序列化时将被忽略   
  4. private  String root; //根对象,即要被序列化的对象,如不指定,将序列化action中所有可被序列化的数据   
  5. private   boolean  wrapWithComments; //是否包装成注释   
  6. private   boolean  prefix; //前缀   
  7. private   boolean  enableGZIP =  false ; //是否压缩   
  8. private   boolean  ignoreHierarchy =  true ; //是否忽略层次关系,即是否序列化对象父类中的属性   
  9. private   boolean  ignoreInterfaces =  true ; //是否忽略接口   
  10. private   boolean  enumAsBean =  false ; //是否将枚举类型作为一个bean处理   
  11. private   boolean  excludeNullProperties =  false ; //是否排除空的属性,即是否不序列化空值属性   
  12. private   int  statusCode; //HTTP状态码   
  13. private   int  errorCode; //HTTP错误码   
  14. private  String contentType; //内容类型,通常为application/json,在IE浏览器中会提示下载,可以通过参数配置<param name="contentType">text/html</param>,则不提示下载   
  15. private  String wrapPrefix; //包装前缀   
  16. private  String wrapSuffix; //包装后缀   

 

 

看一下上一篇文章中的相关参数配置:

 

Xml代码  收藏代码
  1. < package   name = "json"   extends = "json-default"   namespace = "/test" >   
  2.     < action   name = "testByAction"   
  3.             class = "cn.ysh.studio.struts2.json.demo.action.UserAction"   method = "testByAction" >   
  4.         < result   type = "json" >   
  5.                 <!-- 这里指定将被Struts2序列化的属性,该属性在action中必须有对应的getter方法 -->   
  6.                 <!-- 默认将会序列所有有返回值的getter方法的值,而无论该方法是否有对应属性 -->   
  7.                 < param   name = "root" > dataMap </ param >   
  8.                 <!-- 指定是否序列化空的属性 -->   
  9.                 < param   name = "excludeNullProperties" > true </ param >   
  10.                 <!-- 这里指定将序列化dataMap中的那些属性 -->   
  11.                 < param   name = "includeProperties" >   
  12.                     user.*  
  13.                 </ param >   
  14.                 <!-- 指定内容类型,默认为application/json,IE浏览器会提示下载 -->   
  15.                 < param   name = "contentType" > text/html </ param >   
  16.                 <!-- 这里指定将要从dataMap中排除那些属性,这些排除的属性将不被序列化,一半不与上边的参数配置同时出现 -->   
  17.                 < param   name = "excludeProperties" >   
  18.                     SUCCESS  
  19.                 </ param >   
  20.         </ result >   
  21.     </ action >   
  22. </ package >   

 

配置中出现了JSONResult的部分属性名,是的,JSONResult中的属性都可以根据需要在struts.xml中配置对应参数以改变默认值来满足我们的需要。

 

 

接下来看看它的两个核心方法:

 

 

Java代码  收藏代码
  1. public   void  execute(ActionInvocation invocation)  throws  Exception {  
  2.         ActionContext actionContext = invocation.getInvocationContext();  
  3.         HttpServletRequest request = (HttpServletRequest) actionContext.get(StrutsStatics.HTTP_REQUEST);  
  4.         HttpServletResponse response = (HttpServletResponse) actionContext.get(StrutsStatics.HTTP_RESPONSE);  
  5.   
  6.         try  {  
  7.             String json;  
  8.             Object rootObject;  
  9.             //查找指定的需要序列化的对象,否则序列化整个action(上文包括前一篇文章中一提到过多次)   
  10.             if  ( this .enableSMD) {  
  11.                 // generate SMD   
  12.                 rootObject = this .writeSMD(invocation);  
  13.             } else  {  
  14.                 // generate JSON   
  15.                 if  ( this .root !=  null ) {  
  16.                     ValueStack stack = invocation.getStack();  
  17.                     rootObject = stack.findValue(this .root);  
  18.                 } else  {  
  19.                     rootObject = invocation.getAction();  
  20.                 }  
  21.             }  
  22.             //这是最核心的一行代码,包括了如何从rootObject抽取"可以"被序列化的属性的值,然后包装称JSON字符串并返回   
  23.             json = JSONUtil.serialize(rootObject, excludeProperties, includeProperties, ignoreHierarchy,  
  24.                     enumAsBean, excludeNullProperties);  
  25.             //针对JSONP的一个成员方法   
  26.             json = addCallbackIfApplicable(request, json);  
  27.   
  28.             boolean  writeGzip = enableGZIP && JSONUtil.isGzipInRequest(request);  
  29.   
  30.             //该方法是org.apache.struts2.json.JSONResult的一个成员方法,用于将JSON字符串根据指定参数包装后发送到客户端   
  31.             writeToResponse(response, json, writeGzip);  
  32.   
  33.         } catch  (IOException exception) {  
  34.             LOG.error(exception.getMessage(), exception);  
  35.             throw  exception;  
  36.         }  
  37.     }  
  38.   
  39.     /**  
  40.      * 负责根据相关参数配置,将制定JSON字符串发送到客户端  
  41.      * @param response  
  42.      * @param json  
  43.      * @param gzip  
  44.      * @throws IOException  
  45.      */   
  46.     protected   void  writeToResponse(HttpServletResponse response, String json,  boolean  gzip)  
  47.             throws  IOException {  
  48.         JSONUtil.writeJSONToResponse(new  SerializationParams(response, getEncoding(), isWrapWithComments(),  
  49.                 json, false , gzip, noCache, statusCode, errorCode, prefix, contentType, wrapPrefix,  
  50.                 wrapSuffix));  
  51.     }  

 

恕笔者愚钝,找了好多资料,始终不明白这里的"SMD"是个什么意思,所在这里包括下文,都将忽略"SMD"。

 

 

可以看到,Struts2序列化对象为JSON字符串的整个过程都被JSONUtil的serialize方法包办了,所以有必要跟入这个方法一探究竟:

 

Java代码  收藏代码
  1. /**  
  2.  * Serializes an object into JSON, excluding any properties matching any of  
  3.  * the regular expressions in the given collection.  
  4.  *   
  5.  * @param object  
  6.  *            to be serialized  
  7.  * @param excludeProperties  
  8.  *            Patterns matching properties to exclude  
  9.  * @param ignoreHierarchy  
  10.  *            whether to ignore properties defined on base classes of the  
  11.  *            root object  
  12.  * @param enumAsBean  
  13.  *            whether to serialized enums a Bean or name=value pair  
  14.  * @return JSON string  
  15.  * @throws JSONException  
  16.  */   
  17. public   static  String serialize(Object object, Collection<Pattern> excludeProperties,  
  18.         Collection<Pattern> includeProperties, boolean  ignoreHierarchy,  boolean  enumAsBean,  
  19.         boolean  excludeNullProperties)  throws  JSONException {  
  20.     JSONWriter writer = new  JSONWriter();  
  21.     writer.setIgnoreHierarchy(ignoreHierarchy);  
  22.     writer.setEnumAsBean(enumAsBean);  
  23.     return  writer.write(object, excludeProperties, includeProperties, excludeNullProperties);  
  24. }  

 

 

该方法还有一个重载的兄弟方法,只是少了boolean enumAsBean这个参数,我们并不关心它,这里不讨论它。可以看到,这个方法更简单:构建一个JSONWriter实例,注入两个参数,然后调用该 实例的write方法。我们进入JSONWriter,查看write方法的源码:

 

 

Java代码  收藏代码
  1. /**  
  2.  * @param object  
  3.  *            Object to be serialized into JSON  
  4.  * @return JSON string for object  
  5.  * @throws JSONException  
  6.  */   
  7. public  String write(Object object, Collection<Pattern> excludeProperties,  
  8.         Collection<Pattern> includeProperties, boolean  excludeNullProperties)  throws  JSONException {  
  9.     this .excludeNullProperties = excludeNullProperties;  
  10.     this .buf.setLength( 0 );  
  11.     this .root = object;  
  12.     this .exprStack =  "" ;  
  13.     this .buildExpr = ((excludeProperties !=  null ) && !excludeProperties.isEmpty())  
  14.             || ((includeProperties != null ) && !includeProperties.isEmpty());  
  15.     this .excludeProperties = excludeProperties;  
  16.     this .includeProperties = includeProperties;  
  17.     this .value(object,  null );  
  18.   
  19.     return   this .buf.toString();  
  20. }  

 

 

它同样有一个重载的方法,我们同样不关心,浏览整个方法,不难发现,它只是所做了一些赋值操作,然后将对象的序列化工作交给了value成员方法,那么我们进入value方法看一看: 

 

 

Java代码  收藏代码
  1. /**  
  2.  * Detect cyclic references  
  3.  */   
  4. private   void  value(Object object, Method method)  throws  JSONException {  
  5.     if  (object ==  null ) {  
  6.         this .add( "null" );  
  7.   
  8.         return ;  
  9.     }  
  10.   
  11.     if  ( this .stack.contains(object)) {  
  12.         Class clazz = object.getClass();  
  13.   
  14.         // cyclic reference   
  15.         if  (clazz.isPrimitive() || clazz.equals(String. class )) {  
  16.             this .process(object, method);  
  17.         } else  {  
  18.             if  (LOG.isDebugEnabled()) {  
  19.                 LOG.debug("Cyclic reference detected on "  + object);  
  20.             }  
  21.   
  22.             this .add( "null" );  
  23.         }  
  24.   
  25.         return ;  
  26.     }  
  27.   
  28.     this .process(object, method);  
  29. }  

 

 

很简洁,进入process方法

 

 

Java代码  收藏代码
  1. /**  
  2.  * Serialize object into json  
  3.  */   
  4. private   void  process(Object object, Method method)  throws  JSONException {  
  5.     this .stack.push(object);  
  6.   
  7.     if  (object  instanceof  Class) {  
  8.         this .string(object);  
  9.     } else   if  (object  instanceof  Boolean) {  
  10.         this .bool(((Boolean) object).booleanValue());  
  11.     } else   if  (object  instanceof  Number) {  
  12.         this .add(object);  
  13.     } else   if  (object  instanceof  String) {  
  14.         this .string(object);  
  15.     } else   if  (object  instanceof  Character) {  
  16.         this .string(object);  
  17.     } else   if  (object  instanceof  Map) {  
  18.         this .map((Map) object, method);  
  19.     } else   if  (object.getClass().isArray()) {  
  20.         this .array(object, method);  
  21.     } else   if  (object  instanceof  Iterable) {  
  22.         this .array(((Iterable) object).iterator(), method);  
  23.     } else   if  (object  instanceof  Date) {  
  24.         this .date((Date) object, method);  
  25.     } else   if  (object  instanceof  Calendar) {  
  26.         this .date(((Calendar) object).getTime(), method);  
  27.     } else   if  (object  instanceof  Locale) {  
  28.         this .string(object);  
  29.     } else   if  (object  instanceof  Enum) {  
  30.         this .enumeration((Enum) object);  
  31.     } else  {  
  32.         this .bean(object);  
  33.     }  
  34.   
  35.     this .stack.pop();  
  36. }  

 

 

发现它做了很多判断,并结合不同的方法来支持不同的数据类型,那么从这里我们可以知道Struts-json-plugin支持哪些数据类型了。对 于每一种支持的数据类型,Struts-json-plugin都有相应的方法来从从对象中抽取数据并封装成JSON字符串,以Map为例,我们看一下 map方法的源码:

 

 

Java代码  收藏代码
  1.   /**  
  2.   * Add map to buffer  
  3.   */   
  4.  private   void  map(Map map, Method method)  throws  JSONException {  
  5.      //这是一个对象,按照JSON语法,应该以"{}"括起来   
  6. his.add("{" );  
  7.   
  8.      Iterator it = map.entrySet().iterator();  
  9.   
  10.      boolean  warnedNonString =  false // one report per map   
  11.      boolean  hasData =  false ;  
  12.      while  (it.hasNext()) {  
  13.          Map.Entry entry = (Map.Entry) it.next();  
  14. //如果key不是String类型,将发出警告   
  15.          Object key = entry.getKey();  
  16. //当前属性的OGNL表达式   
  17.          String expr = null ;  
  18.          if  ( this .buildExpr) {  
  19.              if  (key ==  null ) {  
  20.                  LOG.error("Cannot build expression for null key in "  +  this .exprStack);  
  21.                  continue ;  
  22.              } else  {  
  23.         //获取完整的OGNL表达式   
  24.                  expr = this .expandExpr(key.toString());  
  25.         //是否是被排除的属性   
  26.         //如果你对上边生成的OGNL表达式的格式有所了解,那么includeProperties和excludeProperties的正则配置绝对不是问题   
  27.                  if  ( this .shouldExcludeProperty(expr)) {  
  28.                      continue ;  
  29.                  }  
  30.         //如果不被排除,则将当前属性名压入表达式栈(其实就是一个String而非传统意义上的栈,此处是模拟,非常精巧的算法)   
  31.         //该方法返回原来的表达式,稍后还将恢复该表达式到"栈"中   
  32.                  expr = this .setExprStack(expr);  
  33.              }  
  34.          }  
  35. //如果还有数据,则以","风格,这是JSON的语法格式   
  36.          if  (hasData) {  
  37.              this .add( ',' );  
  38.          }  
  39.          hasData = true ;  
  40. //如果key不是String类型,将发出警告,且只警告一次   
  41.          if  (!warnedNonString && !(key  instanceof  String)) {  
  42.              LOG.warn("JavaScript doesn't support non-String keys, using toString() on "   
  43.                      + key.getClass().getName());  
  44.              warnedNonString = true ;  
  45.          }  
  46.          this .value(key.toString(), method);  
  47.          this .add( ":" );  
  48. //递归抽取数据   
  49.          this .value(entry.getValue(), method);  
  50. //下一层的数据递归完成后,恢复表达式栈值为当前层的属性名   
  51.          if  ( this .buildExpr) {  
  52.              this .setExprStack(expr);  
  53.          }  
  54.      }  
  55.   
  56.      this .add( "}" );  
  57.  }  

 

这个方法中比较重要的几行代码都做了注释,不再赘述。过滤某些属性,以使其不被序列化时struts2-JSON应用中非常常见的,比如在序列化一 个用户对象的时候,密码信息时不应该被传送到客户端的,所以要排除掉。了解shouldExcludeProperty方法的过滤规则,可以帮助我们更好 的使用此功能。源码如下:

 

Java代码  收藏代码
  1. private   boolean  shouldExcludeProperty(String expr) {  
  2.         if  ( this .excludeProperties !=  null ) {  
  3.             for  (Pattern pattern :  this .excludeProperties) {  
  4.                 if  (pattern.matcher(expr).matches()) {  
  5.                     if  (LOG.isDebugEnabled())  
  6.                         LOG.debug("Ignoring property because of exclude rule: "  + expr);  
  7.                     return   true ;  
  8.                 }  
  9.             }  
  10.         }  
  11.   
  12.         if  ( this .includeProperties !=  null ) {  
  13.             for  (Pattern pattern :  this .includeProperties) {  
  14.                 if  (pattern.matcher(expr).matches()) {  
  15.                     return   false ;  
  16.                 }  
  17.             }  
  18.   
  19.             if  (LOG.isDebugEnabled())  
  20.                 LOG.debug("Ignoring property because of include rule:  "  + expr);  
  21.             return   true ;  
  22.         }  
  23.   
  24.         return   false ;  
  25.     }  

 

非常简单,就是简单的正则匹配,如果有排除配置,则先判断当前属性是否被排除,如果没有被排除,且有包含配置则检查是否被包含,如果没有被包含,则不序列化该属性,如果没有被排除且没有包含配置,则将序列化该属性。

源码跟踪到这里,已经没有继续下去的必要了,因为我们已经很清楚Struts2是如何将一个对象转换成JSON字符串并返回客户端的:


1、收集用户配置;
2、JSONWriter通过判断对象的类型来有针对性的抽取其中的属性值,对于嵌套的对象则采用递归的方式来抽取,抽取的同时,包装成符合JSON语法规范的字符串;
3、JSONUtil.writeJSONToResponse将序列化的JSON字符串按照相关配置发送到客户端;
 
  
不难看出,代码逻辑清晰,简单,朴素,没有半点花巧和卖弄,但确实是非常的精巧,表现出作者扎实的编程功底和过人的逻辑思维能力。尤其是递归抽取嵌套对象的属性值和获取当前属性的OGNL表达式的算法,堪称经典!

 

通过以上的源码跟踪,我们很清楚的了解Struts2序列化对象的原理和过程,并对相关参数的配置有了深刻的体会。只是令人感到奇怪的是,他并没有 使用json-lib.xx.jar中的API接口,而是以字符串拼接的方式手动构建JSON字符串,我想原因可能是因为它要用正则表达式包含或排除某些 属性的原因吧,仅作猜测,还望高人指点。

 

有很多人说不知道includeProperties和excludeProperties的正则表达式该怎么配置,我想说其实很简单,除了正则知 识外,就是"对象名.属性名",数组稍微不同,以为它有下标,所以是"数组对象名\[\d+\]\.属性名"。如果这里觉得说的不清楚,可以阅读以下 JSONWriter中关于OGNL表达式是如何获取的部分代码,就会明白正则该如何写了。

 

 

纯属个人理解,如有错误,烦请指正,不胜荣幸!

 

 

 

 

原创文章,转载请注明出处:http://yshjava.iteye.com/blog/1333602

 

分享到:
评论

相关推荐

    struts2-json-plugin-2.3.24-API文档-中文版.zip

    赠送源代码:struts2-json-plugin-2.3.24-sources.jar; 赠送Maven依赖信息文件:struts2-json-plugin-2.3.24.pom; 包含翻译后的API文档:struts2-json-plugin-2.3.24-javadoc-API文档-中文(简体)版.zip; Maven...

    最简单的:Struts2+JSON+JQUERY+AJAX 完整DEMO源代码

    最简单的:Struts2+JSON+JQUERY+AJAX 完整DEMO源代码,相信一定是你此刻最想找的!

    struts2与json插件(2)

    JsonUtils.java 源代码. 博文链接:https://flym.iteye.com/blog/183562

    基于Struts2和Spring的网络硬盘系统,批量上传文件和在线解压,优秀源代码!

    技术架构:Struts2(json-plugin) + Spring2 + ExtJS2.2 无数据库 已经实现的功能: 1.多文件队列批量上传,友好的上传进度条,完整的上传进度信息. 2.实现了文件在线压缩解压功能(可以压缩成zip格式,可以直接解压缩RAR...

    extjs tree + json+struts2示例源代码

    extjs tree + json+struts2示例源代码extjs tree + json+struts2示例源代码

    Extjs Tree + JSON + Struts2 示例源代码

    Extjs Tree + JSON + Struts2 示例源代码

    模具排程序系管理系统Struts2+Json+mysql

    公司正在使用的模具排程系统,全是自己手敲写的代码,良心资源,架上数据库马上可以运行。struts2+Json异步更新+mysql.有助于新人学习的Java语言.

    struts2+json+ajax

    基础不好的,还是看源代码方便,推荐像我一样的新人学习。

    jsp+struts2完整实例

    使用struts2处理前后台的数据,并且在jsp页面使用json和dom树显示数据的完全源代码

    java图片上传源代码

    struts2 图片上传源代码。数据返回 json 网页开发

    struts项目学习笔记

    基于AOP(面向切面编程)思想的拦截器机制,更易扩展(不修改源代码的条件下,增强代码功能) 更强大、更易用输入校验功能 整合Ajax支持:json插件 Struts2的今生前世: 1.早期开发模型Servlet+JSP+JavaBean显得...

    struts2-json-plugin-2.3.24.jar中文-英文对照文档.zip

    源代码下载地址:【***-sources.jar下载地址(官方地址+国内镜像地址).txt】 # 本文件关键字: 中文-英文对照文档,中英对照文档,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,...

    JAVA上百实例源码以及开源项目源代码

    2个目标文件 摘要:Java源码,网络相关,UDP 基于JAVA的UDP服务器模型源代码,内含UDP服务器端模型和UDP客户端模型两个小程序,向JAVA初学者演示UDP C/S结构的原理。 简单聊天软件CS模式 2个目标文件 一个简单的CS模式...

    京东商城高仿源代码

    基于Apache Http协议和SOAP协议请求服务器的Android项目,使用ActivityGroup组件实现导航.服务器是由Struts、Hibernate、Spring、CXF 2.3四大框架实现. 使用Gson封装的Json数据交换格式.

    struts自我学习过程程序以及说明

    本资源包括struts的学习程序,能够完全运行,当然,我是完全调试运行出来的,都是源代码原封上传,还有说明文档。还有自我总结资料,放到下一个文件夹当中上传,这里压缩只有这么多了,希望能去下载,那个全部是文档...

    位置跟踪查询源代码

    调用百度地图实现位置跟踪.工作原理APP1获取所在位置的经纬度,然后立即上传到服务器,APP2想要查看APP1的位置打开APP就可以在地图上查看到了。

Global site tag (gtag.js) - Google Analytics