RSS
热门关键字:  java  Ajax  JSP  JSF  Struts
当前位置 : 首页>Struts>列表

让Struts 1焕发青春:小议对Struts的改造

来源: 作者: 时间:2007-09-06 点击:
目前流行的新型的MVC框架 几乎都在"增强单元测试能力"上做了很多文章. 目的就是让 Controller 可以脱离web容器单独进行单元测试.大多数采用的方法都是使 Controller 中的方法的参数 和 返回值 与 j2ee特有的类(如HttpXXXX)无关.

  例如 传入的 是若干个 HashMap ,返回的是纯的字符串.这样在单元测试的时候,只要new一个Controller,准备一些装有测试数据的Map, 然后执行相关的Controller方法,再然后看看返回值,就可以了.

  显然,流行的Struts 1.X (>=1.2) 是不具备这样的特性的.如果您现在有一个遗留的使用Struts 1.X(>=1.2) 的系统,或者因为人力资源的原因,不能立即引入ww或Struts2.x, 那么如何让您的Action类似的可测性了呢(其实除了可测试性,这样的改进往往也会大大的减少代码的开发量).

  下面我就把我的一些心得写出来,和大家一起分享,欢迎大家拍砖. 注意:以下讨论均以改造 DispatchAction 为例(不使用DispatchAction的情况应该比较少吧 呵呵).

  先说明一下改造的目的:

   简化开发,减少代码量
   增强单元测试能力
   全面兼容现有DispatchAction(这点很重要)

  一个传统的 DispatchAction 代码结构

public class MyClassicAction extends DispatchAction {
// 一个添加user的操作
public ActionForward addUser(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
// 1 取得参数 ...
// 2 执行相关的BO DAO方法
// 3 取得执行后的返回值

if (添加成功) {
return mapping.findForward("Succee");
}

return mapping.findForward("Fail");
// 还有一种情况是 返回 new ActionForward(....).
}
}

  <script>render_code(); </script>

  对于上面的代码大家再熟悉不过了,我们现在要做的事情就是通过改造,让上面的action方法变成下面的样子.

// 一个添加user的操作
public String addUser( 传入与web容器无关的参数 )
throws Exception {
// 1 取得参数 ...
// 2 执行相关的BO DAO方法
// 3 取得执行后的返回值

if (添加成功) {
return "Succee";
}

return "Fail";
// 对于action中返回 new ActionForward(....).的情况,我们可以设定另外一种特殊的前缀.
// 例如 return "NEW:/addUserFail.jsp";
}

<script>render_code(); </script>

  上面的代码是一个最终的目标,我们先一步一步来. 首先这种改造我们一定要从 Struts 提供的 DispatchAction 做起.总体思路很明显: 自己写个类 继承 DispatchAction ,并且添加或修改一些方法, 然后项目中的其他的DispatchAction都继承这个自己写的DispatchAction类.

  简单说一下Struts 的 DispatchAction的工作流程.

  首先它也是一个action,所以请求来了 他会自动去执行execute方法
execute方法内部做一些操作后,会调用 dispatchMethod 方法dispatchMethod方法会,调用getMethod方法 来取得欲执行的方法,并且执行之

  改造的核心就是围绕最关键的dispatchMethod方法. 而由于我们要"全面兼容现有DispatchAction",所以对于getMethod我们不做修改,而是选择增加一个类似的方法 getMethodTD

  首先写一个新的 .DispatchAction基类,他继承org.apache.struts.actions.DispatchAction.

public class TDDispatchAction extends DispatchAction {

protected Class[] typesTD = {TDServletWrapper.class };

protected final static String TD_METHODCACHE_PREFIX="TD_METHODCACHE_PREFIX";
protected final static String TD_DEFAULT_METHOD="defaultMethod";
protected final static String TD_NEW_FORWARD_PREFIX="NEW:";



protected Method getMethodTD(String name)
throws NoSuchMethodException {

String cacheKey=TD_METHODCACHE_PREFIX+name;

synchronized (methods) {
Method method = (Method) methods.get(cacheKey);

if (method == null) {
method = clazz.getMethod(name, typesTD);
methods.put(cacheKey, method);
}

return (method);
}
}

// 对unspecified这个方法的扩展实际上与本次讨论无关,只是为我们框架加的一个小功能
// 提供一个默认的方法.也就是说,如果您的action中,有名为 defaultMethod 的方法,那么无需在请求的参数中加入相关信息,就会自动执行.
protected ActionForward unspecified(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
return dispatchMethod(mapping, form, request, response, TD_DEFAULT_METHOD);
}

protected ActionForward dispatchMethod(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response, String name)
throws Exception {
// 后面再说

}
}


