注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

我的博客

 
 
 

日志

 
 

Struts原理与实践  

2011-08-16 00:49:20|  分类: JAVA |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

(第1部分)

一、 什么是Struts

框架(Framework)是可重用的,半完成的应用程序,可以用来产生专门的定制程序。

您只要细心地研究真实的应用程序,就会发现程序大致上由两类性质不同的组件组成,一类与程序要处理的具体事务密切相关,我们不妨把它们叫做业务组件;另一类是应用服务。比如说:一个税务征管系统和一个图书管理系统会在处理它们的业务方面存在很大的差异,这些直接处理业务的组件由于业务性质的不同不大可能在不同的系统中重用,而另一些组件如决定程序流向的控制、输入的校验、错误处理及标签库等这些只与程序相关的组件在不同的系统中可以很好地得到重用。人们自然会想要是把这些在不同应用程序中有共性的一些东西抽取出来,做成一个半成品程序,这样的半成品就是所谓的程序框架,再做一个新的东西时就不必白手起家,而是可以在这个基础上开始搭建。实际上,有些大型软件企业选择自己搭建这样的框架。但大多数中小型软件企业或者其他组织,没有条件自己建立框架。

Struts作为一个开放原代码的应用框架,在最近几年得到了飞速的发展,在JSP Web应用开发中应用得非常广泛,有的文献上说它已经成为JSP Web应用框架的事实上的标准。那么,究竟什么是Struts呢?

要回答这个问题还得从JSP Web应用的两种基本的结构模式:Model 1和Model 2说起,为了给读者一些实实在在的帮助,并力图让学习曲线变得平坦一些,我想采用实例驱动的方法来逐步深入地回答有关问题,因为,学一门技术的最好方法莫过于在实践中学习、在实践中体会,逐步加深对其精神实质的理解和把握,而不是一上来就引入一大堆新概念让大家觉得无所适从,或者死记硬背一大堆概念而面对一个真正的实际需求束手无策。正如,一个人即使在书本上学成了游泳博士,只要他不下水,我想他也是不大可能真正会游泳的。

Model 1结构如图1所示:

Struts原理与实践 - christhb - 我的博客

图1

mode1 1是一个以JSP文件为中心的模式,在这种模式中JSP页面不仅负责表现逻辑,也负责控制逻辑。专业书籍上称之为逻辑耦合在页面中,这种处理方式,对一些规模很小的项目如:一个简单的留言簿,也没什么太大的坏处,实际上,人们开始接触一些对自己来说是新的东西的时候,比如,用JSP访问数据库时,往往喜欢别人能提供一个包含这一切的单个JSP页面,因为这样在一个页面上他就可以把握全局,便于理解。但是,用Model 1模式开发大型时,程序流向由一些互相能够感知的页面决定,当页面很多时要清楚地把握其流向将是很复杂的事情,当您修改一页时可能会影响相关的很多页面,大有牵一发而动全身的感觉,使得程序的修改与维护变得异常困难;还有一个问题就是程序逻辑开发与页面设计纠缠在一起,既不便于分工合作也不利于代码的重用,这样的程序其健壮性和可伸缩性都不好。

Grady Booch等人在UML用户指南一书中,强调建模的重要性时,打了一个制作狗窝、私人住宅、和大厦的形象比喻来说明人们处理不同规模的事物时应该采用的合理方法一样,人们对不同规模的应用程序也应该采用不同的模式。

为了克服Model 1的缺陷,人们引入了Model 2,如图2所示:

Struts原理与实践 - christhb - 我的博客

图2

它引入了"控制器"这个概念,控制器一般由servlet来担任,客户端的请求不再直接送给一个处理业务逻辑的JSP页面,而是送给这个控制器,再由控制器根据具体的请求调用不同的事务逻辑,并将处理结果返回到合适的页面。因此,这个servlet控制器为应用程序提供了一个进行前-后端处理的中枢。一方面为输入数据的验证、身份认证、日志及实现国际化编程提供了一个合适的切入点;另一方面也提供了将业务逻辑从JSP文件剥离的可能。业务逻辑从JSP页面分离后,JSP文件蜕变成一个单纯完成显示任务的东西,这就是常说的View。而独立出来的事务逻辑变成人们常说的Model,再加上控制器Control本身,就构成了MVC模式。实践证明,MVC模式为大型程序的开发及维护提供了巨大的便利。

