生活随笔
收集整理的這篇文章主要介紹了
从源代码角度看Struts2返回JSON数据的原理
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
前面一篇文章其實(shí)只是介紹了如何在Struts2中返回JSON數(shù)據(jù)到客戶(hù)端的具體范例而無(wú)關(guān)其原理,內(nèi)容與標(biāo)題不符惹來(lái)標(biāo)題黨嫌疑確實(shí)是筆者發(fā)文不夠嚴(yán)謹(jǐn),目前已修改標(biāo)題,與內(nèi)容匹配。本文將從struts2-json插件的源碼角度出發(fā),結(jié)合之前的應(yīng)用范例來(lái)說(shuō)明struts2-json插件返回JSON數(shù)據(jù)的原理。
用winrar打開(kāi)struts2-json-plugin-xx.jar(筆者使用版本為2.1.8.1),根目錄下有一個(gè)struts-plugin.xml,這個(gè)文件想必大家都很了解,不做過(guò)多介紹了。打開(kāi)該文件,內(nèi)容非常簡(jiǎn)答,如下:
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數(shù)據(jù)到客戶(hù)端,那么action所在的package必須繼承自json-default包,原因就在上邊的配置文件中:這里的配置文件指定了該插件的包名為json-default,所以要使用該插件的功能,就必須繼承自該包——json-default。
上面的配置文件中,配置了兩個(gè)類(lèi):org.apache.struts2.json.JSONResult和org.apache.struts2.json.JSONInterceptor,前者是結(jié)果類(lèi)型,后者是一個(gè)攔截器。簡(jiǎn)單說(shuō)一下,org.apache.struts2.json.JSONResult負(fù)責(zé)將action中的“某些”(通過(guò)相關(guān)參數(shù)可以指定,前文已有詳述)或action中所有"可獲取"(有g(shù)etter方法的屬性或一個(gè)有返回值的getter方法的返回值)數(shù)據(jù)序列化成JSON字符串,然后發(fā)送給客戶(hù)端;org.apache.struts2.json.JSONInterceptor負(fù)責(zé)攔截客戶(hù)端到j(luò)son-default包下的所有請(qǐng)求,并檢查客戶(hù)端提交的數(shù)據(jù)是否是JSON類(lèi)型,如果是則根據(jù)指定配置來(lái)反序列化JSON數(shù)據(jù)到action中的bean中(說(shuō)的有點(diǎn)簡(jiǎn)單,其實(shí)該攔截器內(nèi)部對(duì)數(shù)據(jù)做了很多判斷),攔截器不是本文的重點(diǎn),介紹到此為止。看一張圖,或許能夠更加清晰明了的說(shuō)明JSON插件執(zhí)行的流程:
?
?
下面重點(diǎn)說(shuō)說(shuō)org.apache.struts2.json.JSONResult。
首先看一下org.apache.struts2.json.JSONResult源碼的核心部分:
部分屬性
Java代碼??
private?String?defaultEncoding?=?"ISO-8859-1";//默認(rèn)的編碼?? private?List<Pattern>?includeProperties;//被包含的屬性的正則表達(dá)式,這些屬性的值將被序列化為JSON字符串,傳送到客戶(hù)端?? private?List<Pattern>?excludeProperties;//被排除的屬性的正則表達(dá)式,這些屬性的值在對(duì)象序列化時(shí)將被忽略?? private?String?root;//根對(duì)象,即要被序列化的對(duì)象,如不指定,將序列化action中所有可被序列化的數(shù)據(jù)?? private?boolean?wrapWithComments;//是否包裝成注釋?? private?boolean?prefix;//前綴?? private?boolean?enableGZIP?=?false;//是否壓縮?? private?boolean?ignoreHierarchy?=?true;//是否忽略層次關(guān)系,即是否序列化對(duì)象父類(lèi)中的屬性?? private?boolean?ignoreInterfaces?=?true;//是否忽略接口?? private?boolean?enumAsBean?=?false;//是否將枚舉類(lèi)型作為一個(gè)bean處理?? private?boolean?excludeNullProperties?=?false;//是否排除空的屬性,即是否不序列化空值屬性?? private?int?statusCode;//HTTP狀態(tài)碼?? private?int?errorCode;//HTTP錯(cuò)誤碼?? private?String?contentType;//內(nèi)容類(lèi)型,通常為application/json,在IE瀏覽器中會(huì)提示下載,可以通過(guò)參數(shù)配置<param?name="contentType">text/html</param>,則不提示下載?? private?String?wrapPrefix;//包裝前綴?? private?String?wrapSuffix;//包裝后綴?? ?
看一下上一篇文章中的相關(guān)參數(shù)配置:
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中必須有對(duì)應(yīng)的getter方法?-->?? ????????????????<!--?默認(rèn)將會(huì)序列所有有返回值的getter方法的值,而無(wú)論該方法是否有對(duì)應(yīng)屬性?-->?? ????????????????<param?name="root">dataMap</param>?? ????????????????<!--?指定是否序列化空的屬性?-->?? ????????????????<param?name="excludeNullProperties">true</param>?? ????????????????<!--?這里指定將序列化dataMap中的那些屬性?-->?? ????????????????<param?name="includeProperties">?? ????????????????????user.*?? ????????????????</param>?? ????????????????<!--?指定內(nèi)容類(lèi)型,默認(rèn)為application/json,IE瀏覽器會(huì)提示下載?-->?? ????????????????<param?name="contentType">text/html</param>?? ????????????????<!--?這里指定將要從dataMap中排除那些屬性,這些排除的屬性將不被序列化,一半不與上邊的參數(shù)配置同時(shí)出現(xiàn)?-->?? ????????????????<param?name="excludeProperties">?? ????????????????????SUCCESS?? ????????????????</param>?? ????????</result>?? ????</action>?? </package>?? ?
配置中出現(xiàn)了JSONResult的部分屬性名,是的,JSONResult中的屬性都可以根據(jù)需要在struts.xml中配置對(duì)應(yīng)參數(shù)以改變默認(rèn)值來(lái)滿(mǎn)足我們的需要。
接下來(lái)看看它的兩個(gè)核心方法:
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;?? ????????????//查找指定的需要序列化的對(duì)象,否則序列化整個(gè)action(上文包括前一篇文章中一提到過(guò)多次)?? ????????????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抽取"可以"被序列化的屬性的值,然后包裝稱(chēng)JSON字符串并返回?? ????????????json?=?JSONUtil.serialize(rootObject,?excludeProperties,?includeProperties,?ignoreHierarchy,?? ????????????????????enumAsBean,?excludeNullProperties);?? ????????????//針對(duì)JSONP的一個(gè)成員方法?? ????????????json?=?addCallbackIfApplicable(request,?json);?? ?? ????????????boolean?writeGzip?=?enableGZIP?&&?JSONUtil.isGzipInRequest(request);?? ?? ????????????//該方法是org.apache.struts2.json.JSONResult的一個(gè)成員方法,用于將JSON字符串根據(jù)指定參數(shù)包裝后發(fā)送到客戶(hù)端?? ????????????writeToResponse(response,?json,?writeGzip);?? ?? ????????}?catch?(IOException?exception)?{?? ????????????LOG.error(exception.getMessage(),?exception);?? ????????????throw?exception;?? ????????}?? ????}?? ?? ????/**? ?????*?負(fù)責(zé)根據(jù)相關(guān)參數(shù)配置,將制定JSON字符串發(fā)送到客戶(hù)端? ?????*?@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"是個(gè)什么意思,所在這里包括下文,都將忽略"SMD"。
可以看到,Struts2序列化對(duì)象為JSON字符串的整個(gè)過(guò)程都被JSONUtil的serialize方法包辦了,所以有必要跟入這個(gè)方法一探究竟:
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);?? }?? ?
該方法還有一個(gè)重載的兄弟方法,只是少了boolean enumAsBean這個(gè)參數(shù),我們并不關(guān)心它,這里不討論它。可以看到,這個(gè)方法更簡(jiǎn)單:構(gòu)建一個(gè)JSONWriter實(shí)例,注入兩個(gè)參數(shù),然后調(diào)用該實(shí)例的write方法。我們進(jìn)入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();?? }?? ?
它同樣有一個(gè)重載的方法,我們同樣不關(guān)心,瀏覽整個(gè)方法,不難發(fā)現(xiàn),它只是所做了一些賦值操作,然后將對(duì)象的序列化工作交給了value成員方法,那么我們進(jìn)入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);?? }??
很簡(jiǎn)潔,進(jìn)入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ā)現(xiàn)它做了很多判斷,并結(jié)合不同的方法來(lái)支持不同的數(shù)據(jù)類(lèi)型,那么從這里我們可以知道Struts-json-plugin支持哪些數(shù)據(jù)類(lèi)型了。對(duì)于每一種支持的數(shù)據(jù)類(lèi)型,Struts-json-plugin都有相應(yīng)的方法來(lái)從從對(duì)象中抽取數(shù)據(jù)并封裝成JSON字符串,以Map為例,我們看一下map方法的源碼:
Java代碼??
?/**? ??*?Add?map?to?buffer? ??*/?? ?private?void?map(Map?map,?Method?method)?throws?JSONException?{?? ?????//這是一個(gè)對(duì)象,按照J(rèn)SON語(yǔ)法,應(yīng)該以"{}"括起來(lái)?? 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類(lèi)型,將發(fā)出警告?? ?????????Object?key?=?entry.getKey();?? //當(dāng)前屬性的OGNL表達(dá)式?? ?????????String?expr?=?null;?? ?????????if?(this.buildExpr)?{?? ?????????????if?(key?==?null)?{?? ?????????????????LOG.error("Cannot?build?expression?for?null?key?in?"?+?this.exprStack);?? ?????????????????continue;?? ?????????????}?else?{?? ????????//獲取完整的OGNL表達(dá)式?? ?????????????????expr?=?this.expandExpr(key.toString());?? ????????//是否是被排除的屬性?? ????????//如果你對(duì)上邊生成的OGNL表達(dá)式的格式有所了解,那么includeProperties和excludeProperties的正則配置絕對(duì)不是問(wèn)題?? ?????????????????if?(this.shouldExcludeProperty(expr))?{?? ?????????????????????continue;?? ?????????????????}?? ????????//如果不被排除,則將當(dāng)前屬性名壓入表達(dá)式棧(其實(shí)就是一個(gè)String而非傳統(tǒng)意義上的棧,此處是模擬,非常精巧的算法)?? ????????//該方法返回原來(lái)的表達(dá)式,稍后還將恢復(fù)該表達(dá)式到"棧"中?? ?????????????????expr?=?this.setExprStack(expr);?? ?????????????}?? ?????????}?? //如果還有數(shù)據(jù),則以","風(fēng)格,這是JSON的語(yǔ)法格式?? ?????????if?(hasData)?{?? ?????????????this.add(',');?? ?????????}?? ?????????hasData?=?true;?? //如果key不是String類(lèi)型,將發(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(":");?? //遞歸抽取數(shù)據(jù)?? ?????????this.value(entry.getValue(),?method);?? //下一層的數(shù)據(jù)遞歸完成后,恢復(fù)表達(dá)式棧值為當(dāng)前層的屬性名?? ?????????if?(this.buildExpr)?{?? ?????????????this.setExprStack(expr);?? ?????????}?? ?????}?? ?? ?????this.add("}");?? ?}?? ?
這個(gè)方法中比較重要的幾行代碼都做了注釋,不再贅述。過(guò)濾某些屬性,以使其不被序列化時(shí)struts2-JSON應(yīng)用中非常常見(jiàn)的,比如在序列化一個(gè)用戶(hù)對(duì)象的時(shí)候,密碼信息時(shí)不應(yīng)該被傳送到客戶(hù)端的,所以要排除掉。了解shouldExcludeProperty方法的過(guò)濾規(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;?? ????}?? ?
非常簡(jiǎn)單,就是簡(jiǎn)單的正則匹配,如果有排除配置,則先判斷當(dāng)前屬性是否被排除,如果沒(méi)有被排除,且有包含配置則檢查是否被包含,如果沒(méi)有被包含,則不序列化該屬性,如果沒(méi)有被排除且沒(méi)有包含配置,則將序列化該屬性。
源碼跟蹤到這里,已經(jīng)沒(méi)有繼續(xù)下去的必要了,因?yàn)槲覀円呀?jīng)很清楚Struts2是如何將一個(gè)對(duì)象轉(zhuǎn)換成JSON字符串并返回客戶(hù)端的:
1、收集用戶(hù)配置;
2、JSONWriter通過(guò)判斷對(duì)象的類(lèi)型來(lái)有針對(duì)性的抽取其中的屬性值,對(duì)于嵌套的對(duì)象則采用遞歸的方式來(lái)抽取,抽取的同時(shí),包裝成符合JSON語(yǔ)法規(guī)范的字符串;
3、JSONUtil.writeJSONToResponse將序列化的JSON字符串按照相關(guān)配置發(fā)送到客戶(hù)端;?
??
不難看出,代碼邏輯清晰,簡(jiǎn)單,樸素,沒(méi)有半點(diǎn)花巧和賣(mài)弄,但確實(shí)是非常的精巧,表現(xiàn)出作者扎實(shí)的編程功底和過(guò)人的邏輯思維能力。尤其是遞歸抽取嵌套對(duì)象的屬性值和獲取當(dāng)前屬性的OGNL表達(dá)式的算法,堪稱(chēng)經(jīng)典!
通過(guò)以上的源碼跟蹤,我們很清楚的了解Struts2序列化對(duì)象的原理和過(guò)程,并對(duì)相關(guān)參數(shù)的配置有了深刻的體會(huì)。只是令人感到奇怪的是,他并沒(méi)有使用json-lib.xx.jar中的API接口,而是以字符串拼接的方式手動(dòng)構(gòu)建JSON字符串,我想原因可能是因?yàn)樗谜齽t表達(dá)式包含或排除某些屬性的原因吧,僅作猜測(cè),還望高人指點(diǎn)。
有很多人說(shuō)不知道includeProperties和excludeProperties的正則表達(dá)式該怎么配置,我想說(shuō)其實(shí)很簡(jiǎn)單,除了正則知識(shí)外,就是"對(duì)象名.屬性名",數(shù)組稍微不同,以為它有下標(biāo),所以是"數(shù)組對(duì)象名\[\d+\]\.屬性名"。如果這里覺(jué)得說(shuō)的不清楚,可以閱讀以下JSONWriter中關(guān)于OGNL表達(dá)式是如何獲取的部分代碼,就會(huì)明白正則該如何寫(xiě)了。
純屬個(gè)人理解,如有錯(cuò)誤,煩請(qǐng)指正,不勝榮幸!
轉(zhuǎn)載自 :?http://www.yshjava.cn/post/329.html
轉(zhuǎn)載于:https://my.oschina.net/u/129971/blog/131946
總結(jié)
以上是生活随笔為你收集整理的从源代码角度看Struts2返回JSON数据的原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。