<script>render_code(); </script>


  首先来看一下这个 getMethodTD 方法.原getMethod(String name)方法的作用是:取得一个名字为name,参数类型为 ActionMapping , ActionForm , HttpServletRequest ,HttpServletResponse 的一个方法.

  而这个新写的 getMethodTD 方法的作用是:取得一个名字为name,参数类型为 TDServletWrapper 的一个方法. 这个大家和 原getMethod一对比就能看出来作用和差异了,注意那个cacheKey=TD_METHODCACHE_PREFIX+name,这个是必须的,不能直接用name做key.

  其中这个 TDServletWrapper 就是封装的一个"可以"与web容器脱离的对象,注意,这里用的是可以.而不是直接就脱离了. 对此后面再做解释.

  要添加的 getMethodTD 方法添加完了. 下面来看一下重点 改造 dispatchMethod方法:

  原dispatchMethod方法的流程其实很简单:

   1 调用 getMethod(name)方法,取得真正要执行的方法 并执行(传入的参数类型为 ActionMapping , ActionForm , HttpServletRequest ,HttpServletResponse ).
   2 返回 方法执行的 结果 (返回结果类型为 ActionForward ).

  改造后的目的是:

   1 调用 getMethodTD(name)方法,取得真正要执行的方法并执行(传入的参数类型为TDServletWrapper ).
   2 取得 方法执行的 结果 (返回结果类型为 String ).
   3 根据这个返回的String, 创建ActionForward对象,并返回

  但是我们还有一个目的是"兼容",所以最后确定的流程是

   1 调用 getMethod(name)方法,取得真正要执行的方法,如果取得,则按原方式继续.
   2 如果没有取得 调用 getMethodTD(name)方法,取得真正要执行的方法,若取得则执行.
   3 根据返回结果的类型不同 做不同的操作.

// 改造的 dispatchMethod方法,重点看 //TD...字样所在的代码段
protected ActionForward dispatchMethod(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response, String name)
throws Exception {

if (name == null) {
return this.unspecified(mapping, form, request, response);
}

Method method = null;

//TD: Modified by Wei Zijun
boolean useTDMethod=false;
try {
// 按传统方式取 action方法
method = getMethod(name);
} catch (NoSuchMethodException ex) {
try {
// 没取到的话 再按新方式取 action方法
method = getMethodTD(name);
// 标识一下到底是使用的原始方法 还是新方法
useTDMethod=true;
} catch (NoSuchMethodException e) {
String message =
messages.getMessage("dispatch.method", mapping.getPath(), name);

log.error(message, e);

String userMsg =
messages.getMessage("dispatch.method.user", mapping.getPath());
throw new NoSuchMethodException(userMsg);
}

}

ActionForward forward = null;

try {
//TD: Modified by Wei Zijun
Object forwardR=null;

if (!useTDMethod){
// 如果是原始 action 方法
Object[] args = { mapping, form, request, response };
forwardR= method.invoke(this, args);

}else{
// 如果是新 action 方法
// TDServletWrapperFactory.getInstance创建一个封装的对象
Object[] args = { TDServletWrapperFactory.getInstance(mapping, form,request, response) };
forwardR= method.invoke(this, args);
}

if (forwardR!=null){
if (forwardR instanceof ActionForward){
forward=(ActionForward)forwardR;
}else{
// 如果返回值是字符串:
// 以 TD_NEW_FORWARD_PREFIX("NEW:")开头,则new ActionForward;
// 否则 调用 mapping.findForward.
if ( (String.valueOf(forwardR) ).toUpperCase().startsWith(TD_NEW_FORWARD_PREFIX)){

共2页: 上一页 1 [2] 下一页
最新评论共有 0 位网友发表了评论
发表评论
评论内容:不能超过250字,需审核,请自觉遵守互联网相关政策法规。
用户名: 密码:
匿名?
注册
Google Adsense
相关文章