在struts2方法上直接使用参数(不带注解)进行逻辑处理

众所周知,在使用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. 方法内数据返回

获取方法信息

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

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

相关文章:

作者: flym

I am flym,the master of the site:)

发表评论

邮箱地址不会被公开。 必填项已用*标注