框架(Framework)是可重用的,半完成的应用程序,可以用来产生专门的定制程序。
您只要细心地研究真实的应用程序,就会发现程序大致上由两类性质不同的组件组成, 一类与程序要处理的具体事务密切相关,我们不妨把它们叫做业务组件;另一类是应用服务。比如说:一个税务征管系统和一个图书管理系统会在处理它们的业务方面存在很大的差异,这些直接处理业务的组件由于业务性质的不同不大可能在不同的系统中重用,而另一些组件如决定程序流向的控制、输入的校验、错误处理及标签库等这些只与程序相关的组件在不同的系统中可以很好地得到重用。人们自然会想要是把这些在不同应用程序中有共性的一些东西抽取出来,做成一个半成品程序,这样的半成品就是所谓的程序框架,再做一个新的东西时就不必白手起家,而是可以在这个基础上开始搭建。实际上,有些大型软件企业选择自己搭建这样的框架。但大多数中小型软件企业或者其他组织,没有条件自己建立框架。
Struts作为一个开放原代码的应用框架,在最近几年得到了飞速的发展,在JSP Web应用开发中应用得非常广泛,有的文献上说它已经成为JSP Web应用框架的事实上的标准。那么,究竟什么是Struts呢?
要回答这个问题还得从JSP Web应用的两种基本的结构模式:Model 1和Model 2说起,为了给读者一些实实在在的帮助,并力图让学习曲线变得平坦一些,我想采用实例驱动的方法来逐步深入地回答有关问题,因为,学一门技术的最好方法莫过于在实践中学习、在实践中体会,逐步加深对其精神实质的理解和把握,而不是一上来就引入一大堆新概念让大家觉得无所适从,或者死记硬背一大堆概念而面对一个真正的实际需求束手无策。正如,一个人即使在书本上学成了游泳博士,只要他不下水,我想他也是不大可能真正会游泳的。
Model 1结构如图1所示:
图1
mode1 1是一个以JSP文件为中心的模式,在这种模式中JSP页面不仅负责表现逻辑,也负责控制逻辑。专业书籍上称之为逻辑耦合在页面中,这种处理方式,对一些规模很小的项目如:一个简单的留言簿,也没什么太大的坏处,实际上,人们开始接触一些对自己来说是新的东西的时候,比如,用JSP访问数据库时,往往喜欢别人能提供一个包含这一切的单个JSP页面,因为这样在一个页面上他就可以把握全局,便于理解。但是,用Model 1模式开发大型时,程序流向由一些互相能够感知的页面决定,当页面很多时要清楚地把握其流向将是很复杂的事情,当您修改一页时可能会影响相关的很多页面,大有牵一发而动全身的感觉,使得程序的修改与维护变得异常困难;还有一个问题就是程序逻辑开发与页面设计纠缠在一起,既不便于分工合作也不利于代码的重用,这样的程序其健壮性和可伸缩性都不好。
Grady Booch等人在UML用户指南一书中,强调建模的重要性时,打了一个制作狗窝、私人住宅、和大厦的形象比喻来说明人们处理不同规模的事物时应该采用的合理方法一样,人们对不同规模的应用程序也应该采用不同的模式。
为了克服Model 1的缺陷,人们引入了Model 2,如图2所示:
图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的应用程序的结构,从左到右,分别是其 表示层(view)、控制层(controller)、和模型层(Model)。 其表示层使用Struts标签库构建。来自客户的所有需要通过框架的请求统一由叫ActionServlet的servlet接收(ActionServlet Struts已经为我们写好了,只要您应用没有什么特别的要求,它基本上都能满足您的要求),根据接收的请求参数和Struts配置(struts-config.xml)中ActionMapping,将请求送给合适的Action去处理,解决由谁做的问题,它们共同构成Struts的控制器。Action则是Struts应用中真正干活的组件,开发人员一般都要在这里耗费大量的时间,它解决的是做什么的问题,它通过调用需要的业务组件(模型)来完成应用的业务,业务组件解决的是如何做的问题,并将执行的结果返回一个代表所需的描绘响应的JSP(或Action)的ActionForward对象给ActionServlet以将响应呈现给客户。
过程如图四所示:
图四
这里要特别说明一下的是:就是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
如果前面的步骤没有纰漏的话,一个如图所示的登录画面就会出现在你的眼前。
如果,不输入任何内容直接点击Submit按钮,就会返回到logon.jsp并显示missing username和missing password错误信息;如果输入其他内容,则会返回no matched user的错误;如果输入的用户名是lhb且口令是awave则会显示表示登录成功的欢迎页面。