UML软件工程组织

Struts原理与实践(2)
作者:罗会波

 

下面,我们就从一个最简单的登录例子入手,以对Struts的主要部分有一些直观而清晰的认识。这个例子功能非常简单,假设有一个名为lhb的用户,其密码是awave,程序要完成的任务是,呈现一个登录界面给用户,如果用户输入的名称和密码都正确返回一个欢迎页面给用户,否则,就返回登录页面要求用户重新登录并显示相应的出错信息。这个例子在我们讲述Struts的基础部分时会反复用到。之所以选用这个简单的程序作为例子是因为不想让过于复杂的业务逻辑来冲淡我们的主题。

因为Struts是建立在MVC设计模式上的框架,你可以遵从标准的开发步骤来开发你的Struts Web应用程序,这些步骤大致可以描述如下:
1定义并生成所有代表应用程序的用户接口的Views,同时生成这些Views所用到的所有ActionForms并将它们添加到struts-config.xml文件中。
2在ApplicationResource.properties文件中添加必要的MessageResources项目
3生成应用程序的控制器。
4在struts-config.xml文件中定义Views与 Controller的关系。
5生成应用程序所需要的model组件
6编译、运行你的应用程序.

下面,我们就一步步按照上面所说的步骤来完成我们的应用程序:

第一步,我们的应用程序的Views部分包含两个.jsp页面:一个是登录页面logon.jsp,另一个是用户登录成功后的用户功能页main.jsp,暂时这个页面只是个简单的欢迎页面。

其中,logon.jsp的代码清单如下:


<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<HTML>
<HEAD>
<TITLE><bean:message key="logon.jsp.title"/></TITLE>
<html:base/>
</HEAD>
<BODY>
<h3><bean:message key="logon.jsp.page.heading"/></h3>
<html:errors/>
<html:form action="/logonAction.do" focus="username">
<TABLE border="0" width="100%">
<TR>
<TH align="right"><bean:message key="logon.jsp.prompt.username"/></TH>
<TD align="left"><html:text property="username"/></TD>
</TR>
<TR>
<TH align="right"><bean:message key="logon.jsp.prompt.password"/></TH>
<TD align="left"><html:password property="password"/></TD>
</TR>
<TR>
<TD align="right">
  <html:submit><bean:message key="logon.jsp.prompt.submit"/></html:submit>
</TD>
<TD align="left">
  <html:reset><bean:message key="logon.jsp.prompt.reset"/></html:reset>
</TD>
</TR>
</TABLE>
</html:form>
</BODY>
</HTML>


main.jsp的代码清单如下:


<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>

<HTML>
<HEAD>
<TITLE><bean:message key="main.jsp.title"/></TITLE>
<html:base/>
</HEAD>
<BODY>
<logic:present name="userInfoForm">
<H3>
  <bean:message key="main.jsp.welcome"/> 
  <bean:write name="userInfoForm" property="username"/>!
</H3>
</logic:present>
</BODY>
</HTML>


首先,我们看一下logon.jsp文件,会发现它有这么两个鲜明的特点:一是文件头部有诸如:
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

