一个关于Struts Action的小框架, 请高手指点一下

首先是一个基类BaseAction
=================================================================
public class BaseAction extends ActionSupport {
/** 简单的得到spring bean的方法*/
public Object getBean(String beanName) {
return this.getWebApplicationContext().getBean(beanName);
}

/** 由子类Action对象调用, 可以在初始化时反射自身的结构, 形成一个映射, 用来日后dispatchSubaction.
*
* @return 存放了methods对象的Map
*/
protected Map getMethodMap() {
......
}

/** 将Action转移给子动作.
* 在子类中调用时,只需要新建一个Object[] {参数1, 参数2, ...} 一般为 {form, request, dao}
* 然后调用dispatchSubAction(String, objects) 即可
*
* @param action 字符串,决定子动作的方法名
* @param objs 参数对象列表
* @param methods 由getMethodMap得到的Map
* @return 用来findForward的别名
*/
protected String dispatchSubAction(String action, Object[] objs, Map methods) throws Exception {
......
}

之后是子类的写法:
================================================================
public class SomeAction extends BaseAction {
protected Map methods = getMethodMap(); //各线程公用
//--------- 下面是此Action中公用的一些对象,线程安全?
XxxDAO xxxDao=(XxxDAO) getBean("xxxDao");
XxxService xxxService = (XxxService) getBean("xxxService");

public ActionForward execute(......) throws Exception {
// ===================== 初始化环境 ===================== \\
XxxForm form = (XxxForm) actionForm;
ActionErrors errors = new ActionErrors();

// ==================== 获取用户参数 ==================== \\
//主要是进行数据转换如:form.setXxx() = form.getXxx().split("..");

// ==================== 调用业务逻辑 ==================== \\
Object[] params = {form, request, errors};
//这里的params要和下面定义的所有子程序的参数对应起来
String forward = dispatchSubAction(form.getSubaction(), params, methods);

// ==================== 设置回传参数 ==================== \\
saveErrors(request,errors);
return mapping.findForward(forward);
}

//所有的子程序的参数声明都应该相同, 但是每个Action可以根据自己的情况有所区别
public String show(MonthDayExecuteForm form, HttpServletRequest request, ActionErrors errors) {
......
return "show";
}

public String edit(MonthDayExecuteForm form, HttpServletRequest request, ActionErrors errors) {
......
return "edit";
}

public String save(MonthDayExecuteForm form, HttpServletRequest request, ActionErrors errors) {
......
return "show";
}
}
================================================================
归纳:我知道struts有lookupDispatchAction,1.2还有MappedAction,但是那些东西使用起来都不太方便。
1. 有的需要在MessageResources里面进行Submit按钮文字定义。
2. 有的需要在config中有特殊的配置
3. 必须使用和execute完全相同的冗长的函数声明
4. 各方法之间没有交集,难以实现统一的log处理,代码重复写的太多,例如一个简单的XxxForm form = (XxxForm) actionForm; 就必须写在所有的子方法中。

不知道我这样做能不能较好的解决上面的问题?虽然会带来一些初始化的负担,但是实际运行时应该不会怎么影响效率。我主要关心的是,这个结构还可不可以优化?另外有没有什么安全/效率方面的硬伤?望高手们多多指点!

没看明白

是自己写的mvc实现?

还是在struts上作的一些方便开发的功能?

一个建议:
将 Object[] params = {form, request, errors};
包装成对象,其实这是DTO对象,可参考Jdon框架中的EventModel。

一个疑问:
show、edit等重要方法是如何激活的? 这里是性能的关键点。

如果使用struts的dispatchAction,它根据参数parameter的值来直接映射的,比较方便。

在Jdon框架中,BaseAction(SomeAction ) 相当于ModelHandler,但是这部分代码在通常情况下可自动生成,通过配置完成。

为了确保线程安全在一个应用的生命周期中,struts框架只会为每个action类创建一个action实例,所有的用户共请求共享同一个action实例,并且所有的请求线程可以同时执行action实例的execute()方法。所以你的小框架中的xxxDao 变量是实例变量,肯定是线程不安全的。采用同步方法也是不能解决线程安全的问题的。同样还是会出现“脏读”现象。

banq说的对,你为什么不用dispatchAction呢,config配置并不复杂呀。这点灵活性

ThreadLocal 是解决多线程内部数据的,你可以在网上看看相关例子

楼主的想法有点从新制造轮子的嫌疑了.
dispatchAction的确非常实用,MappingDispatchAction 解决了dispatchAction的不足之处,这两个类已经非常完善了,简单配置下文件就可以了,完全没有必要再去做重复的工作.

问一个小问题
楼主为什么用GETBEAN 而不用SETDAO方法?

把SERVICE 与DAO放在同一层是不是有些混乱呢?

没看懂
建议下次能否先说明一下思路,然后在把代码贴出来

大概看了一下,不知道对不对
这种结构我好象在sun的demo里,要不就是在ibatis的demo里见到过,没什么发明的,参考一下就可以了

不知道有没有人在实际中使用过struts的DispatchAction系列? 谁又能给我明白的讲讲DispatchAction, LookupDispatchAction, MappingDispatchAction各自的用法和区别呢?

1) DispatchAction就是在struts-config中用parameter参数配置一个表单字段名,这个字段的值就是最终替代execute被调用的方法. 例如parameter="method"而request.getParameter("method")="save",其中"save"就是MethodName。struts的请求将根据parameter被分发到"save"或者"edit"或者什么。但是有一点,save()或者edit()等方法的声明和execute必须一模一样。

