生活随笔
收集整理的這篇文章主要介紹了
                                
从源代码角度看Struts2返回JSON数据的原理
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.                        
 
                                
                            
                            
                            2019獨角獸企業(yè)重金招聘Python工程師標準>>>  
   前面一篇文章其實只是介紹了如何在Struts2中返回JSON數據到客戶端的具體范例而無關其原理,內容與標題不符惹來標題黨嫌疑確實是筆者發(fā)文不夠嚴謹,目前已修改標題,與內容匹配。本文將從struts2-json插件的源碼角度出發(fā),結合之前的應用范例來說明struts2-json插件返回JSON數據的原理。 
  用winrar打開struts2-json-plugin-xx.jar(筆者使用版本為2.1.8.1),根目錄下有一個struts-plugin.xml,這個文件想必大家都很了解,不做過多介紹了。打開該文件,內容非常簡答,如下: 
   Xml代碼??    
 <?xml?version="1.0"?encoding="UTF-8"??>??  ??  <!DOCTYPE?struts?PUBLIC??  ????????"-//Apache?Software?Foundation//DTD?Struts?Configuration?2.0//EN"??  ????????"http://struts.apache.org/dtds/struts-2.0.dtd">??  ??  <struts>??  ????<package?name="json-default"?extends="struts-default">??  ????????<result-types>??  ????????????<result-type?name="json"?class="org.apache.struts2.json.JSONResult"/>??  ????????</result-types>??  ????????<interceptors>??  ????????????<interceptor?name="json"?class="org.apache.struts2.json.JSONInterceptor"/>??  ????????</interceptors>??  ????</package>??  </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字符串,然后發(fā)送給客戶端;org.apache.struts2.json.JSONInterceptor負責攔截客戶端到json-default包下的所有請求,并檢查客戶端提交的數據是否是JSON類型,如果是則根據指定配置來反序列化JSON數據到action中的bean中(說的有點簡單,其實該攔截器內部對數據做了很多判斷),攔截器不是本文的重點,介紹到此為止。看一張圖,或許能夠更加清晰明了的說明JSON插件執(zhí)行的流程: 
  ? 
  
  ? 
  下面重點說說org.apache.struts2.json.JSONResult。 
  首先看一下org.apache.struts2.json.JSONResult源碼的核心部分: 
  部分屬性 
   Java代碼??    
 private?String?defaultEncoding?=?"ISO-8859-1";//默認的編碼??  private?List<Pattern>?includeProperties;//被包含的屬性的正則表達式,這些屬性的值將被序列化為JSON字符串,傳送到客戶端??  private?List<Pattern>?excludeProperties;//被排除的屬性的正則表達式,這些屬性的值在對象序列化時將被忽略??  private?String?root;//根對象,即要被序列化的對象,如不指定,將序列化action中所有可被序列化的數據??  private?boolean?wrapWithComments;//是否包裝成注釋??  private?boolean?prefix;//前綴??  private?boolean?enableGZIP?=?false;//是否壓縮??  private?boolean?ignoreHierarchy?=?true;//是否忽略層次關系,即是否序列化對象父類中的屬性??  private?boolean?ignoreInterfaces?=?true;//是否忽略接口??  private?boolean?enumAsBean?=?false;//是否將枚舉類型作為一個bean處理??  private?boolean?excludeNullProperties?=?false;//是否排除空的屬性,即是否不序列化空值屬性??  private?int?statusCode;//HTTP狀態(tài)碼??  private?int?errorCode;//HTTP錯誤碼??  private?String?contentType;//內容類型,通常為application/json,在IE瀏覽器中會提示下載,可以通過參數配置<param?name="contentType">text/html</param>,則不提示下載??  private?String?wrapPrefix;//包裝前綴??  private?String?wrapSuffix;//包裝后綴??   ? 
  看一下上一篇文章中的相關參數配置: 
   Xml代碼??    
 <package?name="json"?extends="json-default"?namespace="/test">??  ????<action?name="testByAction"??  ????????????class="cn.ysh.studio.struts2.json.demo.action.UserAction"?method="testByAction">??  ????????<result?type="json">??  ????????????????<!--?這里指定將被Struts2序列化的屬性,該屬性在action中必須有對應的getter方法?-->??  ????????????????<!--?默認將會序列所有有返回值的getter方法的值,而無論該方法是否有對應屬性?-->??  ????????????????<param?name="root">dataMap</param>??  ????????????????<!--?指定是否序列化空的屬性?-->??  ????????????????<param?name="excludeNullProperties">true</param>??  ????????????????<!--?這里指定將序列化dataMap中的那些屬性?-->??  ????????????????<param?name="includeProperties">??  ????????????????????user.*??  ????????????????</param>??  ????????????????<!--?指定內容類型,默認為application/json,IE瀏覽器會提示下載?-->??  ????????????????<param?name="contentType">text/html</param>??  ????????????????<!--?這里指定將要從dataMap中排除那些屬性,這些排除的屬性將不被序列化,一半不與上邊的參數配置同時出現?-->??  ????????????????<param?name="excludeProperties">??  ????????????????????SUCCESS??  ????????????????</param>??  ????????</result>??  ????</action>??  </package>??   ? 
  配置中出現了JSONResult的部分屬性名,是的,JSONResult中的屬性都可以根據需要在struts.xml中配置對應參數以改變默認值來滿足我們的需要。 
  接下來看看它的兩個核心方法: 
   Java代碼??    
 public?void?execute(ActionInvocation?invocation)?throws?Exception?{??  ????????ActionContext?actionContext?=?invocation.getInvocationContext();??  ????????HttpServletRequest?request?=?(HttpServletRequest)?actionContext.get(StrutsStatics.HTTP_REQUEST);??  ????????HttpServletResponse?response?=?(HttpServletResponse)?actionContext.get(StrutsStatics.HTTP_RESPONSE);??  ??  ????????try?{??  ????????????String?json;??  ????????????Object?rootObject;??  ????????????//查找指定的需要序列化的對象,否則序列化整個action(上文包括前一篇文章中一提到過多次)??  ????????????if?(this.enableSMD)?{??  ????????????????//?generate?SMD??  ????????????????rootObject?=?this.writeSMD(invocation);??  ????????????}?else?{??  ????????????????//?generate?JSON??  ????????????????if?(this.root?!=?null)?{??  ????????????????????ValueStack?stack?=?invocation.getStack();??  ????????????????????rootObject?=?stack.findValue(this.root);??  ????????????????}?else?{??  ????????????????????rootObject?=?invocation.getAction();??  ????????????????}??  ????????????}??  ????????????//這是最核心的一行代碼,包括了如何從rootObject抽取"可以"被序列化的屬性的值,然后包裝稱JSON字符串并返回??  ????????????json?=?JSONUtil.serialize(rootObject,?excludeProperties,?includeProperties,?ignoreHierarchy,??  ????????????????????enumAsBean,?excludeNullProperties);??  ????????????//針對JSONP的一個成員方法??  ????????????json?=?addCallbackIfApplicable(request,?json);??  ??  ????????????boolean?writeGzip?=?enableGZIP?&&?JSONUtil.isGzipInRequest(request);??  ??  ????????????//該方法是org.apache.struts2.json.JSONResult的一個成員方法,用于將JSON字符串根據指定參數包裝后發(fā)送到客戶端??  ????????????writeToResponse(response,?json,?writeGzip);??  ??  ????????}?catch?(IOException?exception)?{??  ????????????LOG.error(exception.getMessage(),?exception);??  ????????????throw?exception;??  ????????}??  ????}??  ??  ????/**?  ?????*?負責根據相關參數配置,將制定JSON字符串發(fā)送到客戶端?  ?????*?@param?response?  ?????*?@param?json?  ?????*?@param?gzip?  ?????*?@throws?IOException?  ?????*/??  ????protected?void?writeToResponse(HttpServletResponse?response,?String?json,?boolean?gzip)??  ????????????throws?IOException?{??  ????????JSONUtil.writeJSONToResponse(new?SerializationParams(response,?getEncoding(),?isWrapWithComments(),??  ????????????????json,?false,?gzip,?noCache,?statusCode,?errorCode,?prefix,?contentType,?wrapPrefix,??  ????????????????wrapSuffix));??  ????}??   ? 
  恕筆者愚鈍,找了好多資料,始終不明白這里的"SMD"是個什么意思,所在這里包括下文,都將忽略"SMD"。 
  可以看到,Struts2序列化對象為JSON字符串的整個過程都被JSONUtil的serialize方法包辦了,所以有必要跟入這個方法一探究竟: 
   Java代碼??    
 /**?  ?*?Serializes?an?object?into?JSON,?excluding?any?properties?matching?any?of?  ?*?the?regular?expressions?in?the?given?collection.?  ?*??  ?*?@param?object?  ?*????????????to?be?serialized?  ?*?@param?excludeProperties?  ?*????????????Patterns?matching?properties?to?exclude?  ?*?@param?ignoreHierarchy?  ?*????????????whether?to?ignore?properties?defined?on?base?classes?of?the?  ?*????????????root?object?  ?*?@param?enumAsBean?  ?*????????????whether?to?serialized?enums?a?Bean?or?name=value?pair?  ?*?@return?JSON?string?  ?*?@throws?JSONException?  ?*/??  public?static?String?serialize(Object?object,?Collection<Pattern>?excludeProperties,??  ????????Collection<Pattern>?includeProperties,?boolean?ignoreHierarchy,?boolean?enumAsBean,??  ????????boolean?excludeNullProperties)?throws?JSONException?{??  ????JSONWriter?writer?=?new?JSONWriter();??  ????writer.setIgnoreHierarchy(ignoreHierarchy);??  ????writer.setEnumAsBean(enumAsBean);??  ????return?writer.write(object,?excludeProperties,?includeProperties,?excludeNullProperties);??  }??   ? 
  該方法還有一個重載的兄弟方法,只是少了boolean enumAsBean這個參數,我們并不關心它,這里不討論它。可以看到,這個方法更簡單:構建一個JSONWriter實例,注入兩個參數,然后調用該實例的write方法。我們進入JSONWriter,查看write方法的源碼: 
   Java代碼??    
 /**?  ?*?@param?object?  ?*????????????Object?to?be?serialized?into?JSON?  ?*?@return?JSON?string?for?object?  ?*?@throws?JSONException?  ?*/??  public?String?write(Object?object,?Collection<Pattern>?excludeProperties,??  ????????Collection<Pattern>?includeProperties,?boolean?excludeNullProperties)?throws?JSONException?{??  ????this.excludeNullProperties?=?excludeNullProperties;??  ????this.buf.setLength(0);??  ????this.root?=?object;??  ????this.exprStack?=?"";??  ????this.buildExpr?=?((excludeProperties?!=?null)?&&?!excludeProperties.isEmpty())??  ????????????||?((includeProperties?!=?null)?&&?!includeProperties.isEmpty());??  ????this.excludeProperties?=?excludeProperties;??  ????this.includeProperties?=?includeProperties;??  ????this.value(object,?null);??  ??  ????return?this.buf.toString();??  }??   ? 
  它同樣有一個重載的方法,我們同樣不關心,瀏覽整個方法,不難發(fā)現,它只是所做了一些賦值操作,然后將對象的序列化工作交給了value成員方法,那么我們進入value方法看一看:? 
   Java代碼??    
 /**?  ?*?Detect?cyclic?references?  ?*/??  private?void?value(Object?object,?Method?method)?throws?JSONException?{??  ????if?(object?==?null)?{??  ????????this.add("null");??  ??  ????????return;??  ????}??  ??  ????if?(this.stack.contains(object))?{??  ????????Class?clazz?=?object.getClass();??  ??  ????????//?cyclic?reference??  ????????if?(clazz.isPrimitive()?||?clazz.equals(String.class))?{??  ????????????this.process(object,?method);??  ????????}?else?{??  ????????????if?(LOG.isDebugEnabled())?{??  ????????????????LOG.debug("Cyclic?reference?detected?on?"?+?object);??  ????????????}??  ??  ????????????this.add("null");??  ????????}??  ??  ????????return;??  ????}??  ??  ????this.process(object,?method);??  }??   
  很簡潔,進入process方法 
   Java代碼??    
 /**?  ?*?Serialize?object?into?json?  ?*/??  private?void?process(Object?object,?Method?method)?throws?JSONException?{??  ????this.stack.push(object);??  ??  ????if?(object?instanceof?Class)?{??  ????????this.string(object);??  ????}?else?if?(object?instanceof?Boolean)?{??  ????????this.bool(((Boolean)?object).booleanValue());??  ????}?else?if?(object?instanceof?Number)?{??  ????????this.add(object);??  ????}?else?if?(object?instanceof?String)?{??  ????????this.string(object);??  ????}?else?if?(object?instanceof?Character)?{??  ????????this.string(object);??  ????}?else?if?(object?instanceof?Map)?{??  ????????this.map((Map)?object,?method);??  ????}?else?if?(object.getClass().isArray())?{??  ????????this.array(object,?method);??  ????}?else?if?(object?instanceof?Iterable)?{??  ????????this.array(((Iterable)?object).iterator(),?method);??  ????}?else?if?(object?instanceof?Date)?{??  ????????this.date((Date)?object,?method);??  ????}?else?if?(object?instanceof?Calendar)?{??  ????????this.date(((Calendar)?object).getTime(),?method);??  ????}?else?if?(object?instanceof?Locale)?{??  ????????this.string(object);??  ????}?else?if?(object?instanceof?Enum)?{??  ????????this.enumeration((Enum)?object);??  ????}?else?{??  ????????this.bean(object);??  ????}??  ??  ????this.stack.pop();??  }??   ? 
  發(fā)現它做了很多判斷,并結合不同的方法來支持不同的數據類型,那么從這里我們可以知道Struts-json-plugin支持哪些數據類型了。對于每一種支持的數據類型,Struts-json-plugin都有相應的方法來從從對象中抽取數據并封裝成JSON字符串,以Map為例,我們看一下map方法的源碼: 
   Java代碼??    
 ?/**?  ??*?Add?map?to?buffer?  ??*/??  ?private?void?map(Map?map,?Method?method)?throws?JSONException?{??  ?????//這是一個對象,按照JSON語法,應該以"{}"括起來??  his.add("{");??  ??  ?????Iterator?it?=?map.entrySet().iterator();??  ??  ?????boolean?warnedNonString?=?false;?//?one?report?per?map??  ?????boolean?hasData?=?false;??  ?????while?(it.hasNext())?{??  ?????????Map.Entry?entry?=?(Map.Entry)?it.next();??  //如果key不是String類型,將發(fā)出警告??  ?????????Object?key?=?entry.getKey();??  //當前屬性的OGNL表達式??  ?????????String?expr?=?null;??  ?????????if?(this.buildExpr)?{??  ?????????????if?(key?==?null)?{??  ?????????????????LOG.error("Cannot?build?expression?for?null?key?in?"?+?this.exprStack);??  ?????????????????continue;??  ?????????????}?else?{??  ????????//獲取完整的OGNL表達式??  ?????????????????expr?=?this.expandExpr(key.toString());??  ????????//是否是被排除的屬性??  ????????//如果你對上邊生成的OGNL表達式的格式有所了解,那么includeProperties和excludeProperties的正則配置絕對不是問題??  ?????????????????if?(this.shouldExcludeProperty(expr))?{??  ?????????????????????continue;??  ?????????????????}??  ????????//如果不被排除,則將當前屬性名壓入表達式棧(其實就是一個String而非傳統意義上的棧,此處是模擬,非常精巧的算法)??  ????????//該方法返回原來的表達式,稍后還將恢復該表達式到"棧"中??  ?????????????????expr?=?this.setExprStack(expr);??  ?????????????}??  ?????????}??  //如果還有數據,則以","風格,這是JSON的語法格式??  ?????????if?(hasData)?{??  ?????????????this.add(',');??  ?????????}??  ?????????hasData?=?true;??  //如果key不是String類型,將發(fā)出警告,且只警告一次??  ?????????if?(!warnedNonString?&&?!(key?instanceof?String))?{??  ?????????????LOG.warn("JavaScript?doesn't?support?non-String?keys,?using?toString()?on?"??  ?????????????????????+?key.getClass().getName());??  ?????????????warnedNonString?=?true;??  ?????????}??  ?????????this.value(key.toString(),?method);??  ?????????this.add(":");??  //遞歸抽取數據??  ?????????this.value(entry.getValue(),?method);??  //下一層的數據遞歸完成后,恢復表達式棧值為當前層的屬性名??  ?????????if?(this.buildExpr)?{??  ?????????????this.setExprStack(expr);??  ?????????}??  ?????}??  ??  ?????this.add("}");??  ?}??   ? 
  這個方法中比較重要的幾行代碼都做了注釋,不再贅述。過濾某些屬性,以使其不被序列化時struts2-JSON應用中非常常見的,比如在序列化一個用戶對象的時候,密碼信息時不應該被傳送到客戶端的,所以要排除掉。了解shouldExcludeProperty方法的過濾規(guī)則,可以幫助我們更好的使用此功能。源碼如下: 
   Java代碼??    
 private?boolean?shouldExcludeProperty(String?expr)?{??  ????????if?(this.excludeProperties?!=?null)?{??  ????????????for?(Pattern?pattern?:?this.excludeProperties)?{??  ????????????????if?(pattern.matcher(expr).matches())?{??  ????????????????????if?(LOG.isDebugEnabled())??  ????????????????????????LOG.debug("Ignoring?property?because?of?exclude?rule:?"?+?expr);??  ????????????????????return?true;??  ????????????????}??  ????????????}??  ????????}??  ??  ????????if?(this.includeProperties?!=?null)?{??  ????????????for?(Pattern?pattern?:?this.includeProperties)?{??  ????????????????if?(pattern.matcher(expr).matches())?{??  ????????????????????return?false;??  ????????????????}??  ????????????}??  ??  ????????????if?(LOG.isDebugEnabled())??  ????????????????LOG.debug("Ignoring?property?because?of?include?rule:??"?+?expr);??  ????????????return?true;??  ????????}??  ??  ????????return?false;??  ????}??   ? 
  非常簡單,就是簡單的正則匹配,如果有排除配置,則先判斷當前屬性是否被排除,如果沒有被排除,且有包含配置則檢查是否被包含,如果沒有被包含,則不序列化該屬性,如果沒有被排除且沒有包含配置,則將序列化該屬性。 
  源碼跟蹤到這里,已經沒有繼續(xù)下去的必要了,因為我們已經很清楚Struts2是如何將一個對象轉換成JSON字符串并返回客戶端的: 
  
1、收集用戶配置;
 2、JSONWriter通過判斷對象的類型來有針對性的抽取其中的屬性值,對于嵌套的對象則采用遞歸的方式來抽取,抽取的同時,包裝成符合JSON語法規(guī)范的字符串;
 3、JSONUtil.writeJSONToResponse將序列化的JSON字符串按照相關配置發(fā)送到客戶端;?
 ??
 不難看出,代碼邏輯清晰,簡單,樸素,沒有半點花巧和賣弄,但確實是非常的精巧,表現出作者扎實的編程功底和過人的邏輯思維能力。尤其是遞歸抽取嵌套對象的屬性值和獲取當前屬性的OGNL表達式的算法,堪稱經典! 
  通過以上的源碼跟蹤,我們很清楚的了解Struts2序列化對象的原理和過程,并對相關參數的配置有了深刻的體會。只是令人感到奇怪的是,他并沒有使用json-lib.xx.jar中的API接口,而是以字符串拼接的方式手動構建JSON字符串,我想原因可能是因為它要用正則表達式包含或排除某些屬性的原因吧,僅作猜測,還望高人指點。 
  有很多人說不知道includeProperties和excludeProperties的正則表達式該怎么配置,我想說其實很簡單,除了正則知識外,就是"對象名.屬性名",數組稍微不同,以為它有下標,所以是"數組對象名\[\d+\]\.屬性名"。如果這里覺得說的不清楚,可以閱讀以下JSONWriter中關于OGNL表達式是如何獲取的部分代碼,就會明白正則該如何寫了。 
  純屬個人理解,如有錯誤,煩請指正,不勝榮幸! 
  轉載自 :?http://www.yshjava.cn/post/329.html 
      
 
轉載于:https://my.oschina.net/u/129971/blog/131946
                            總結
                            
                                以上是生活随笔為你收集整理的从源代码角度看Struts2返回JSON数据的原理的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                            
                                如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。