打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
在struts2方法上直接使用参数(不带注解)进行逻辑处理 | i flym

众所周知,在使用struts2时,所写的逻辑方法都是无参数的,所以的参数都是写在action内部的。如果一个action方法过多,而每个方法所使用的参数都不尽相同时,就会造成一个action内参数过多,且每个参数都有get/set,造成程序混乱,并且在可读性和使用上都不太方便。因为,你不知道你所调用的方法使用了哪些参数,而这个方法会返回哪些结果,在界面上哪些get对象是可以使用的。

那么,能否直接在方法上使用参数呢,就像在action里面一样,直接注入,直接使用,并且没有副作用呢?你想像使用spring mvc甚至更好的方式(无注解)使用struts2吗?这种想法是可以滴,当然现有的struts2是不支持滴。不过,我们可以修改之,让其支持。
本篇所使用以及参考的相关技术前提(可google之)

  • struts2(xwork)中valueStack中的工作原理
  • 在valueStack中CompoundRoot对象如何使用
  • OGNL的propertyAccessor以及扩展
  • struts2(xwork)所提供的ognl扩展
  • 使用spring中所提供的localVariable访问方法参数信息
  • java编译保存方法变量信息

本篇所提供内容工作前提

  • action中每个方法名称惟一(不支持方法重载)
  • java文件编译时保存了调试信息中的方法变量信息

本篇技术内容索引

  1. 获取方法信息
  2. 方法参数注入
  3. 方法调用调整
  4. Map及List泛型参数注入修正
  5. 方法内数据返回

获取方法信息

首先我们需要能够获取所调用的方法信息。方法信息即包括方法名称,同样包括方法的参数信息;而参数信息包括参数名和参数类型以及参数泛型(后面会用到)。
如我们的方法为

1
public void login(User user, Map<String, String> other)

我们期望获取的方法信息有
方法名:login
方法参数信息(按顺序):
参数名:user    类型:User    泛型:无
参数名:other   类型:Map     泛型:<String,String>

所以提供一个数据结构Map<Method, MethodParam[]> methodParamCache来保存这些信息,MethodParam构造信息如下所示:

1
2
3
4
5
6
public static class MethodParam {
        public final Class type;//参数类型
        public final String name;//参数名称
        public final Class parameterizedType0;//泛型参数0,用于描述List<XXX>类泛型,以及Map<XXX,YYY>中XXX泛型
        public final Class parameterizedType1;//用于描述Map<XXX,YYY>中的YYY泛型
}

而Method对象,可以根据由界面传递过来的action以及method名称进行获取,我们提供一个类似findByMethodName的方法即可,方法实现简单如下所示:

01
02
03
04
05
06
07
08
09
10
 public static Method getUniquePublicMethod(Class<?> clazz, String methodName) throws NoSuchMethodException {
        Map<String, List<Method>> m = methodCache.get(clazz);
        if(m == null) {
            m = resolveClass(clazz);//使用class.getMethods()迭代方法信息,只需要公共方法即可
            methodCache.put(clazz, m);
        }
        List<Method> mList = m.get(methodName);
//空方法判断以及多个方法判断
        return mList.get(0);
    }

方法参数注入

为了保证与action对象信息有相同的容器效果(即可以存放参数信息值),我们定义了一个类ActionParam来保存参数名与相应的参数信息,以方便进行参数注入。其底层实现为HashMap(实际上就是一个HashMap,只是加了一个别名),以特殊类型的方式通知Ognl使用定制的ActionParamAcessor进行参数注入。

首先,我们需要将这个ActionParam对象放到valueStack中,然后才能进行注入。我们知道,用于参数处理的拦截器为ParametersInterceptor,此为一个调用拦截器,因此我们的此对象需要此实现在Invocation的init中进行,以方便在进行拦截器调用前就确定相应信息。
在init中设置actionParam的逻辑如下所示:

01
02
03
04
05
06
07
08
09
10
11
  if(actionMethod != null) {
            ActionParam params = new ActionParam();
            MethodUtils.MethodParam[] mps = MethodUtils.getMethodParam(actionMethod);
            for(MethodUtils.MethodParam mp : mps) {//以参数名为key,对象默认值为value(对象默认值为0或class.newInstance())
                params.put(mp.name, ObjectUtils.getDefaultValue(mp.type, objectFactory));
            }
            if(!params.isEmpty()) {
                stack.push(params);
                ActionMethodUtils.setCurrentMethod(actionMethod);
            }
        }

通过stack push之后,在ParametersInterceptor中就会使用我们自定义的actionParamAccessor进行数据注入了。在注入时,主要处理的就是处理像user.name=xx这种情况了。在struts2中,这种操作通过getUser().setName(xx)来完成的。所以首先要处理的就是getUser()这种情况,我们必须保证getUser()返回的对象不为null,这样才能完成setName操作。因此,要处理这种情况,就需要ovveride propertyAccessor的getProperty方法,方法简单实现如下所示:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 public Object getProperty(Map context, Object target, Object name) throws OgnlException {
        ReflectionContextState.updateCurrentPropertyPath(context, name);
 
        Object result = null;
            result = super.getProperty(context, target, name);
 
        Method currentMethod = ActionMethodUtils.getCurrentMethod();
        if(result == null && currentMethod != null) {//准备按照规则进行新建对象
 
            Object key = xworkConverter.convertValue(context, name, String.class);
            Map map = (Map) target;
            result = map.get(key);
 
            if(result == null && ReflectionContextState.isCreatingNullObjects(context)) {
                MethodUtils.MethodParam methodParam = MethodUtils.getMethodParam(currentMethod, (String) key);
                if(methodParam != null) {
                    result = ObjectUtils.getDefaultValue(methodParam.type, objectFactory);//这里为新建的对象
                    map.put(key, result);
                }
            }
        }
        return result;
    }