这样的指令代码,他们的作用就是指示页面要用到struts的自定义标签,标签库uri是一个逻辑引用,标签库的描述符(tld)的位置在web.xml文件中给出,见上篇文章的配置部分。struts的标签库主要由四组标签组成,它们分别是:
  • bean标签,作用是在jsp中操纵bean
  • logic标签,作用是在jsp中进行流程控制
  • html标签,作用是显示表单等组件
  • template标签,作用是生成动态模板

    关于每类标签的具体作用及语法,因受篇幅限制,不在这里详细讨论,大家可参考struts手册之类的资料。只是心里要明白所谓标签其后面的东西就是一些类,这点与bean有些相似,它们在后端运行,生成标准的html标签返回给浏览器。

    要使用它们显然要把它们的标签库描述文件引入到我们的系统中,这是些以.tld为扩展名的文件,我们要把它们放在 /webapps/mystruts/WEB-INF/目录下。引入struts标签后原来普通的html标签如文本框的标签变成了这样的形式

    Jsp文件的第二个特点,就是页面上根本没有直接写用于显示的文字如:username,password等东西,而是用 这种形式出现。这个特点为国际化编程打下了坚实的基础,关于国际化编程后面的文章还会专门讨论。

    这个简单的应用所用到的ActionForm为UserInfoForm,代码清单如下:

    
    package entity;
    import org.apache.struts.action.ActionForm;
    import org.apache.struts.action.ActionMapping;
    import javax.servlet.http.HttpServletRequest;
    
    public class UserInfoForm extends ActionForm{
    
      private String username;
      private String password;
    
    
      public String getUsername() {
        return (this.username);
      }
      public void setUsername(String username) {
        this.username = username;
      }
    
      public String getPassword() {
        return (this.password);
      }
      public void setPassword(String password) {
        this.password = password;
      }
    }
    


    在你的应用程序的WEB-INF目录下再建一个classes目录,在新建的这个classes目录下再建如下几个目录entity(用于存放ActionForm类)、action目录(用于存放Action类)、bussness目录(用于存放作为Model的业务对象类)。Classes目录下的子目录就是所谓的包,以后,还会根据需要增加相应的包。

    现在,将UserInfoForm.java保存到entity目录中。

    把如下代码添加到 /webapps/mystruts/WEB-INF/struts-config.xml文件中

    
    <form-beans>
        <form-bean name="userInfoForm" type="entity.UserInfoForm" />
      </form-beans>
    


    特别要提醒一下的是:关于ActionForm的大小写,一定要按照上面的写,以免造成不必要的麻烦。

    到此,我们完成了第一步工作。

    第二步,我们建一个名为ApplicationResource.properties的文件,并把它放在 /webapps/mystruts/WEB-INF/classes目录下。它在struts-config.xml的配置信息我们已在第一篇文章的末尾说了,就是:


    目前我们在ApplicationResource.properties文件中加入的内容是:

    
    #Application Resource for the logon.jsp
    logon.jsp.title=The logon page
    logon.jsp.page.heading=Welcome World!
    logon.jsp.prompt.username=Username:
    logon.jsp.prompt.password=Password:
    logon.jsp.prompt.submit=Submit
    logon.jsp.prompt.reset=Reset
    
    #Application Resource for the main.jsp
    main.jsp.title=The main page
    main.jsp.welcome=Welcome:
    


    到此,我们已完成了第二个步骤。

    第三步,我们开始生成和配置Controller组件。

    在前面我们已经提到,Struts应用程序的控制器由org.apache.struts.action.ActionServlet和org.apache.struts.action.Action类组成,其中,前者已由Struts准备好了,后者Struts只是为我们提供了个骨架,我们要做的是为实现应用程序的特定功能而扩展Action类,下面是实现我们登录程序的Action类的代码清单:

    
    package action;
    import java.io.IOException;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    import javax.servlet.http.HttpServletResponse;
    import org.apache.struts.action.Action;
    import org.apache.struts.action.ActionError;
    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.ActionServlet;
    import bussness.UserInfoBo;
    import entity.UserInfoForm;
    public final class LogonAction extends Action {
      
      public ActionForward execute(ActionMapping mapping,
             ActionForm form,
             HttpServletRequest request,
             HttpServletResponse response)
             throws IOException, ServletException {
        UserInfoForm userInfoForm = (UserInfoForm) form;     	
        //从web层获得用户名和口令
        String username = userInfoForm.getUsername().trim();
        String password = userInfoForm.getPassword().trim();
        //声明错误集对象
        ActionErrors errors = new ActionErrors();
        //校验输入
        if(username.equals("")){
          ActionError error=new ActionError("error.missing.username");
          errors.add(ActionErrors.GLOBAL_ERROR,error);
        }
        if(password.equals("")){
          ActionError error=new ActionError("error.missing.password");
          errors.add(ActionErrors.GLOBAL_ERROR,error);
        }
        
        //调用业务逻辑
        if(errors.size()==0){
          String validated = "";
          try{
            UserInfoBo userInfoBo=new UserInfoBo();
            validated =userInfoBo.validatePwd(username,password);
            if(validated.equals("match")){
              //一切正常就保存用户信息并转向成功的页面	
              HttpSession session = request.getSession();
              session.setAttribute("userInfoForm", form);          
          	  return mapping.findForward("success");
            } 
          }
          
          catch(Throwable e){
            //处理可能出现的错误
            e.printStackTrace();
            ActionError error=new ActionError(e.getMessage());
            errors.add(ActionErrors.GLOBAL_ERROR,error);
          }
        }  
        //如出错就转向输入页面,并显示相应的错误信息
        saveErrors(request, errors);    
        return new ActionForward(mapping.getInput());    
      } 
    }
    


    这个action类中有两个错误消息键要加到ApplicationResource.properties文件中,清单如下:

    
    #Application Resource for the LogonAction.java
    error.missing.username=<li><font color="red">missing username</font></li>
    error.missing.password=<li><font color="red">missing password</font></li>>
    


    第四步:在struts-config.xml文件中定义Views与 Controller的关系,也就是配置所谓的ActionMapping。它们在struts-config.xml中的位置是排在 标签后,我们的登录程序的配置清单如下:

    
    <action-mappings>
        <action input="/logon.jsp" name="userInfoForm" path="/logonAction" scope="session" 
    	type="action.LogonAction" validate="false">
          <forward name="success" path="/main.jsp" />      
        </action>
      </action-mappings>
    


    第五步:生成应用程序所需要的model组件,该组件是完成应用程序业务逻辑的地方,现在我的登录程序的业务逻辑很简单,就是判断用户是不是lhb并且其口令是不是awave如果是就返回一个表示匹配的字符串"match",否则,就抛出出错信息。其代码清单如下:

    
    package bussness;
    
    import entity.UserInfoForm;
    
    public class UserInfoBo {
    
      public UserInfoBo(){
        
      }  	
    
      public String validatePwd(String username,String password){
        	
        String validateResult=""; 
           
        if(username.equals("lhb")&&password.equals("awave")){
          validateResult="match";
        }
        else{
          
          throw new RuntimeException("error.noMatch");
        }    	
        
        return validateResult;   
        
      }
    }
    


    将其放在bussness包中。

    我们同样要将其表示错误信息的键值设置在ApplicationResource.properties文件中,清单如下:

    
    #Application Resource for the UserInfoBo.java
    error.noMatch=<li><font color="red">no matched user</font></li>
    


    到此为止,我们已经完成了这个简单登录程序的所有组件。下面就可以享受我们的劳动成果了。

    第六步、编译运行应用程序。

    常规的做法是用Ant来装配和部署Struts应用程序,如果按这个套路,这篇文章就会显得十分冗长乏味,同时也没有太大的必要,因为,用一个IDE一般可以很方便地生成一个应用。因此,我们采用简便的方法,直接编译我们的.java文件。不过这里要注意一点的是:实践证明,要使得编译过程不出错,还必须将struts.jar文件放一份拷贝到 /common/lib目录中,并在环境变量中设置CLASSPATH 其值是 /common/lib/struts.jar;配置好后就可以分别编译entity、bussness及action目录下的.java文件了。编译完成后:打开 /conf目录下的server.xml文件,在 前加上如下语句为我们的应用程序建一个虚拟目录:

    
    <Context path="/mystruts" docBase="mystruts" debug="0"
                     reloadable="true">                 
    		</Context>
    


    启动,tomcat。在浏览器中输入:http://localhost:8080/mystruts/logon.jsp
    如果前面的步骤没有纰漏的话,一个如图所示的登录画面就会出现在你的眼前。



    如果,不输入任何内容直接点击Submit按钮,就会返回到logon.jsp并显示missing username和missing password错误信息;如果输入其他内容,则会返回no matched user的错误;如果输入的用户名是lhb且口令是awave则会显示表示登录成功的欢迎页面。

    上面虽然是一个功能很简单的应用程序,但麻雀虽小,五脏俱全,基本涉及到了struts的主要组成部分。下面我们就来分析一下程序的特点和基本的工作原理。

    首先,我们在浏览器中输入.jsp文件时,后台将struts的自定义标签"翻译"成普通的html标签返回给浏览器,而一些提示信息如作为输入框label的username、password还有按钮上提示信息还有错误信息等都来自MessageResources即ApplicationResource.properties文件中对应的键值。当我们点击Submit按钮时,从web.xml的配置可以看出,请求将被ActionServlet截获。它通过表单中提供的action参数在struts-config.xml文件中查找对应的 项目,如果有对应的ActionForm,它就用表单中数据填充ActionForm的对应属性,本例中的ActionForm为userInfoForm,相应的属性是username和password,这就是所谓的实例化ActionForm。然后,将控制交给对应的Action,本例中是LogonAction,它做的主要工作是对ActionForm中取出的username和password做了一下校验,这里只是简单检验它们是否为空(这些简单的格式化方面的校验应该放在客户端进行,而且struts也为我们提供了一个很好的模式,后面如果有可能会详细介绍)。如果不为空则调用判断用户及口令是否正确的业务逻辑模块UserInfoBo,同时,它会捕获可能出现的错误,然后根据业务逻辑返回的结果将程序导向不同的页面,本例中如果业务逻辑返回的结果是"match"则依据 中的 返回main.jsp页面给浏览器同时在session对象中保存了用户的登录信息;否则,返回输入页面并显示相应的出错信息,完成了上篇文章所说的它的四个主要职责。

    大家一定注意到了,在本例的业务逻辑模块UserInfoBo中,将用户与密码是写死在程序中的,在一个真实的应用程序中是不会这样做的,那些需要永久保存的信息如,username及口令等都会保存在数据库文件之类的永久介质中,下一篇文章我们将介绍在struts中如何访问数据库。
 

版权所有:UML软件工程组织