第3章 使用DispatchAction优化控制层 本章导读 我们在前面章节中提到过Struts架构主要是对MVC模式的控制层和视图层规范进行了技术实现,在第2章我们已经初步认识了开发Struts应用的过程和Struts运行机制。本章我们将从对Struts控制层的认识开始,并基于项目的实际应用掌握一种Struts控制层开发的优化方法,即DispatchAction类的应用。 通过本章的学习,读者能够掌握控制层中各个类之间的协作关系,以及如何使用DispatchAction类来优化控制层的开发。 工作任务 使用DispatchAction优化原型系统,完成登录、注册、退出功能。 3.1 默认的Action类 3.1.1 默认的Action 这里我们所说的默认Action类指的是类org.apache.struts.action.Action.java。具体的Action类继承该类,并且具体Action类的功能一般都在execute()方法中完成。在第2章的案例中我们采用的就是这种Action类的子类来完成控制器的功能。 3.1.2 解读Action类的execute ()方法继承org.apache.struts.action.Action.java的具体子类的名称推荐使用Action结尾。具体子类必须重写execute()方法。 execute ()方法的签名如下: public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) 在execute()方法中需要通过编码完成下列工作:  接收请求参数和请求数据。ActionForm对象form中传递进来的是请求表单中的参数,request和response中分别存放请求和响应信息,其功用和servlet中接收的请求和响应一致。  编写业务逻辑,实现业务功能。通常可以通过调用Model层的业务接口实现。  根据业务功能的反馈结果,决定程序流程方向。输入参数mapping中存放的是在struts-config.xml文件中定义的映射关系,如跳转路径名(即forward标签中的name属性)和实际路径(forward标签中的path属性)间的映射关系。根据处理结果,把程序流向(如视图跳转目标)封装成ActionForward对象的形式返回给ActionServlet类。提示: 在loginAction的execute()方法中我们实现了数据验证,一般情况下,本着MVC分离的原则,也就是视图级的验证工作放在视图端来完成,可以由ActionForm来完成,也可以在前端通过JavaScript脚本来完成。 当用户请求由Struts框架的总控制器ActionServlet分发给具体的Action子类时,Action子类对象会执行execute()方法。这种默认Action类的子类只有一个execute()方法,即同一个Action具体子类只能处理一种业务逻辑(或者通过在execute()方法内编写复杂的选择分支也可以达到处理多种业务逻辑的目的). 3.2 DispatchAction 3.2.1 使用DispatchAction的必要性 在第2章中,我们实现了登录、注册和退出功能,解决方案是每个视图页面对应一个Action具体子类,每个Action具体子类对应一个ActionForm具体子类。 随着开发的进行我们的原型系统要进行扩展会添加更多的视图页面和功能进来。项目功能扩展完善之后,肯定会有一个页面多个功能按钮,或者一个页面一个功能按钮的情形出现,按照第2章的解决方案,有多少个页面就会有多少个Action具体子类,甚至更多。另外一种解决方案是把操作请求的判断逻辑写在Action具体子类的execute()方法代码内部。 无论哪种解决方案,都会给我们的项目维护带来困难。一方面,会使我们的项目规模大到难以控制;另一方面,我们的项目会出现冗余代码,使Action具体子类内的逻辑复杂,不易阅读维护。那么我们有什么更好的解决方案吗? Struts框架中提供的DispatchAction就可以很好地解决这种问题。 3.2.2 DispatchAction的使用 通常,一个Action中只能完成一种业务操作,通过扩展org.apache.struts.actions.DispatchAction实现的Action类可以完成一组相关的几种业务操作。DispatchAction类称为可自动分发的Action,它的作用就是实现按业务实体划分类。 扩展自DispatchAction的Action类中不必定义execute()方法,而是创建一些实现实际业务操作的方法,如doReg () 、doLogin ()等。但是这些业务方法的传入参数和返回值要和普通Action的execute ()方法相同,并声明可能抛出同样的异常。 而且DispatchAction类内部的业务处理方法的名字是取决于传递过来的form表单的参数名的。 我们要使用DispatchAction类进行系统的优化,需要参考以下步骤。 (1) 对业务实体进行识别,然后把对同一个业务实体的多个操作合并到一个DispatchAction子类中进行处理。 (2) 在struts-config.xml文件中的标签中添加一个参数parameter。该参数用来指定函数入口的参数名。 (3) 在视图页面的表单
的请求地址中传递参数“method=test" . (4) 在Action子类中修改父类为DispatchAction,并新建参数值对应的函数。此函数的签名和execute()方法一致,只是方法名为method参数的值,即和第三步中的method参数的值一致。 完成了上述操作之后,在运行时,就会根据参数的不同,由不同的方法自动运行不同的业务处理。 3.3 使用DispatchAction改进原型系统 现在我们要对第2章完成的原型系统进行优化,按照业务实体进行划分,登录、退出和注册功能都是对用户业务实体进行操作,所以,我们只需要一个控制Action类,即DispatchAction子类。 我们命名处理用户实体的Action子类为UserAction.java,用户实体用UserForm.java表示。要对用户实体进行的操作分别是登录、注册和退出,所以我们分别命名这几个操作对应的方法名为doLogin、doRegister和doExit. 提示: 在DispatchAction子类中,方法名称的前缀也常常遵循一定的惯例。 (1) 转到编辑页面的方法常命名为toEdit; (2) 执行编辑操作的方法常命名为doEdit; (3) 以此类推,命名为doAdd、toList、doDel等。 toXxx表示转到Xxx页面,而doXxx表示执行Xxx操作。 通过这样的命名,可以使程序逻辑更清晰,减少出错的几率,便于维护。 3.3.1 使用DispatchAction为原型系统添加注册功能 我们参考3.2.2节的DispatchAction步骤来对原型系统进行修改,优化控制层的开发。  修改struts-config.xml文件中的LoginAction类对应的action标签。 ■为该签添加属性parameter= "method" . ■修改该action标签的Type属性的全类名为com.wangyingling.struts.actions.UserAction. ■Action标签的name属性的值修改为userForm. ■Action标签的path属性的值修改为/userAction.  修改LoginAction类。 ■重命名LoginAction类为UserAction. ■修改UserAction类的父类为org.apache.struts.actions.DispatchAction. ■修改execute ()方法名为doLogin. ■添加方法doRegister () . 修改编辑后的UserAction类的源代码如代码清单3-1所示。 代码清单3-1 UserAction.java / Generated by MyEclipse Struts Template path: templates/java/JavaClass.vtl / package com.wangyingling.struts.actions; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionMessage; import org.apache.struts.actions.DispatchAction; import com.wangyingling.struts.forms.UserForm; import com.wangyingling.struts.model.UserHandlerBean; public class UserAction extends DispatchAction { public ActionForward doLogin(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response){ UserForm loginForm=(UserForm) form; ActionErrors errors=new ActionErrors(); ActionForward forward=new ActionForward(); try { //获得请求参数 String username=loginForm.getUname(); //如果会话存在,则使其失效。 HttpSession session=request.getSession(false); if (session !=null){ session.invalidate(); } //为当前用户创建一个会话 session=request.getSession(true); //执行登录操作 boolean isValid=valid(request, loginForm); if (isValid){ session.setAttribute("userName", username); } else { errors.add(ActionErrors.GLOBAL_MESSAGE, new ActionMessage( "login.message.failed")); } } catch (Exception e){ errors.add(ActionErrors.GLOBAL_MESSAGE, new ActionMessage( "login.message.failed")); } //If a message is required, save the specified key(s) //into the request for use by the tag. if (!errors.isEmpty()){ saveErrors(request, errors); request.setAttribute("loginFormBean", loginForm); forward=mapping.findForward("fail"); } else { forward=mapping.findForward("welcome"); } //Finish with return (forward); } public ActionForward doRegister(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response){ UserForm registerForm=(UserForm) form; //method stub ActionErrors errors=new ActionErrors(); ActionForward forward=new ActionForward(); try { //判断用户名是否已存在 boolean isExist=isExist(request, registerForm); if (isExist){ errors.add(ActionErrors.GLOBAL_MESSAGE, new ActionMessage( "register.message.failed")); } else { insert(request, registerForm);//执行注册操作 } } catch (Exception e){ errors.add(ActionErrors.GLOBAL_MESSAGE, new ActionMessage( "register.message.failed")); } //If a message is required, save the specified key(s) //into the request for use by the tag. if (!errors.isEmpty()){ saveErrors(request, errors); request.setAttribute("registerFormBean", registerForm); forward=mapping.findForward("fail"); } else { forward=mapping.findForward("login"); } //Finish with return (forward); } private void insert(HttpServletRequest request, UserForm registerForm){ //调用业务层方法实现注册 UserHandlerBean uhb=new UserHandlerBean(); uhb.add(registerForm.getUname(), registerForm.getUpass(), registerForm .getEmail()); } private boolean isExist(HttpServletRequest request, UserForm registerForm){ //调用业务层方法判断用户名是否已经存在 UserHandlerBean uhb=new UserHandlerBean(); boolean isExist=uhb.isExist(registerForm.getUname()); return isExist; } private boolean valid(HttpServletRequest request, UserForm loginForm){ UserHandlerBean uhb=new UserHandlerBean(); boolean isValid=uhb.valid(loginForm.getUname(), loginForm.getUpass()); return isValid; } } 修改视图页面login.jsp、register.jsp中的form表单中的action属性。 ■login.jsp文件中的form表单的action属性被修改为: ■register.jsp文件中的form表单的action属性被修改为:  修改RegisterForm类。把RegisterForm类重命名为UserForm.  修改struts-config.xml文件中的registerForm对应的form-bean标签。把该标签的name属性值修改为userFrom, type属性值修改为com.wangyingling.struts.forms.UserForm.  在userAction的action中添加如下子标签:  重命名RegisterForm.java为UserForm.java. 3.3.2 使用DispatchAction为原型系统添加退出功能 在注册功能使用DispatchAction实现的基础上再来修改退出功能的实现需要经过下述步骤。  main.jsp文件中的form表单的action属性被修改为:  在UserAction类中添加方法doExit()。该方法的完整代码如代码清单3-2所示。 代码清单3-2 UserAction.java中添加的doExit()方法 public ActionForward doExit(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response){ ActionForward forward=new ActionForward(); //invalidate the original session if exists HttpSession session=request.getSession(false); String username=(String)session.getAttribute("userName"); if (session !=null){ session.invalidate(); } forward=mapping.findForward("exit"); //Finish with return (forward); } 在struts-config.xml文件中,为userAction的action标签添加子标签: 完成控制层优化并删除不需要的其他内容之后,struts-config.xml文件如代码清单3-3所示。 代码清单3-3 struts-config.xml文件 3.4 实验与能力拓展 1. 请根据本书内容,完成案例练习。 2. 请重新建一个工程dispatchDemo,工程要求完成的内容和原型系统要求一致,但是要求从一开始就采用DispatchAction类来完成控制层。 3. 请尝试用MyEclipse的调试功能,跟踪登录、注册和退出的运行,理解采用了DispatchAction的原型系统的运行流程。 4. 假若我们的各个页面提交到userAction时,参数method改为op,那么应该如何修改工程才能运行正常? 5. 使用MyEclipse的调试功能对原型系统进行跟踪运行,从而进一步理解参数值如何关联到DispatchAction的相关方法运行。修改参数值或DispatchAction子类中的方法名,看看会出现什么问题,并解释原因。