1. 程式人生 > >[struts2.x] 探索struts值棧如何賦值給action成員變數

[struts2.x] 探索struts值棧如何賦值給action成員變數

接著上篇部落格論述:

測試到這裡,我要的結果已經有了,但是我還有個疑問,就是我並沒有指定map的實現類,struts2.x是用什麼策略去例項化這個map的?為了探索這個問題,我做了如下測試,這個測試的內容比上述還有意義得多O(∩_∩)O~。

首先,還是不指定map的實現類,修改getter方法,如下

public Map<String,Object> getMap() {
    System.out.println(map);
    return map;
}

即列印map,然後重新提交表格,檢視控制檯有如下結果

null
{age=[Ljava.lang.String;@75a032}
{age=[Ljava.lang.String;@75a032,code=[Ljava.lang.String;@686b45}
{name=[Ljava.lang.String;@15ab69d,age=[Ljava.lang.String;@75a032, code=[Ljava.lang.String;@686b45}
{sex=[Ljava.lang.String;@12bd412,name=[Ljava.lang.String;@15ab69d, age=[Ljava.lang.String;@75a032,code=[Ljava.lang.String;@686b45}
{sex=[Ljava.lang.String;@12bd412,name=[Ljava.lang.String;@15ab69d, age=[Ljava.lang.String;@75a032, code=[Ljava.lang.String;@686b45}
{sex=[Ljava.lang.String;@12bd412,name=[Ljava.lang.String;@15ab69d, age=[Ljava.lang.String;@75a032,code=[Ljava.lang.String;@686b45}

結果很有意思,相比剛才的控制檯輸出,多了5行,即這個map的getter方法被呼叫了5次。可以知道,最後一次是轉換成json資料前被呼叫的,而前面四次就是struts2.x注值到map前的呼叫。從當前的測試看,每次struts2.x為某個屬性賦值前,即呼叫屬性相應的setter方法前,都會先呼叫這個屬性的getter方法。為了證實這個還沒有理論證實的推斷,再次做個試驗,將map如下初始化,並加個計數器:

@ParentPackage(value = "json-default")
public class MapInputTestAction {
 
    private int i = 0;
   
    private Map<String,Object> map = new HashMap<String, Object>();
   
    @Action(value="/map/input", results= {@Result(name = ActionSupport.SUCCESS, type="json")})
    public String input() {
        System.out.println(getMap());
        return ActionSupport.SUCCESS;
    }
 
    public Map<String,Object> getMap() {
        System.out.println("getter[" + (i++) + "]" + map);
        return map;
    }
 
    public void setMap(Map<String, Object> map) {
        System.out.println("setter[" + (i++) + "]");
        this.map = map;
    }
   
}

控制檯輸出結果是:

getter[0]{}
getter[1]{age=[Ljava.lang.String;@5ee84a}
getter[2]{age=[Ljava.lang.String;@5ee84a,code=[Ljava.lang.String;@e45691}
getter[3]{name=[Ljava.lang.String;@ef513a,age=[Ljava.lang.String;@5ee84a, code=[Ljava.lang.String;@e45691}
getter[4]{sex=[Ljava.lang.String;@b48e49,name=[Ljava.lang.String;@ef513a, age=[Ljava.lang.String;@5ee84a,code=[Ljava.lang.String;@e45691}
{sex=[Ljava.lang.String;@b48e49,name=[Ljava.lang.String;@ef513a, age=[Ljava.lang.String;@5ee84a,code=[Ljava.lang.String;@e45691}
getter[5]{sex=[Ljava.lang.String;@b48e49,name=[Ljava.lang.String;@ef513a, age=[Ljava.lang.String;@5ee84a,code=[Ljava.lang.String;@e45691}

這個結果太令人迷糊了,呼叫getter的方式是和我們想象的一樣,但是居然沒有呼叫setter方法。是不是我哪裡理解錯了,難道setter方法並不是用來填充值的?

再改測試程式碼如下:

@ParentPackage(value = "json-default")
public class MapInputTestAction {
 
    private int i = 0;
    private Map<String,Object> map;
    private String message;  //使用簡單的
   
    @Action(value="/map/input", results= {@Result(name = ActionSupport.SUCCESS, type="json")})
    public String input() {
        System.out.println(getMap());
        return ActionSupport.SUCCESS;
    }
 
    public Map<String,Object> getMap() {
        System.out.println("getter[" + (i++) + "]" + map);
        return map;
    }
 
    public void setMap(Map<String, Object> map) {
        System.out.println("setter[" + (i++) + "]");
        this.map = map;
    }
 
    @JSON(serialize = false)
    public StringgetMessage() {
        System.out.println("getMessage[" + (i++) + "]");
        return message;
    }
 
    public void setMessage(String message) {
        System.out.println("setMessage[" + (i++) + "]");
        this.message = message;
    }
   
}

前端jsp新增message輸入框,<label>資訊:</label><inputtype="text"name="message"/><br/>提交form後檢視console輸出如下

getter[0]null
setter[1]
getter[2]{age=[Ljava.lang.String;@192a93b}
getter[3]{age=[Ljava.lang.String;@192a93b,code=[Ljava.lang.String;@1a474c9}
getter[4]{name=[Ljava.lang.String;@1ef3f1e,age=[Ljava.lang.String;@192a93b, code=[Ljava.lang.String;@1a474c9}
setMessage[5]
getter[6]{sex=[Ljava.lang.String;@1a11b9a,name=[Ljava.lang.String;@1ef3f1e, age=[Ljava.lang.String;@192a93b,code=[Ljava.lang.String;@1a474c9}
{sex=[Ljava.lang.String;@1a11b9a,name=[Ljava.lang.String;@1ef3f1e, age=[Ljava.lang.String;@192a93b,code=[Ljava.lang.String;@1a474c9}
getter[7]{sex=[Ljava.lang.String;@1a11b9a,name=[Ljava.lang.String;@1ef3f1e, age=[Ljava.lang.String;@192a93b,code=[Ljava.lang.String;@1a474c9}

可以發現,我們不初始化map,則在呼叫map的getter方法後會先去呼叫setter方法,然後交由struts傳入例項,具體struts是如何去建立例項和賦值的,可以通過在getter方法丟擲RuntimeException來打出呼叫方法堆疊,然後從DefaultActionInvocation類開始跟蹤,ParametersInterceptor.doIntercept(),直至跟蹤到OgnlRuntime類的invokeMethod(),getNullHandler()等。對於當前的我來說,只能看出個大概流程,具體細節還有待提升功力後再做詳細分析。光從上面的對比可以看出是這麼個流程:

如果注入的屬性是基礎型別,沒有物件巢狀屬性的,即從表示式看沒有obj.pro,只是簡單的pro,struts直接呼叫其setter方法賦值,因為並不需要對這個物件初始化。但是,如果是物件巢狀屬性,如我們的例子map.code,則需要有map例項,才能對該物件屬性進行值注入。所以,在getter方法呼叫發現該物件為空後,struts預設會呼叫setter方法對該物件例項化,並將這個例項放在上下文;如果該物件不為空,則直接對該物件的屬性賦值。如果真是這樣,那麼我們取消map的getter方法,則最終的map只能填充一個值,如下:

setter[0]
setter[1]
setter[2]
setter[3]
{sex=[Ljava.lang.String;@1c933b0}

這就是為什麼我們在使用Action的時候,對基本物件如果不做返回,有時候並不需要getter方法,但是如果有對物件屬性賦值的情況,則必須給定getter方法。

這個結論不一定準確,先記錄下來,待回頭能仔細看懂原始碼再做解析。

小結:

物理學中非常重視實驗學,不僅視實驗為理論的校驗器,甚至視實驗為理論的指路燈,比如著名的丁肇中先生髮現的J粒子,就是再理論未曾預測的情況下做實驗發現的現象。在物理界,實驗發現的現象,往往又會苛求理論的相應構建。這就是物理學的實驗和理論雙生子。它們相互指引,有時候你前我後,有時候你後我前,有時候齊頭並進,但總是不離不棄。

今天這個探索主要是基於表象,一步步做探索感知。對於我當前這樣的水平,我很享受這種過程,像是解連環鎖一樣,一步步逼近答案。IT本身更像是一個實戰學科,和物理架構不算特別吻合,但是邏輯和現象的交織判斷和推引,也是非常相似的。