其实,MVC开始并不是为Web应用程序提出的模式,传统的MVC要求M将其状态变化通报给V,但由于Web浏览器工作在典型的拉模式而非推模式,很难做到这一点。因此有些人又将用于Web应用的MVC称之为MVC2。正如上面所提到的MVC是一种模式,当然可以有各种不同的具体实现,包括您自己就可以实现一个体现MVC思想的程序框架,Struts就是一种具体实现MVC2的程序框架。它的大致结构如图三所示:

Struts原理与实践 - christhb - 我的博客

图三

图三基本勾勒出了一个基于Struts的应用程序的结构,从左到右,分别是其表示层(view)、控制层(controller)、和模型层(Model)。其表示层使用Struts标签库构建。来自客户的所有需要通过框架的请求统一由叫ActionServlet的servlet接收(ActionServlet Struts已经为我们写好了,只要您应用没有什么特别的要求,它基本上都能满足您的要求),根据接收的请求参数和Struts配置(struts-config.xml)中ActionMapping,将请求送给合适的Action去处理,解决由谁做的问题,它们共同构成Struts的控制器。Action则是Struts应用中真正干活的组件,开发人员一般都要在这里耗费大量的时间,它解决的是做什么的问题,它通过调用需要的业务组件(模型)来完成应用的业务,业务组件解决的是如何做的问题,并将执行的结果返回一个代表所需的描绘响应的JSP(或Action)的ActionForward对象给ActionServlet以将响应呈现给客户。

过程如图四所示:

Struts原理与实践 - christhb - 我的博客

图四

