欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

struts2 18拦截器详解(十五)

程序员文章站 2022-04-15 13:05:46
ParametersInterceptor      该拦截器处于defaultStack第十五的位置,这里跳过了一个拦截器,先讲Paramete...
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方法则把该参数的值赋给该对象,如果没有找到则继承往栈底寻找,直到找到为止,如果找到栈底还是没有找到也就没有赋值成功。