第5章〓软件实现 在完成系统分析和设计之后,进入软件实现环节。软件实现的目标是: 利用已有的资产和构件,遵循程序开发规范,按照系统详细设计说明书中的数据结构、算法和模块实现等方面的设计,用面向对象技术实现目标系统的功能、性能、接口、界面等要求。 在eGov电子政务系统中,基于StrutsSpringHibernate架构完成了软件实现步骤。 5.1StrutsSpringHibernate架构概述 目前,国内外信息化建设已经进入以Web应用为核心的阶段。Java语言是开发Web应用的最佳语言之一。然而,就算用Java建造一个不是很复杂的Web应用系统,也不是一件轻松的事情。有很多东西需要仔细考虑,例如,要考虑怎样建立用户接口,在哪里处理业务逻辑,怎样持久化数据。幸运的是,Web应用面临的一些问题已经由曾遇到过这类问题的开发者通过建立相应的框架解决了。事实上,企业开发中直接采用的往往并不是某些具体的技术,例如大家熟悉的Core Java、JDBC、Servlet、JSP等,而是基于这些技术的应用框架,Struts、Spring和Hibernate就是其中最常用的几种。 这里讨论一个使用3种开源框架的策略: 表示层用Struts,业务层用Spring,持久层用Hibernate。 接下来介绍这些技术。 5.2Struts技术 在eGov电子政务系统中,使用Struts框架来实现用户接口(User Interface,UI)层及其与后端应用层之间的交互。软件工程与项目案例教程第5章软件实现5.2.1Struts概述 Struts是由Craig McClanahan在2001年发布的Web框架。经过多年的改进,Struts越来越稳定和成熟。Struts 1.x版本被统称为Struts 1。2006年,Struts推出全新框架,命名为Struts 2,它改进了Struts 1的一些主要不足。 Struts 1的主要缺点如下: (1) 支持的表示层技术单一。Struts 1只支持JSP视图技术,当然,可以部分支持Velocity等技术。 (2) Struts 1与Servlet API严重耦合,难于测试。 例如,Struts 1的Action的execute方法有4个参数: ActionMapping、ActionForm、HttpServletRequest和HttpServletResponse,初始化这4个参数比较困难,尤其是HttpServletRequest和HttpServletResponse两个参数,因为这两个参数通常是由容器进行注入的。如果脱离Web服务器,Action的测试是很困难的。 (3) Struts 1的侵入性太大。一个Action中包含了大量的Struts API,例如ActionMapping、ActionForm、ActionForward等。这种侵入式设计最大的弱点在于切换框架相当困难,代码复用率较低,不利于重构。 Struts 2在另一个MVC框架——WebWork的优良基础设计之上进行了一次巨大的升级。注意,Struts 2不是基于Struts 1,而是基于WebWork的。Struts 2针对Struts 1的不足,提出了全新的解决方案。 5.2.2MVC与Struts映射 Struts的体系结构实现了MVC设计模式的概念,它将这些概念映射到Web应用的组件和概念中。 1. 控制器层 与Struts 1使用ActionServlet作为控制器不同,Struts 2使用了Filter技术,FilterDispatcher是Struts 2框架的核心控制器,该控制器负责拦截和过滤所有的用户请求。如果用户请求以Action结尾,该请求将被转入Struts框架来进行处理。Struts 2框架获得了.action请求后,将根据该请求前面的名称部分决定调用哪个业务控制Action类。例如,对于test.action请求,Struts 2框架调用名为test的Action来处理该请求。 Struts 2应用中的Action都被定义在struts.xml文件中。在该文件中配置Action时,主要定义了该Action的name属性和class属性,其中,name属性决定了该Action处理哪个用户请求,而class属性决定了该Action的实现类。例如,<action name="registAction" class="com.ascent.action.RegistAction">。 用于处理用户请求的Action实例并没有与Servlet API耦合,所以无法直接处理用户请求。为此,Struts 2框架提供了系列拦截器,该系列拦截器负责将HttpServletRequest请求中的请求参数解析出来,传入Action,并回调Action的execute方法来处理用户请求。 2. 显示层 Struts 2框架改进了Struts 1只能使用JSP作为视图技术的缺点(当然,Struts 1可以部分支持Velocity等技术)。Struts 2框架允许使用其他视图技术(如FreeMarker等)作为显示层。 当Struts 2的控制器调用业务逻辑组件处理完用户请求后,会返回一个字符串,该字符串代表逻辑视图,它并未与任何视图技术关联。 当在struts.xml文件中配置Action时,还要为Action元素指定系列result子元素,每个result子元素定义上述逻辑视图和物理视图之间的映射。一般情况下,使用JSP技术作为视图,故配置result子元素时没有type属性,默认使用JSP作为视图资源。例如,<result name="error">/product/register.jsp</result>。 如果需要在Struts 2中使用其他视图技术,则可以在配置result子元素时指定相应的type属性。例如,为type属性指定的值可以是velocity。 3. 模型层 模型层指的是后端业务逻辑处理,Action调用它来处理用户请求。当控制器需要获得业务逻辑组件实例时,通常并不会直接获得,而是通过工厂模式来获得,或者利用其他IoC容器(如Spring容器)来管理业务逻辑组件的实例。在后面会详细展开这些技术。 基于MVC的系统中的业务逻辑组件还可以细分为两个概念: 系统的内部状态以及能够改变状态的行为。可以把内部状态当作名词(事物),把行为当作动词(对事物状态的改变),它们使用JavaBean、EJB或Web Service实现。 5.2.3Struts 2的工作流程和配置文件1. Struts 2的工作流程Struts 2的工作流程是WebWork的升级,而不是Struts 1的升级。Struts 2的体系如图51所示。 图51Struts 2的体系 Struts 2的工作流程如下: (1) 浏览器发送请求,例如请求/regist.action、/reports/myreport.pdf等。 (2) 核心控制器FilterDispatcher根据请求调用合适的Action。 (3) WebWork的拦截器链自动对请求应用通用功能,例如验证、工作流或文件上传等功能。 (4) 回调Action的execute方法,该方法先获取用户请求参数,然后执行某种业务操作,既可以将数据保存到数据库,也可以从数据库中检索信息。实际上,因为Action只是一个控制器,它会调用业务逻辑组件(即模型层)来处理用户的请求。 (5) Action的execute方法的处理结果信息将被输出到浏览器中,可以是HTML页面或者图像,也可以是PDF文档或者其他文档。Struts 2支持的视图技术非常多,既支持JSP,也支持Velocity、FreeMarker等模板技术。 要想更深入地掌握Struts的核心技术和流程,就要先理解Struts的配置文件。 2. Struts 2的配置文件 Struts 2的配置文件有两个,包括配置Action的struts.xml文件和配置Struts 2全局属性的struts.properties文件。接下来分别对它们进行讨论。 1) struts.xml文件 Struts框架的核心配置文件就是struts.xml。在默认情况下,Struts 2框架将自动加载放在WEBINF/classes路径下的struts.xml文件。该文件主要负责管理应用中的Action映射、该Action包含的result定义等以及其他相关配置。 以下是eGov电子政务系统中的struts.xml内容:<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation// DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <constantname="struts.i18n.encoding" value="GBK"/> <package name="struts2" extends="json-default"> <action name="authAction" class="com.ascent.action.AuthAction" method="{1}"> <result>/jsp/authplot.jsp</result> <result name="qxx">/jsp/manager.jsp</result> <result name="de" type="json">/jsp/authplot.jsp</result> <result name="am">/jsp/authmanager.jsp</result> </action> <action name="exitAction" class="com.ascent.action.ExitAction"> <result>/index.jsp</result> </action> <action name="loginAction" class="com.ascent.action.LoginAction"> <result>/index.jsp</result> </action> <action name="newsAction" class="com.ascent.action.NewsAction" method="{1}"> <result>/jsp/manager.jsp</result> <result name="auditing">/jsp/auditing.jsp </result> <result name="auditings">/jsp/auditings.jsp </result> <result name="myissue">/jsp/myissue.jsp</result> <result name="view">/jsp/view.jsp</result> <result name="list">/jsp/list.jsp</result> <result name="newsselect">/jsp/updatenews.jsp</result> </action> </package> </struts>2) struts.properties文件 除了struts.xml文件外,Struts 2框架还包含struts.properties文件,该文件通常放在Web应用的WEBINF/classes路径下。它定义了Struts 2框架的大量属性,开发者可以通过改变这些属性来满足个性化应用的需求。 在有些时候,开发者不喜欢使用额外的struts.properties 文件。前面提到,Struts 2允许在struts.xml文件中管理Struts 2属性,在该文件中通过配置constant元素,一样可以配置这些属性。建议尽量在struts.xml文件中配置Struts 2常量。 5.2.4创建Controller组件 Struts的核心是Controller组件。它是连接Model组件和View组件的桥梁,也是理解Struts 2架构的关键。Struts 2的控制器由两个部分组成: FilterDispatcher和Action。 1. FilterDispatcher 任何MVC框架都需要与Web应用整合,这就离不开web.xml文件,只有配置在web.xml 文件中Filter/Servlet才会被Web应用加载。对于Struts 2框架而言,需要加载FilterDispatcher。因为Struts 2将核心控制器设计成Filter,而不是一个Servlet。因此,为了让Web应用加载FilterDispatcher,需要在web.xml文件中配置FilterDispatcher。 配置FilterDispatcher的代码片段如下:<!--配置Struts 2框架的核心Filter--> <filter> <!--配置Struts 2核心Filter的名字--> <filter-name>struts</filter-name> <!--配置Struts 2核心Filter的实现类--> <filter-class>org.apache.struts2.dispatcher.Filter Dispatcher </filter-class> <init-param> <!--配置Struts 2框架默认加载的Action包结构--> <param-name>actionpackages</param-name> <param-value> org.apache.struts2.showcase.person</param- value> </init-param> <!--配置Struts 2框架的配置提供者类--> <init-param> < param-name>configProviders</param-name> <param-value>lee.MyConfigurationProvider</param-value> </init-param> </filter>正如上面所示,当配置Struts 2的FilterDispatcher类时,可以指定一系列的初始化参数。为该Filter配置初始化参数时,其中的3个初始化参数有特殊意义: (1) Config。该参数的值是一系列以英文逗号(,)隔开的字符串,每个字符串都有一个XML配置文件的位置。Struts 2框架将自动加载该属性指定的配置文件。 (2) ActionPackages。该参数的值也是一系列以英文逗号隔开的字符串,每个字符串都是一个包空间,Struts 2框架将扫描这些包空间下的Action类。 (3) ConfigurationProviders。如果用户需要实现自己的ConfigurationProvider类,则可以提供一个或多个实现了ConfigurationProvider接口的类,然后将这些类的类名设置成该属性的值,类名之间以英文逗号隔开。 除此之外,还可在此处配置Struts 2常量,每个<initparam>元素配置一个Struts 2常量,其中<paramname>子元素指定了常量name,而<paramvalue>子元素指定了常量value。 在web.xml文件中配置该Filter,还需要配置该Filter拦截的URL。通常,让该Filter拦截所有的用户请求,因此使用通配符来配置该Filter拦截的URL。 下面是配置该Filter拦截URL的代码片段:<!--配置Filter拦截的URL--> <filter-mapping> <!--配置Struts 2的核心FilterDispatcher以拦截所有用户请求--> <filter-name>struts</filter-name> <url-pattern>/</url- pattern> </filter-mapping>配置了Struts 2的核心FilterDispatcher后,就基本完成了Struts 2在web.xml文件中的配置了。 2. Action的开发 对于Struts 2应用而言,Action是应用系统的核心,也称Action为业务控制器。开发者需要提供大量的Action类,并在struts.xml文件中配置这个Action类。 1) 实现Action类 相对于Struts 1而言,Struts 2采用了低侵入式的设计。Struts 2的Action类是普通的POJO(通常应该包含一个无参数的execute方法),从而具有很好的代码复用性。 例如,用户登录模块LoginAction类的代码如下:package com.ascent.action; import com.ascent.po.Usr; import com.opensymphony.xwork2.ActionContext; public class LoginAction extends BaseAction { public String username; public String password; public String getPassword() { return password; } public void setPassword(String password) { this.password=password; } public String getUsername() { return username; } public void setUsername(String username) { this.username=username; } public String execute(){ if(this.getUsername()!=null && this.getPassword()!=null){ Usr user=userService.userLogin(this.getUsername(), this.getPassword()); if(user!=null){ ActionContext.getContext().getSession().put("user",user); } else { ActionContext.getContext().put("tip","账号或密码不正确"); return SUCCESS; } } else { ActionContext.getContext().put("tip","账号或密码不正确"); return SUCCESS; } return SUCCESS; } }注意: 上面的LoginAction类继承了BaseAction类,它是本书作者开发的一个帮助类,是为了对Spring集成提供支持的工具类。它可以帮助读者更好地理解Spring,有兴趣的读者可参考配套电子资源中的源代码。 上面的Action类只是一个普通类,这个Java类提供了两个属性: username和password,这两个属性分别对应两个HTTP请求参数。 为了让用户开发的Action类更规范,Struts 2提供了一个Action接口。该接口定义了Struts 2的Action处理类应该实现的规范。它只定义了一个execute方法,该接口的规范规定了Action类应该包含能够返回一个字符串的方法。除此之外,该接口还定义了5个字符串常量,分别是ERROR、INPUT、LOGIN、NONE和SUCCESS,它们的作用是统一execute方法的返回值。例如,当Action类处理用户请求成功后,有人喜欢返回WELCOME字符串,有人喜欢返回SUCCESS字符串,这样不利于项目的统一管理。Struts 2的Action类定义了上面的5个字符串,分别代表了统一的特定含义。 另外,Struts 2还提供了Action类的一个实现类——ActionSupport。它是一个默认的Action类,该类里已经提供了许多默认方法,这些默认方法包括获取国际化信息的方法、数据校验的方法、默认的处理用户请求的方法等。实际上,ActionSupport类是Struts 2默认的Action处理类,如果让开发者的Action类继承该Action类,则会大大简化Action的开发。 2) Action访问Servlet API Struts 2的Action并未直接与任何Servlet API耦合,这是Struts 2相对于Struts 1的一个改进之处,因为这样的Action类具有更好的重用性,并且能更轻松地测试Action。 然而,对于Web应用的控制器而言,不访问Servlet API几乎是不可能的,例如获得HTTP Request参数、跟踪HTTP Session状态等。为此,Struts 2提供了ActionContext类,Struts 2的Action可以通过该类来访问Servlet API,包括HttpServletRequest、HttpSession和ServletContext这3个类,它们分别代表JSP内置对象中的request、session和application。 表51是ActionContext类中包含的常用方法。表51ActionContext类中包含的常用方法 方法描述Object get(Object key)该方法类似于调用HttpServletRequest的getAttribute(stringname)方法Map getApplication返回一个Map对象,该对象模拟了该应用的ServletContext实例Static ActionContext getContext静态方法,获取系统的ActionContext实例Map getParameters获取所有的请求参数。类似于调用HttpServletRequest对象的getparameter Map方法Map getSession返回一个Map对象,该对象模拟了HttpSession实例Void setApplication(Map application)直接传入一个Map实例,将该Map实例里的键值(keyvalue)对转换成session的属性名和属性值Void setSession(Map session)直接传入一个Map实例,将该Map实例里的键值对转换成session的属性名和属性值虽然Struts 2提供了ActionContext来访问Servlet API,但这种访问毕竟不能直接获取Servlet API实例。为了在Action中直接访问Servlet API,还提供了一些接口,如表52所示。 另外,为了直接访问Servlet API,Struts 2提供了ServletActionContext类。借助于这个类的帮助,开发者也能够在Action中直接访问Servlet API,同时无须在Action类中实现上面的接口。这个类包含的静态方法如表53所示。表52直接访问Servlet API的接口 接口描述ServletContextAware实现该接口的Action可以直接访问应用的ServletContext实例ServletRequestAware实现该接口的Action可以直接访问用户请求的HttpServletRequest实例ServletResponseAware实现该接口的Action可以直接访问服务器响应的HttpServletResponse实例表53ServletActionContext类包含的静态方法 方法描述Static PageContext getPageContext()取得Web应用的PageContext对象Static HttpServletRequest getRequest()取得Web应用的HttpServletRequest对象Static HttpServletResponse getResponse()取得Web应用的HttpServletResponse对象Static ServletContext getServletContext()取得Web应用的ServletContext对象在eGov电子政务系统中,AuthAction就使用了Servlet API,其代码如下:package com.ascent.action; import java.util.ArrayList; import java.util.List; import java.util.Map; import com.ascent.po.Authorization; import com.ascent.po.Department; import com.ascent.po.News; import com.ascent.po.Userauth; import com.ascent.po.Usr; import com.opensymphony.xwork2.ActionContext; public class AuthAction extends BaseAction { public String typ; public String pars; public String id; public String name; public String gid; public String sid; public String gname; public String sname; public String dept; public String getGname() { return gname; } public void setGname(String gname) { this.gname=gname; } public String getSname() { return sname; } public void setSname(String sname) { this.sname=sname; } public String getGid() { return gid; } public void setGid(String gid) { this.gid=gid; } public String getSid() { return sid; } public void setSid(String sid) { this.sid=sid; } public String getId() { return id; } public void setId(String id) { this.id=id; } public String getName() { return name; } public void setName(String name) { this.name=name; } public String getPars() { return pars; } public void setPars(String pars) { this.pars=pars; } public String getTyp() { return typ; } public void setTyp(String typ) { this.typ=typ; } public String getDept() { return dept; } public void setDept(String dept) {