2) LookupDispatchAction继承DispatchAction, 用于对同一个页面上的多个submit按钮进行不同的响应。其原理是,首先用MessageResource将按钮的文本和ResKey相关联,例如button.save=保存;然后再复写getKeyMethodMap(), 将ResKey和MethodName对应起来, 例如map.put("button.save", "save"); 其配置方法和DispatchAction是一样的, 使用时要这么写:
<html:submit property="method">
<bean:message key="button.save"/>
</html:submit>

3) MappingDispatchAction是1.2新加的, 也继承自DispatchAction. 它实现的功能和上面两个区别较大, 是通过struts-config.xml将多个action-mapping映射到同一个Action类的不同方法上, 典型的配置就是:
<action-mappings>
<action path="/saveUser" type="logic.UserAction" parameter="save"></action>
<action path="/editUser" type="logic.UserAction" parameter="edit"></action>
</action-mappings>
然后UserAction继承MappingDispatchAction,其中有:
public ActionForward save(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception
public ActionForward edit(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception
等方法

可以看到, 不管怎么变化, 其实这些类都是把execute给分解开, 不管是save, edit还是其他什么方法, 其实都是和原来的execute是等价的, save和edit之间没有任何直接的关系, 而事实呢,它们是同一个业务模型的两种不同操作。 我觉得这就是一个问题,对于save和edit这两种请求, 我后台逻辑有可能只是调用service的方法那一句不一样,其他代码是完全一致的(例如错误处理, 日志记录等)。因此我想出了这个小东西,在execute方法内部进行局部分解。

上面有人说看iBATIS的代码看到过类似的。的确,iBATIS实现了一个BeanAction,将ActionForm和Acion写到了一个类里,然后通过一个ThreadLocal的局部变量在各个方法之间传递actionMapping, request, response这些参数。其每个方法的声明变得非常简单,返回的也是字符串forward名。

我在很大程度上是受了Clinton Begin的一些启发,但是BeanAction实现的框架稍微有些大,改写了struts的根本模式。而且作者也说,该框架没有经过实际应用的测试,所以我不想用。

楼顶的帖子我是想说的尽量全面些,所以把service,dao什么的都写进去了。有人说为什么不用setDao, 我不太明白是什么意思。是不是用spring直接注入呢?那样的话,必须用spring的代理,感觉不是很稳定而且大大增加了配置工作。
有人说xxxDao是局部变量,线性不安全。我也是想问问这个,象这种DAO和Service类,其本身一旦建立,是不会进行任何修改的(但是因为要从spring取,所以不能声明为final)。是不是可以理解为提供一些静态方法(例如saveWorld)的工具类呢?这样的话,是不是就不存在线程不安全的问题了?
究竟啥是线性不安全啊!!


下面是使用这个框架的例子。为了简便,去掉了接口层。BaseAction就不写了

===================== TestDAO,真正的业务逻辑实现 =============================
public class TestDAO {
    public void editWorld() {
        System.out.println("Editing the world");
    }

    public void saveWorld() {
        System.out.println("Saving the world");
     }
}

===================== TestService 业务逻辑的facade, 通过spring将testDao注入 =============================
public class TestService {
    private TestDAO testDao;

    public void setTestDao(TestDAO testDao) { this.testDao = testDao; }

    public void editWorldTest() {
        testDao.editWorld();
    }

    public void saveWorldTest() {
        testDao.saveWorld();
    }
}

===================== TestAction =============================
public class TestAction extends BaseAction {

    private TestService getTestService() { return (TestService) getBean("testService"); };

    public ActionForward execute(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception {
        TestActionForm form = (TestActionForm) actionForm;
        ActionErrors errors = new ActionErrors();

        Object[] params={form, request, errors};
// 上面是关键!Object[]不能设置为对象,因为有可能Test2Action的这一行是{form,request,response,messages,context}。这是这个框架灵活性的体现
        String forward = dispatchSubAction(form.getMethod(), params, methods);
// 通过method进行分发, 可以采用其他任何变量

        saveErrors(request, errors);
        return mapping.findForward(forward);
    }

    //edit,save等方法的参数要和上面的Object[]相对照
    public String edit(TestActionForm form, HttpServletRequest request, ActionErrors errors) {
        log.info("现在是在edit子动作中");
        getTestService().editWorldTest();
        return "edit";
    }

    public String save(TestActionForm form, HttpServletRequest request, ActionErrors errors) {
        log.info("现在是在save子动作中");
        getTestService().saveWorldTest();
        return "save";
    }
}

中间格式有点问题:重发前边几段

不知道有没有人在实际中使用过struts的DispatchAction系列? 谁又能给我明白的讲讲DispatchAction, LookupDispatchAction, MappingDispatchAction各自的用法和区别呢?

1) DispatchAction就是在struts-config中用parameter参数配置一个表单字段名,这个字段的值就是最终替代execute被调用的方法. 例如parameter="method"而request.getParameter("method")="save",其中"save"就是MethodName。struts的请求将根据parameter被分发到"save"或者"edit"或者什么。但是有一点,save()或者edit()等方法的声明和execute必须一模一样。

2) LookupDispatchAction继承DispatchAction, 用于对同一个页面上的多个submit按钮进行不同的响应。其原理是,首先用MessageResource将按钮的文本和ResKey相关联,例如button.save=保存;然后再复写getKeyMethodMap(), 将ResKey和MethodName对应起来, 例如map.put("button.save", "save"); 其配置方法和DispatchAction是一样的, 使用时要这么写:
<html:submit property="method">
  <bean:message key="button.save"/>
</html:submit>

3) MappingDispatchAction是1.2新加的, 也继承自DispatchAction. 它实现的功能和上面两个区别较大, 是通过struts-config.xml将多个action-mapping映射到同一个Action类的不同方法上, 典型的配置就是:
<action-mappings>
  <action path="/saveUser" type="logic.UserAction" parameter="save"></action>
  <action path="/editUser" type="logic.UserAction" parameter="edit"></action>