这里要特别说明一下的是:就是Action这个类,上面已经说到了它是Struts中真正干活的地方,也是值得我们高度关注的地方。可是,关于它到底是属于控制层还是属于模型层,存在两种不同的意见,一种认为它属于模型层,如:《JSP Web编程指南》;另一些则认为它属于控制层如:《Programming Jakarta Struts》、《Mastering Jakarta Struts》和《Struts Kick Start》等认为它是控制器的一部分,还有其他一些书如《Struts in Action》也建议要避免将业务逻辑放在Action类中,也就是说,图3中Action后的括号中的内容应该从中移出,但实际中确有一些系统将比较简单的且不打算重用的业务逻辑放在Action中,所以在图中还是这样表示。显然,将业务对象从Action分离出来后有利于它的重用,同时也增强了应用程序的健壮性和设计的灵活性。因此,它实际上可以看作是Controller与Model的适配器,如果硬要把它归于那一部分,笔者更倾向于后一种看法,即它是Controller的一部分,换句话说,它不应该包含过多的业务逻辑,而应该只是简单地收集业务方法所需要的数据并传递给业务对象。实际上,它的主要职责是:

  • 校验前提条件或者声明
  • 调用需要的业务逻辑方法
  • 检测或处理其他错误
  • 路由控制到相关视图

    上面这样简单的描述,初学者可能会感到有些难以接受,下面举个比较具体的例子来进一步帮助我们理解。如:假设,我们做的是个电子商务程序,现在程序要完成的操作任务是提交定单并返回定单号给客户,这就是关于做什么的问题,应该由Action类完成,但具体怎么获得数据库连接,插入定单数据到数据库表中,又怎么从数据库表中取得这个定单号(一般是自增数据列的数据),这一系列复杂的问题,这都是解决怎么做的问题,则应该由一个(假设名为orderBo)业务对象即Model来完成。orderBo可能用一个返回整型值的名为submitOrder的方法来做这件事,Action则是先校验定单数据是否正确,以免常说的垃圾进垃圾出;如果正确则简单地调用orderBo的submitOrder方法来得到定单号;它还要处理在调用过程中可能出现任何错误;最后根据不同的情况返回不同的结果给客户。

    二、为什么要使用Struts框架

    既然本文的开始就说了,自己可以建这种框架,为什么要使用Struts呢?我想下面列举的这些理由是显而易见的:首先,它是建立在MVC这种公认的好的模式上的,Struts在M、V和C上都有涉及,但它主要是提供一个好的控制器和一套定制的标签库上,也就是说它的着力点在C和V上,因此,它天生就有MVC所带来的一系列优点,如:结构层次分明,高可重用性,增加了程序的健壮性和可伸缩性,便于开发与设计分工,提供集中统一的权限控制、校验、国际化、日志等等;其次,它是个开源项目得到了包括它的发明者Craig R.McClanahan在内的一些程序大师和高手持续而细心的呵护,并且经受了实战的检验,使其功能越来越强大,体系也日臻完善;最后,是它对其他技术和框架显示出很好的融合性。如,现在,它已经与tiles融为一体,可以展望,它很快就会与JSF等融会在一起。当然,和其他任何技术一样,它也不是十全十美的,如:它对类和一些属性、参数的命名显得有些随意,给使用带来一些不便;还有如Action类execute方法的只能接收一个ActionForm参数等。但瑕不掩瑜,这些没有影响它被广泛使用。

    三、Struts的安装与基本配置

    我们主要针对Struts1.1版本进行讲解,这里假定读者已经配置好java运行环境和相应的Web容器,本文例子所使用的是j2sdk和Tomcat4.1.27。下面,将采用类似于step by step的方式介绍其基础部分。

    安装Struts
    到http://jakarta.apache.org/ 下载Struts的安装文件,本文例子使用的是1.1版。

    接下来您要进行如下几个步骤来完成安装:
    1、解压下载的安装文件到您的本地硬盘
    2、生成一个新的Web应用,假设我们生成的应用程序的根目录在/Webapps/mystruts目录。在server.xml文件中为该应用新建一个别名如/mystruts
    3、从第1步解压的文件中拷贝下列jar文件到/Webapps/mystruts/WEB-INF/lib目录,主要文件有如下一些。

    struts.jar  commons-beanutils.jar  commons-collections.jar  commons-dbcp.jar  commons-digester.jar  commons-logging.jar  commons-pool.jar  commons-services.jar  commons-validator.jar


    4、创建一个web.xml文件,这是一个基于servlet的Web应用程序都需要的部署描述文件,一个Struts Web应用,在本质上也是一个基于servlet的Web应用,它也不能例外。

    Struts有两个组件要在该文件中进行配置,它们是:ActionServlet和标签库。下面是一个配置清单:

    <?xml version="1.0" encoding="UTF-8"?>  <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3  //EN" "http://java.sun.com/dtd/web-app_2_3.dtd">  <web-app>    <servlet>      <servlet-name>action</servlet-name>      <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>      <init-param>        <param-name>config</param-name>        <param-value>/WEB-INF/struts-config.xml</param-value>      </init-param>      <init-param>        <param-name>debug</param-name>        <param-value>2</param-value>      </init-param>      <load-on-startup>2</load-on-startup>    </servlet>    <servlet-mapping>      <servlet-name>action</servlet-name>      <url-pattern>*.do</url-pattern>    </servlet-mapping>    <taglib>      <taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri>      <taglib-location>/WEB-INF/struts-bean.tld</taglib-location>    </taglib>    <taglib>      <taglib-uri>/WEB-INF/struts-html.tld</taglib-uri>      <taglib-location>/WEB-INF/struts-html.tld</taglib-location>    </taglib>    <taglib>      <taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri>      <taglib-location>/WEB-INF/struts-logic.tld</taglib-location>    </taglib>  </web-app>


    上面我们在web.xml中完成了对servlet和标签库的基本配置,而更多的框架组件要在struts-config.xml中进行配置:

    5、创建一个基本的struts-config.xml文件,并把它放在/Webapps/mystruts/WEB-INF/目录中,该文件是基于Struts应用程序的配置描述文件,它将MVC结构中的各组件结合在一起,开发的过程中会不断对它进行充实和更改。在Struts1.0时,一个应用只能有一个这样的文件,给分工开发带来了一些不便,在Struts1.1时,可以有多个这样的文件,将上述缺点克服了。需在该文件中配置的组件有:data-sources

    global-execptions  form-beans  global-forwards  action-mappings  controller  message-resources  plug-in


    配置清单如下:

    <?xml version="1.0" encoding="UTF-8"?>  <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1  //EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">  <struts-config>    <message-resources parameter="ApplicationResources" />  </struts-config>


    到此为止,我们已经具备了完成一个最简单Struts应用的所需的各种组件。前面已经提到,在开发过程中我们会不断充实和修改上面两个配置描述文件。下面我们将实际做一个非常简单的应用程序来体验一下Struts应用开发的真实过程,以期对其有一个真实的认识。在完成基础部分的介绍后,笔者会给出一些在实际开发中经常用到而又让初学者感到有些难度的实例。最后,会介绍Struts与其他框架的关系及结合它们生成应用程序的例子.

    下面,我们就从一个最简单的登录例子入手,以对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编译、运行你的应用程序.

    (第2部分)


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

    第一步,我们的应用程序的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文件,会发现它有这么两个鲜明的特点:一是文件头部有诸如:



    这样的指令代码,他们的作用就是指示页面要用到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
    如果前面的步骤没有纰漏的话,一个如图所示的登录画面就会出现在你的眼前。

    Struts原理与实践 - christhb - 我的博客

    如果,不输入任何内容直接点击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中如何访问数据库
  • (第三部分)

    一、JDBC的工作原理

    Struts在本质上是java程序,要在Struts应用程序中访问数据库,首先,必须搞清楚Java Database Connectivity API(JDBC)的工作原理。正如其名字揭示的,JDBC库提供了一个底层API,用来支持独立于任何特定SQL实现的基本SQL功能。提供数据库访问的基本功能。它是将各种数据库访问的公共概念抽取出来组成的类和接口。JDBC API包括两个包:java.sql(称之为JDBC内核API)和javax.sql(称之为JDBC标准扩展)。它们合在一起,包含了用Java开发数据库应用程序所需的类。这些类或接口主要有:
    Java.sql.DriverManager
    Java.sql.Driver
    Java.sql.Connection
    Java.sql.Statement
    Java.sql.PreparedStatement
    Java.sql.ResultSet等

    这使得从Java程序发送SQL语句到数据库变得比较容易,并且适合所有SQL方言。也就是说为一种数据库如Oracle写好了java应用程序后,没有必要再为MS SQL Server再重新写一遍。而是可以针对各种数据库系统都使用同一个java应用程序。这样表述大家可能有些难以接受,我们这里可以打一个比方:联合国开会时,联合国的成员国的与会者(相当我们这里的具体的数据库管理系统)往往都有自己的语言(方言)。大会发言人(相当于我们这里的java应用程序)不可能用各种语言来发言。你只需要使用一种语言(相当于我们这里的JDBC)来发言就行了。那么怎么保证各成员国的与会者都听懂发言呢,这就要依靠同声翻译(相当于我们这里的JDBC驱动程序)。实际上是驱动程序将java程序中的SQL语句翻译成具体的数据库能执行的语句,再交由相应的数据库管理系统去执行。因此,使用JDBC API访问数据库时,我们要针对不同的数据库采用不同的驱动程序,驱动程序实际上是适合特定的数据库JDBC接口的具体实现,它们一般具有如下三种功能:

  • 建立一个与数据源的连接
  • 发送SQL语句到数据源
  • 取回结果集

    那么,JDBC具体是如何工作的呢?

    Java.sql.DriverManager装载驱动程序,当Java.sql.DriverManager的getConnection()方法被调用时,DriverManager试图在已经注册的驱动程序中为数据库(也可以是表格化的数据源)的URL寻找一个合适的驱动程序,并将数据库的URL传到驱动程序的acceptsURL()方法中,驱动程序确认自己有连接到该URL的能力。生成的连接Connection表示与特定的数据库的会话。Statement(包括PreparedStatement和CallableStatement)对象作为在给定Connection上执行SQL语句的容器。执行完语句后生成ResultSet结果集对象,通过结果集的一系列getter就可以访问表中各列的数据。

    这里,是讲的JDBC的基本工作过程,实际应用中,往往会使用JDBC扩展对象如DataSource等,限于篇幅,就不在此详细讨论了。

    二、访问数据库所要做的基本配置

    我们以访问MS SQL Server2000数据库为例,介绍其基本的配置情况。首先,要到微软网站去下载JDBC的驱动程序,运行setup.exe将得到的三个文件:msbase.jar、mssqlserver.jar及msutil.jar放在/webapps/mystruts/WEB-INF/lib目录下。

    在struts-config.xml文件中配置数据源

    这里,有一点要引起大家的注意的,就是,struts-config.xml中配置的各个项目是有一定的顺序要求的,几个主要项目的顺序大致是这样的:

    data-sources  form-beans  action-mappings  message-resources  plug-in


    在配置时要遵守上述顺序

    <data-sources>      <data-source key="A" type="org.apache.commons.dbcp.BasicDataSource">        <set-property property="driverClassName"            value="com.microsoft.jdbc.sqlserver.SQLServerDriver" />        <set-property property="url"            value="jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=mystruts;SelectMethod=cursor" />        <set-property property="username" value="sa" />        <set-property property="password" value="yourpwd" />        <set-property property="maxActive" value="10" />        <set-property property="maxWait" value="5000" />        <set-property property="defaultAutoCommit" value="false" />        <set-property property="defaultReadOnly" value="false" />      </data-source>    </data-sources>


    我们来对这段配置代码做一个简单的说明:

    这句中,如果您的struts应用程序中只配置一个数据源则key="A"可以不要,而配置多个数据源时就要用这个键值区别,也就是说,可以为一个应用程序配置多个数据源让它访问多个数据库。

    <set-property property="url"           value="jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=mystruts;          SelectMethod=cursor" />


    这句中的sqlserver://127.0.0.1:1433;DatabaseName=mystruts;的数据库服务器名(本例是用代表本机的ip地址)和数据库名称要与您的具体情况相同。同时,还要注意访问数据库的用户名和口令也要合乎您的实际情况。

    表示最大的活动连接数,这也说明这些连接是池化(pooling)的。

    表示对数据库的增、删、改操作必须显式地提交。即必须使用connect.commit();这样的命令才能真正让数据库表中的记录作相应的改变。设置成这样方便用户组织自己的数据库事务。

    三、现在我们就来扩展前面我们讲的那个登录的例子,让它访问存储在数据库表中的用户名和口令信息,同时也让它给出的出错信息更明确一些。

    为此,我们先要做一些准备工作,如果您还没有安装MS SQL Server2000请先安装,并下载最新的补丁包。再建一个名为mystruts的数据库,并在该数据库中建一个名为userInfo的表,该表有两个字段既:username和password,它们的字段类型都为varchar(10),其中username为主键。在该表中输入一条记录,username和password的字段值分别为lhb和awave。到此准备工作就基本做好了。

    为了访问数据库,首先,要修改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;  import javax.sql.DataSource;  import java.sql.Connection;  import java.sql.SQLException;    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();      //声明数据源和连接对象      DataSource dataSource;      Connection cnn=null;        //校验输入      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{          //取得数据库连接          dataSource = getDataSource(request,"A");          cnn = dataSource.getConnection();            UserInfoBo userInfoBo=new UserInfoBo(cnn);          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());    }  }


    注意:dataSource = getDataSource(request,"A");这句中,如果配置中只有一个数据源,且没有key="A",则这句应写为dataSource = getDataSource(request);

    从清单上可以看出,主要就是增加了访问数据库的代码。同时,我们的业务对象的形式也发生了一个变化,原来没有参数,现在有一个代表数据库连接的参数cnn,因此我们也要对业务对象进行适当地修改。

    更改后的业务对象代码清单如下:

    package bussness;    import entity.UserInfoForm;  import java.sql.Connection;  import java.sql.SQLException;  import java.lang.Exception;  import db.UserInfoDao;    public class UserInfoBo {    private Connection cnn=null;      public UserInfoBo(Connection cnn){      this.cnn=cnn;    }      public String validatePwd(String username,String password){        String validateResult="";           try{        UserInfoDao userInfoDao = new UserInfoDao(cnn);        validateResult=userInfoDao.validatePwd(username,password);        if(validateResult.equals("error.logon.invalid")){          //如果用户名与口令不匹配则报此错          throw new RuntimeException("error.logon.invalid");         }        else if(validateResult.equals("error.removed.user")){          //如果找不到用户则报此错,这样用户看到的出错信息会更详细          throw new RuntimeException("error.removed.user");         }      }      catch(Exception e){        throw new RuntimeException(e.getMessage());      }      finally{        try{          if(cnn!=null){            cnn.close();          }        }        catch(SQLException sqle){          sqle.printStackTrace();          throw new RuntimeException("error.unexpected");        }      }      return validateResult;    }  }


    这个业务对象的代码还是比较简单的,重点要讲的就是它在validatePwd方法中调用了一个名叫UserInfoDao的对象,它就是真正进行数据库操作的数据访问对象。其代码清单如下:

    package db;  import entity.UserInfoForm;  import java.sql.*;    public class UserInfoDao {    private Connection con;      public UserInfoDao(Connection con) {      this.con=con;    }        public String validatePwd(String username,String password){      PreparedStatement ps=null;      ResultSet rs=null;      String validated="error.logon.invalid";      UserInfoForm userInfoForm=null;      String sql="select * from userInfo where username=?";      try{        if(con.isClosed()){          throw new IllegalStateException("error.unexpected");          }        ps=con.prepareStatement(sql);        ps.setString(1,username);        rs=ps.executeQuery();        if(rs.next()){          if(!rs.getString("password").trim().equals(password)){            return validated;//口令不正确返回口令不匹配信息                      }          else{              validated = "match";//口令正确返回口令匹配信息            return validated;          }        }else{                    validated="error.removed.user";//没有找到该用户          return validated;                  }        }catch(SQLException e){          e.printStackTrace();          throw new RuntimeException("error.unexpected");      }finally{        try{          if(ps!=null)            ps.close();          if(rs!=null)            rs.close();        }catch(SQLException e){          e.printStackTrace();          throw new RuntimeException("error.unexpected");        }      }    }  }


    下面,简单地分析一下数据访问对象的工作过程:

    要访问数据库,一般要经历的如下几个步骤:
  • 获得到数据库的连接
  • 创建SQL语句
  • 执行SQL语句
  • 管理结果集

    其中,得到数据库的连接本例中是在Action类中完成的,代码如下:
    dataSource = getDataSource(request,"A");
    cnn = dataSource.getConnection();

    Action在调用业务对象时将连接作为一个参数传给业务对象,再由业务对象传给数据库访问对象。

    要说明一点的是,要将struts-legacy.jar文件放在/webapps/mystruts/WEB-INF/lib目录下。

    我们要在/webapps/mystruts/WEB-INF/classes目录下再建一个名叫db的子目录,将数据访问类以UserInfoDao.java文件名保存在该子目录中。按照上篇文章介绍的方法,编译各个包中的.java文件。就可以启动Tomcat重新运行您的程序了。

    细心一点的读者可能都注意到了,到目前为止,我们程序中的各种消息都不是用中文表示的,在下一篇文章中,我们将讨论Struts的国际化编程即所谓的i18n编程,对我们在编程中经常遇到的乱码问题也一同作些分析。

    参考文献:
    《JSP Web 编程指南》---电子工业出版社 Jayson Falkner等著 司光亚 牛红等译
    《Java数据库编程宝典》John O'Donahue等著 甑广启 于耀等译
    《Struts in Action》Ted Husted Cedric Dumoulin George Franciscus David Winterfeldt著
    《Programming Jakarta Struts》Chuck Cavaness著
    《Mastering Jakarta Struts》James Goodwill著
    《Struts Kick Start》James Turner Kevin Bedell著

  •   评论这张
     
    阅读(190)| 评论(0)
    推荐 转载

    历史上的今天

    评论

    <#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
     
     
     
     
     
     
     
     
     
     
     
     
     
     

    页脚

    网易公司版权所有 ©1997-2018