方法调用调整

通过参数注入之后,我们从界面上传递的user.name,user.password,other.a,other.b就会转换成我们所需要的user对象和other对象了。并且可以从actionParam中进行获取了。因此在最终调用action执行方法时,就不能再直接调用method.invoke(obj),而是应该把相应的参数传递进去。这里就需要修改DefaultActionInvocation中的invokeMethodOnly方法,放弃原来的无参调用,而改用有参数调用了。修改逻辑简单如下所示:

01
02
03
04
05
06
07
08
09
10
11
   //准备执行方法,此处需要取得相应方法参数信息
            MethodUtils.MethodParam[] mps = MethodUtils.getMethodParam(method);
            if(mps.length == 0)//正常方法,无参数
                methodResult = method.invoke(action);
            else {//有参数的方法
                Object[] objs = new Object[mps.length];
                for(int i = 0;i < mps.length;i++) {
                    objs[i] = stack.findValue(mps[i].name, mps[i].type);
                }
                methodResult = method.invoke(action, objs);
            }

Map及List泛型参数注入修正

在第二步中进行other对象注入时,我们看到这个other参数是一个map对象,并且key为字符串类型,value也为字符串类型,因此使用ognl在进行注入时,就必须取得相应的类型参数才行。在原有struts2提供的mapAccessor中,对这块是有处理的,不过它处理的是参数信息在Action类的情况,我们现在要处理的是参数在方法参数表中的情况。因此需要作一部分调整,调整代码如下所示(修改类名为XWorkMapPropertyAccessor,方法为getProperty)

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
    if(result == null) {
            //加入方法参数上调用判断
            String propertyPath = ReflectionContextState.getCurrentPropertyPath(context);//这个是参数调用链,如界面的other.xx,这里就为other.xx
            String firstNodeName = propertyPath == null ? null : !propertyPath.contains(".") ? propertyPath : propertyPath
                .substring(0, propertyPath.indexOf("."));//firstNodeName即为other,
            if(ActionMethodUtils.isInMethodParamAccess(context, firstNodeName)) {//这里即判断,这个other是否为我们方法中的参数名,如果是,则进行方法参数处理
                Method method = ActionMethodUtils.getCurrentMethod();
                MethodUtils.MethodParam mp = MethodUtils.getMethodParam(method, firstNodeName);
                Object key = getMethodParamKey(context, name, mp);//这里使用泛型参数0取key类型
                Map map = (Map) target;
                result = map.get(key);
                if(result == null && ReflectionContextState.isCreatingNullObjects(context)) {
                    Class valueClass = mp.parameterizedType1;//这里使用泛型参数1,即map中的value类型
                    if(valueClass == null)
                        valueClass = Object.class;
                    result = ObjectUtils.getDefaultValue(valueClass, objectFactory);
                    map.put(key, result);
                }
                return result;
            }
。。。。。。走原有逻辑

当然,修改了getProperty,那么setProperty肯定也要作相应调整。同样的,类XWorkListPropertyAccessor,XWorkCollectionPropertyAccessor也需要作相应的调整。这里就不必列代码了。

经过以上的调整,我们的整个改造基本完成,并且代码即可正常地使用了。由于我们将user参数,other参数放进了stack中(由actionParam进行存储),因此在界面上就可以直接使用<s:property value=user.xx/>进行访问。如果需要访问其他对象呢?在原有的struts2中,是通过使用getXXX()将信息传递到界面了,既然我们要精简set/getXXX,就不能使用这种方式呢,那怎么使用了。我们可以采用#xxx这种方式来调用,就像request.setAttribute一样。

方法内数据返回

我们知道,如果使用request.setAttribute,可以在界面上使用#request.xx进行访问。如果,我们连#request中的request也不需要,能否直接使用#xxx呢,这是可以的。其实就是把相应的值放到上下文中即可。我们可以增加一个类似setValue(key,value)的方法,将我们要传递到界面上的信息放到上下文中,然后在界面上直接调用即可。代码如下所示:

01
02
03
04
05
06
07
08
09
10
 /** 往上下文中设置值,以便在界面上使用#key的方式进行获取 */
    protected void setContextValue(String key, Object value) {
        ActionContext actionContext = ServletActionContext.getContext();
        if(actionContext != null)
            actionContext.put(key, value);
        //同时加入到request中
        HttpServletRequest request = ServletActionContext.getRequest();
        if(request != null)
            request.setAttribute(key, value);
    }

至此,整个实现结束。整个代码实现可以到此处下载:http://download.csdn.net/detail/fly_m/4341729 
文技术基于xwork版本2.1.6.1,struts2版本2.1.8.1

转载请标明出处:i flym
本文地址:http://www.iflym.com/index.php/code/201205300001.html

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
覆盖DispatchAction中的分发方法
自己动手写一个Struts2 - superleo - JavaEye技术网站
@RequestParam @RequestBody @PathVariable 等参数绑...
Java 源代码编译成 Class 文件的过程分析(二)
常用的JAVA反射
addCallback的instance参数说明
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服