众所周知,在使用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文件编译时保存了调试信息中的方法变量信息
本篇技术内容索引
- 获取方法信息
- 方法参数注入
- 方法调用调整
- Map及List泛型参数注入修正
- 方法内数据返回
获取方法信息
首先我们需要能够获取所调用的方法信息。方法信息即包括方法名称,同样包括方法的参数信息;而参数信息包括参数名和参数类型以及参数泛型(后面会用到)。
如我们的方法为
public void login(User user, Map<String, String> other)
我们期望获取的方法信息有
方法名:login
方法参数信息(按顺序):
参数名:user 类型:User 泛型:无
参数名:other 类型:Map 泛型:<String,String>
所以提供一个数据结构Map<Method, MethodParam[]> methodParamCache来保存这些信息,MethodParam构造信息如下所示:
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的方法即可,方法实现简单如下所示:
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的逻辑如下所示:
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方法,方法简单实现如下所示:
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方法,放弃原来的无参调用,而改用有参数调用了。修改逻辑简单如下所示:
//准备执行方法,此处需要取得相应方法参数信息 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)
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)的方法,将我们要传递到界面上的信息放到上下文中,然后在界面上直接调用即可。代码如下所示:
/** 往上下文中设置值,以便在界面上使用#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
本文地址:https://www.iflym.com/index.php/code/201205300001.html