ParametersInterceptor 該攔截器處於defaultStack第十五的位置,這裡跳過了一個攔截器,先講ParametersInterceptor再講第十四個ActionMappingParametersInteceptor因為ActionMappingParametersInteceptor繼承自ParametersInterceptor,只是賦值參數源不一樣,所以只要理解了ParametersInterceptor,再理解ActionMappingParametersInteceptor就很簡單了。 ParametersInterceptor攔截器又繼承自MethodFilterInterceptor,其主要功能是把ActionContext中的請求參數設置到ValueStack中,如果棧頂是當前Action則把請求參數設置到了Action中,如果棧頂是一個model(Action實現了ModelDriven接口)則把參數設置到了model中。 前面講解的一個PrepareInterceptor也是繼承自MethodFilterInterceptor,當時沒有講解,是因為PrepareInterceptor在defaultStack沒有進行方法過濾,ParametersInterceptor攔截器在defaultStack中對方法也沒有進行過濾,所以暫時也不講放到AnnotationValidationInterceptor講解,先把該攔截器的doIntercept方法看成是以前的intercept方法。但該攔截器配置了請求參數過濾,下面是ParametersInterceptor攔截器在defaultStack中配置的參數過濾參數: [html] <interceptor-ref name="params"> <param name="excludeParams">dojo\..*,^struts\..*</param> </interceptor-ref> 這裡傳遞了一個名為excludeParams的參數,其值為用逗號(,)分隔的正則表達式,有用指定哪些類型的請求參數不需要設置到ValueStack中 下面是該攔截器的doIntercept方法源碼: [java] @Override public String doIntercept(ActionInvocation invocation) throws Exception { Object action = invocation.getAction();//獲取當前執行的Action對象 if (!(action instanceof NoParameters)) {//判斷Action是否實現了NoParameters接口,實現該接口表示該Action沒有任何請求參數 ActionContext ac = invocation.getInvocationContext();//獲取ActionContext對象 final Map<String, Object> parameters = retrieveParameters(ac);//獲取請求參數Map //省略... if (parameters != null) {//如果請求參數不為null Map<String, Object> contextMap = ac.getContextMap();//獲取ActionContext內部的context Map,即OgnlContext對象 try { //省略... ValueStack stack = ac.getValueStack();//獲取值棧 setParameters(action, stack, parameters);//為值棧設置參數 } finally { //省略... } } } return invocation.invoke();//調用下一個攔截器 } 上面的方法中省略了一些非重要代碼,剩下的代碼是不是很簡單呢?retrieveParameters方法用於獲取請求參數Map,下面是源碼: [java] protected Map<String, Object> retrieveParameters(ActionContext ac) { return ac.getParameters(); }//簡單到不用解釋 setParameters方法才是該攔截器的主要邏輯,現在進入該方法: [java] protected void setParameters(Object action, ValueStack stack, final Map<String, Object> parameters) { ParameterNameAware parameterNameAware = (action instanceof ParameterNameAware) ? (ParameterNameAware) action : null;//判斷Action有無實現ParameterNameAware接口 Map<String, Object> params; Map<String, Object> acceptableParameters;//合法參數集合 //判斷參數設置是否有序,ordered默認為false,即無序 if (ordered) { params = new TreeMap<String, Object>(getOrderedComparator());//如果有序則要獲取比較器 acceptableParameters = new TreeMap<String, Object>(getOrderedComparator()); params.putAll(parameters); } else { params = new TreeMap<String, Object>(parameters); acceptableParameters = new TreeMap<String, Object>(); } //迭代請求參數 for (Map.Entry<String, Object> entry : params.entrySet()) { String name = entry.getKey(); //判斷參數是否合法,如果Action實現了ParameterNameAware則acceptableName(name)返回true且parameterNameAware.acceptableParameterName(name) //也返回true該參數才是合法的;如果Action沒有實現ParameterNameAware則參數是否合法由acceptableName(name)方法決定 boolean acceptableName = acceptableName(name) && (parameterNameAware == null || parameterNameAware.acceptableParameterName(name)); //如果參數合法 if (acceptableName) { acceptableParameters.put(name, entry.getValue());//把合法參數添加到合法參數集合中 } } ValueStack newStack = valueStackFactory.createValueStack(stack); //省略... for (Map.Entry<String, Object> entry : acceptableParameters.entrySet()) {//迭代合法參數 String name = entry.getKey();//參數名 Object value = entry.getValue();//參數值 try { newStack.setValue(name, value);//將該參數設置到ValueStack中 } catch (RuntimeException e) { //省略... } } //省略... //看該方法的名稱是將合法參數添加到ActionContext中,但在該攔截器中,該方法為空實現,無任何代碼 //該方法被聲明為protected,即子類可以覆蓋該方法以改變行為 addParametersToContext(ActionContext.getContext(), acceptableParameters); } 根據上面的注釋大家應該可以發現該setParameters方法邏輯還是很明確的,就是先判斷提交過來的參數是否合法,因為提交過來的參數會影響到值棧所以struts2要對提交過來的參數進行合法性檢查,以防止惡意用戶的攻擊,凡是請求參數中表達式中含有等號(=),逗號(,),#號(#)的都是非法表達式,現在就去看一下具體是如何判斷一個參數是否合法的。上面注釋也講到了,如果Action實現了ParameterNameAware,即要判斷ParameterNameAware接口中聲明的acceptableParameterName(name)方法(邏輯由自己實現)也要判斷該攔截器的acceptableName(name)方法,我們這裡假設Action沒有實現ParameterNameAware接口,參數是否合法由acceptableName(name)方法決定,下面是該方法源碼: [java] protected boolean acceptableName(String name) { //調用isAccepted與isExcluded方法判斷 if (isAccepted(name) && !isExcluded(name)) { return true; } return false; } isAccepted與isExcluded方法源碼: [java] protected boolean isAccepted(String paramName) { if (!this.acceptParams.isEmpty()) { for (Pattern pattern : acceptParams) { Matcher matcher = pattern.matcher(paramName); if (matcher.matches()) { return true; } } return false; } else return acceptedPattern.matcher(paramName).matches(); } protected boolean isExcluded(String paramName) { if (!this.excludeParams.isEmpty()) { for (Pattern pattern : excludeParams) { Matcher matcher = pattern.matcher(paramName); if (matcher.matches()) { return true; } } } return false; } 上面說到了該攔截器配置了參數過濾,配置了一個名為excludeParams的參數,用於指定哪些參數要排除,即不合法,我們傳遞的時候是字符串在設置該字符串的時候該攔截器會對該字符串進行解析轉化成相應的Pattern對象以用於正則表達式校驗,而isAccepted與isExcluded方法中就是在用這些正則表達式進行檢驗,邏輯很簡單,就說這麼多。 該攔截器還有一個ordered屬性,其值默認為false,即將參數設置到ValueStack中是無序的,如果ordered為true,則將請求參數設置到ValueStack是有先後順序的,所以在創建TreeMap的時候,通過getOrderedComparator()方法傳遞進了比較器對象,下面是getOrderedComparator()方法源碼: [java] protected Comparator<String> getOrderedComparator() { return rbCollator; } static final Comparator<String> rbCollator = new Comparator<String>() { public int compare(String s1, String s2) { int l1 = 0, l2 = 0; for (int i = s1.length() - 1; i >= 0; i--) { if (s1.charAt(i) == '.') l1++;//通過循環得到請求參數key中'.'符號的個數 } for (int i = s2.length() - 1; i >= 0; i--) { if (s2.charAt(i) == '.') l2++;//通過循環得到請求參數key中'.'符號的個數 } return l1 < l2 ? -1 : (l2 < l1 ? 1 : s1.compareTo(s2)); } }; rbCollator對象就是一個比較器,是用匿名內部類實現的,我們在提交請求參數的時候經常會寫成xxx.yyy.zzz=value,而這個比較器就是根據請求參數key中含'.'符號的個數來進行比較的,'.'符號越少排在越前,即'.'符號越少的會越先設置到ValueStack中。關於這個在該攔截器的文檔中還舉了例子,大家可以去看一下。 最終進行參數賦值是調用的ValueStack的setValue方法,該方法內部使用是OGNL表達式引擎進行賦值的,雖然內部非常復雜,但我們只需要知道OGNL表達式引擎在把請求參數設置到ValueStack中時,是從棧頂往棧底尋找有相應setter方法的對象,如果正在賦值的參數在ValueStack找到了一個對象有setter方法則把該參數的值賦給該對象,如果沒有找到則繼承往棧底尋找,直到找到為止,如果找到棧底還是沒有找到也就沒有賦值成功。