ParametersInterceptor攔截器其主要功能是把ActionContext中的請求引數設定到ValueStack中,如果棧頂是當前Action則把請求引數設定到了Action中,如果棧頂是一個model(Action實現了ModelDriven介面)則把引數設定到了model中。
下面是該攔截器的doIntercept方法原始碼:
@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();//呼叫下一個攔截器 }
setParameters方法才是該攔截器的主要邏輯,現在進入該方法:
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)方法決定,下面是該方法原始碼:
protected boolean acceptableName(String name) { //呼叫isAccepted與isExcluded方法判斷 if (isAccepted(name) && !isExcluded(name)) { return true; } return false; }
isAccepted與isExcluded方法原始碼:
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方法中就是在用這些正規表示式進行檢驗,邏輯很簡單,就說這麼多。
最終進行引數賦值是呼叫的ValueStack的setValue方法,該方法內部使用是OGNL表示式引擎進行賦值的,雖然內部非常複雜,但我們只需要知道OGNL表示式引擎在把請求引數設定到ValueStack中時,是從棧頂往棧底尋找有相應setter方法的物件,如果正在賦值的引數在ValueStack找到了一個物件有setter方法則把該引數的值賦給該物件,如果沒有找到則繼承往棧底尋找,直到找到為止,如果找到棧底還是沒有找到也就沒有賦值成功。
到此該攔截器就講解完畢了,最後呼叫invocation.invoke();呼叫下一個攔截器......