</action-mappings>
然后UserAction继承MappingDispatchAction,其中有:
public ActionForward save(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception
public ActionForward edit(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception
等方法

可以看到, 不管怎么变化, 其实这些类都是把execute给分解开, 不管是save, edit还是其他什么方法, 其实都是和原来的execute是等价的, save和edit之间没有任何直接的关系, 而事实呢,它们是同一个业务模型的两种不同操作。 我觉得这就是一个问题,对于save和edit这两种请求, 我后台逻辑有可能只是调用service的方法那一句不一样,其他代码是完全一致的(例如错误处理, 日志记录等)。因此我想出了这个小东西,在execute方法内部进行局部分解。

可能还是有些地方没说明白。
TestAction的配置和普通Action的配置完全一样。
TestActionForm里面有一个method属性。
运行结果:
http://localhost/webapp/test.do?method=save
在后台显示Saving the world.
http://localhost/webapp/test.do?method=edit
在后台显示Editing the world.

banq说关键是edit,save等methods是如何激活的。就是这句:
dispatchSubAction(form.getMethod(), params, methods);
根据请求的method值,从methods中选择一个java.lang.Method类,以params为参数调用。

这句是老版本,该贴发出后,经过优化,这里不需要使用methods来调用了。dispatchSubAction(form.getMethod(), params);就行了。在BaseAction里有一句
protected Map methods = getMethodMap();
在struts初始化Action时,反射一次得到本Action的所有save,edit等等方法,Map里保存的是java.lang.Method,这样以后调用应该能快些。子类中根本不需要看不到这一过程了,只要知道dispatchSubAction的第一个参数是方法名,第二个参数是调用方法的参数对象数组即可。


>反射一次得到本Action的所有save,edit等等方法,Map里保存的是java.lang.Method

最好不用用反射,反射是耗费性能的,或者使用缓存优化一下。

//dongqi_zhao@hotmail.com


PublicForm继承自ActionForm,实现了当前登陆用户的统一获取(getUserId()),分页显示参数的统一传递(getStart(),getRange()),功能扩展参数(getDoType())。

PublicAction继承自Action,附加权限的判别,异常处理,日志纪录等功能
e.g.
<action name="stuentForm" type="com.virtual.test.action.StuentAddAction" scope="request" path="/test/stuentAddAction">
<forward name="list" path="/test/stuentListAction.do?doType=0" />
<forward name="add" path="/test/stuentAdd.jsp" />
<forward name="resource" path="2004105001002" />
<forward name="operator" path="201" />
</action>
通过resource,operator获取当前的资源,操作,进而进行权限鉴别


package org.open.struts;

import javax.servlet.http.*;

import org.apache.struts.action.*;
import org.open.util.*;

/**
* 所有actionForm的父类,提供登陆用户的统一获取,
* 分页参数的统一传递 *
*
*/
public class PublicForm
extends ActionForm {
/**
* 功能扩展用
*/
private int doType;
/**
* 当前用户id
*/
private String userId;
/**
* 开始记录数,分页用,从0开始
*/
private int start = 0;
/**
* 每页的记录数,
*/
private int range = 20;

public int getDoType() {
return doType;
}

public void setDoType(int doType) {
this.doType = doType;
}

public ActionErrors validate(ActionMapping actionMapping,
HttpServletRequest httpServletRequest) {

return null;
}

public void reset(ActionMapping actionMapping,
HttpServletRequest httpServletRequest) {
}

/**
* 获取公共信息,userID.....
*/
public void getCurrentDefine(ActionMapping actionMapping,
HttpServletRequest httpServletRequest) {

userId = PublicAction.LOGIN_NA;
Cookie cookie = CookieMgr.getCookie(httpServletRequest,
PublicAction.LOGIN_ACCOUNT_COOKIE);
if (cookie != null) {
userId = cookie.getValue();
}
}

public String getUserId() {
return userId;
}

public void setUserId(String userId) {
this.userId = userId;
}

public int getStart() {
return start;
}

public void setStart(int start) {
this.start = start;
}

public int getRange() {
return range;
}

public void setRange(int range) {
this.range = range;
}


}


package org.open.struts;

import java.util.logging.*;
import javax.servlet.http.*;

import org.apache.struts.action.*;
import org.open.common.auth.logic.*;
import org.open.common.log.logic.*;

/**
* 作为所有业务Action的父类,提供权限统一判别,异常集中处理
*
*/
public class PublicAction
extends Action {

/**
* java logger interface
*/
private Logger logger;
/**
* debug point
*/
private String debugPoint;
/**
* 系统异常,置于request中的属性名
*/
public static final String SYS_EXCEPTION = "sys.exception";

/**
* 系统信息提示,置于reqeust中的属性名
*/
public static final String SYS_MESG = "sys.mesg";
/**
* 系统登陆页面的forward名称
*/
public static final String F_LOGIN = "login";
/**
* 系统登陆错误的forward名称
*/
public static final String F_LOGIN_NO = "login.no";

/**
* 系统登陆成功页面的forward名称
*/
public static final String F_LOGIN_YES = "login.yes";

/**
* 系统登出成功页面的forward名称
*/
public static final String F_LOGOUT = "logout";

//系统异常的forward名称
public static final String F_ERROR = "error";


/**
* 当前用户置于session中的属性名
*/
public static final String LOGIN_ACCOUNT = "sys.account";
/**
* 当前用户置于COOKIE中的属性名
*/
public static final String LOGIN_ACCOUNT_COOKIE = "sys.account";
/**
* 未登陆时得到用户id
*/
public static final String LOGIN_NA = Auth.ACCOUNT_NA;

/**
* 执行成功
*/
public static final String EXECUTE_OK = "1";


/**
* Action的入口方法
*/
public ActionForward execute(ActionMapping actionMapping,
ActionForm actionForm,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) {

ActionForward af = null;
PublicForm form = new PublicForm();
try {
//所有actionForm必须继承自 PublicForm

form = (PublicForm) actionForm;

//统一获取userId等公共信息
form.getCurrentDefine(actionMapping, httpServletRequest);

//获取当前资源代码
String currentResource = "-1";
try {
currentResource = actionMapping.findForward("resource").getPath();
}
catch (Exception ex4) {
}

//获取当前操作代码
String operator = "-1";
try {
operator = actionMapping.findForward("operator").getPath();
}
catch (Exception ex3) {
}

//获取当前是否进行权限判别的标志
String identify = "false";
try {
identify = actionMapping.findForward("identify").getPath();
}
catch (Exception ex2) {
}

String userId = form.getUserId();

//需要判别
if (!identify.equalsIgnoreCase("false")) {

// if (userId.equalsIgnoreCase(LOGIN_NA)) {
// httpServletResponse.sendRedirect("/error/error.jsp?errCode=1");
// }

this.setDebugPoint("权限鉴别...");
boolean flag = Auth.checkPrivilegeBySession(userId, currentResource,
operator, httpServletRequest.getSession());

if (!flag) {
throw new Exception("当前用户(" + userId + ")没有相应的权限:" +
currentResource + "@" + operator);
}

}
this.setDebugPoint("逻辑执行...");
af = performLogic(actionMapping, actionForm, httpServletRequest,
httpServletResponse);
this.setDebugPoint("逻辑执行结束.");
}
catch (Exception ex) {
httpServletRequest.setAttribute(SYS_EXCEPTION, ex);
httpServletRequest.setAttribute(SYS_MESG, ex);
getLogger().log(Level.SEVERE, getDebugPoint(), ex);

try {
LogItemMaster.create(null, ex.getMessage(), ex + "\n" + getDebugPoint(),
LogItemMaster.LEVEL_WARNING,
"SYSTEM", form.getUserId(), new java.util.Date());
}
catch (Exception ex1) {
System.out.println(" LogItemMaster.create():" + ex1);
}
return actionMapping.findForward(F_ERROR);
}
return af;

}

/**
* 业务逻辑调用,所有子类必须实现此方法
*/
public ActionForward performLogic(ActionMapping actionMapping,
ActionForm actionForm,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws
Exception

{
return null;
}

/**
*
*/
public java.util.logging.Logger getLogger() {
if (logger == null) {
logger = Logger.getLogger(this.getClass().getName());
}
return logger;
}

public String getDebugPoint() {
return this.getClass().getName() + " ->_debug_Point: " +
debugPoint;
}

public void setDebugPoint(String debugPoint) {
this.debugPoint = debugPoint;
}

}