forwardpage=“hello.jsp”/>
对于请求转发,转发的资源组件和目标组件共享request范围内的共享数据。
7.1.1.2.请求重定向
请求重定向类似请求转发,但也有一些重要区别:
●Web组件可以将请求重定向到任一URL,而不仅仅是统一应用中的URL。
●重定向的资源组件和目标组件之间不共用一个HttpServletRequest对象,因此不能
●共享request范围内的共享数据。
下图显示了一个Serlet把请求重定向给另一个JSP组件的过程。
图7-3:
请求重定向
如果当前应用的Servlet组件要把请求转发给URL“http:
//jakarta.apache.org/struts”,可
以在Servlet的service()方法中执行以下代码:
response.sendRedirect(“http:
//jakarta.apache.org/struts”);从上图中可以看出,HttpServletResponse的sendRedirect()方法向浏览器返回包含重定向
信息,浏览器根据这一信息发送出一个新的HTTP请求,请求访问重定向目标组件。
7.1.1.3.包含
包含关系允许一个Web组件聚集来自同一个应用中其他Web组件的输出数据,并使用被聚集的数据来创建响应效果。
这种技术通常用于模板处理器,它可以控制网页的布局。
模板中每个页面区域的内容来自不同的URL,conger组成单个页面。
这种技术能够为应用程序提供一致的外观和感觉。
包含关系的源组件和目标组件共用同一个HttpServletRequest对象,因此他们共享request范围内的共享数据。
下图显示了一个Servlet包含另一个JSP组件的过程。
图7-4:
Web组件的包含关系
Servlet类使用javax.servlet.RequestDispather.include()方法包含其他Web组件。
例如,如果当前的Servlet组件包含了3个JSP文件:
header.jsp、main.jsp和footer.jsp,则可以在Servlet的service()方法中执行以下代码:
……
RequestDispatcherrd;
rd=req.getRequestDispatcher(“/header.jsp”);
rd.include(req,res);
rd=req.getRequestDispatcher(“/main.jsp”);
rd.include(req,res);
rd=req.getRequestDispatcher(“/footer.jsp”);
rd.include(req,res);
……
在JSP文件中,可以通过指令来包含其他Web资源,例如:
<%@includefile=“/header.jsp”%>
<%@includefile=“/main.jsp”%>
<%@includefile=“/footer.jsp”%>
7.1.2.MVC概述
模型-视图-控制器(MVC)是80年代Smalltalk-80出现的一种软件设计模式,现在已经被广泛的使用。
开发模式上采用Model-View-Controller(MVC)模式,MVC模式将程序代码整理切割为三部份,Model部分是业务与应用领域(Businessdomain)相关逻辑、管理状态之对象,Controller部分接收来自View所输入的资料并与Model部分互动,是业务流程控制(FlowControl)之处,View部分则负责展现资料、接收使用者输入资料。
●模型(Model)模型是应用程序的主体部分。
模型表示业务数据,或者业务逻辑.
●视图(View)视图是应用程序中用户界面相关的部分,是用户看到并与之交互的界面。
●控制器(controller)控制器工作就是根据用户的输入,控制用户界面数据显示和更新model对象状态。
图7-5:
MVC设计模式
7.1.2.1.MVC的优点
可以为一个模型在运行时同时建立和使用多个视图。
变化-传播机制可以确保所有相关的视图及时得到模型数据变化,从而使所有关联的视图和控制器做到行为同步。
视图与控制器的可接插性,允许更换视图和控制器对象,而且可以根据需求动态的打开或关闭、甚至在运行期间进行对象替换。
模型的可移植性。
因为模型是独立于视图的,所以可以把一个模型独立地移植到新的平台工作。
需要做的只是在新平台上对视图和控制器进行新的修改。
潜在的框架结构。
可以基于此模型建立应用程序框架,不仅仅是用在设计界面的设计中。
7.1.2.2.MVC的不足之处
增加了系统结构和实现的复杂性。
对于简单的界面,严格遵循MVC,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率。
视图与控制器间的过于紧密的连接。
视图与控制器是相互分离,但确实联系紧密的部件,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。
视图对模型数据的低效率访问。
依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。
对未变化数据的不必要的频繁访问,也将损害操作性能。
目前,一般高级的界面工具或构造器不支持MVC模式。
改造这些工具以适应MVC需要和建立分离的部件的代价是很高的,从而造成使用MVC的困难。
7.1.2.3.MVC的应用范围
使用MVC需要精心的计划,由于它内部原理比较复杂,所以需要花费一些时间去理解它。
将MVC运用到应用程序中会带来额外的工作量,增加应用的复杂性,所以MVC部适合小型应用程序。
但是对于开发存在大量用户界面上,并且业务逻辑复杂的大型应用程序,MVC将会使软件在健壮性、代码重用喝结构方面上一个新台阶。
尽管在最初构建MVC框架时会花费一定工作量,但从长远的角度来看,它会大大提供后期软件开发效率。
7.1.3.JSPModel1和JSPModel2
SUN在JSP出现早期制定了两种规范,称为Model1和Model2。
图7-6:
JSPModel1
Model1它是以页面为中心的。
适用于完成简单的应用程序。
实现这个模式的应用程序有一系列的Jsp页面,在这些页面里用户程序运行从一个页面到另一个页面。
因为它的简单容易。
Model1应用的主要问题是难以维护,并且毫无灵活性可言。
另外,由于开发人员已经同时被卷入到了页面开发和商业逻辑的编码中,这个架构模式在页面设计人员和web开发人员之间很难实现劳动分工。
图7-7:
JSPModel2
JSPModel1和JSPModel2的本质区别在于处理用户请求的位置不同。
在Model1体系中,JSP页面负责响应用户请求并将处理结果返回用户。
JSP既要负责具体业务流程控制,又要负责提供表示层数据,同时充当视图和控制器,未能实现这两个模块之间的独立和分离。
尽管Model1体系十分适合简单应用的需要,它却不适合开发复杂的大型应用程序。
不加选择的使用Model1,会导致JSP页内嵌入大量Java代码。
尽管这对Java程序员不是多大的问题,但如果JSP页面由网页设计人员开发并进行维护的(大量的实际项目中情况确实是这样的),这个问题就会变得十分突出。
从根本上讲,将导致角色定义不清和职责分配不明,会给项目管理带来很大的麻烦。
JSPModel2体系结构是一种联合使用JSP和Servlet来提供动态内容服务的方法。
它吸取了JSP和Servlet两种技术各自的突出优点,用JSP生成表示层的内容,让Servlet完成深层次的处理任务。
在这里,Servlet充当控制器的角色,负责处理用户请求,创建JSP页需要使用的JavaBean对象,根据用户请求选择使用的JSP页返回给用户。
在JSP页内没有处理逻辑,它仅负责检索原先由Servlet创建的JavaBean对象,从Servlet中提取动态内容插入静态模板。
这是一种有突破性的软件设计方法,它清晰地分离了表达和内容,明确角色定义以及开发者与网页设计者的分工。
事实上,项目越复杂,使用Model2的好处越大,越容易体现。
7.1.4.Struts概述
Struts实质上就是在JSPModel2的基础上实现一个MVC框架。
在Struts框架中,模型由实现业务逻辑的JavaBean或EJB组件构成,控制器由ActionServlet和Action来实现,视图由一组JSP文件构成。
图7-8:
Struts实现的MVC框架
7.1.4.1.Model
Struts没有定义具体的Model层的实现,Model层通常是和业务逻辑紧密相关的,还通常有持续化的要求,Struts目前没有考虑到这一层,但是,不管在开源世界还是商业领域,都有一些都别优秀的工具可以为Model层次的开发提供便利,例如优秀的O/RMapping开源框架Hibernate。
7.1.4.2.View
通常,Web应用的UI由以下文件组成:
●HTML
●JSP
而JSP中通常包含以下组件:
●自定义标签
●DTO(DataTransferObject数据传输对象)
在Struts中,还包含了以下两种常用的组件:
●StrutsActionForms
●资源绑定(javaresourcebundles),例如将标签的显示内容,错误提示的内容通
过配置文件来配置,这样可以为实现国际化提供基础。
由此可见,Struts对于传统的WebUI所作的扩充就是StrutsActionForms和资源绑定,接下来对其进行进一步描述。
7.1.4.3.Controller
J2EE的前端控制器(FrontController)设计模式中利用一个前端控制器来接受所有客户请求,为应用提供一个中心控制点,在该控制点上,可以很方便地添加一些全局性的,如加密、国际化、日志等通用操作。
Controller的实现机制正是建立在前端控制器的设计模式基础上。
前面我们介绍过,Struts的控制器拥有一些职责,其中最主要的是以下几个:
1.1.接收客户请求。
2.2.映射请求到指定的业务操作。
3.3.获取业务操作的结果并以有效的方式提供给客户。
4.4.根据业务操作的结果和当前的状态把不同的UI推向给客户。
在Struts框架中,控制器中不同的组件负责不同的控制职责,下图是Struts框架中关于控制器部分的一个组件图:
图7-9:
控制器组件
在上图中,很明显地可以看出,ActionServlet处于核心位置,那么,我们就先来了解一下ActionServlet。
org.apache.struts.action.ActionServlet在Struts应用程序中扮演接收器的角色,所有客户端的请求在被其它类处理之前都得通过ActionServlet的控制。
当ActionServlet的实例接收到一个HTTP请求,不管是通过get方法或post方法,ActionServlet的process()方法被调用并用以处理客户请求。
process()方法实现显示如下:
protectedvoidprocess(HttpServletRequestrequest,HttpServletResponseresponse)
throwsIOException,ServletException{
RequestUtils.selectApplication(request,getServletContext());
getApplicationConfig(request).getProcessor().process(request,response);}
该方法的实现很简单,RequestUtils.selectApplication(request,getServletContext());语句是用来根据用户访问的上下文路径来选择处理的应用,如果你只有一个Struts配置文件,就表示你只有一个Struts应用。
getApplicationConfig(request).getProcessor().process(request,response);语句用来获取一个处理器,并将客户请求提交给处理器处理。
7.1.4.4.Struts初始化处理流程
根据在web.xml中配置的初始化参数,Servlet容器将决定在在容器的第一次启动,或第一次客户请求ActionServlet的时机加载ActionServlet,不管哪种方式加载,和其它Servlet一样,ActionServlet的init()方法将被调用,开始初始化过程。
让我们来看看在初始化过程中将发生些什么,理解了这些,对于我们debug和扩展自己的应用更加得心应手。
5.1.初始化框架的内部消息绑定,这些消息用来输出提示,警告,和错误信息到日志文件中。
org.apache.struts.action.ActionResources用来获取内部消息;
6.2.加载web.xml中定义的不同参数,用以控制ActionServlet的不同行为,这些参数包括config,debug,detail,andconvertNull;
7.3.加载并初始化web.xml中定义的servlet名称和servlet映射信息。
通过初始化,框架的各种DTD被注册,DTD用来在下一步校验配置文件的有效性;
8.4.为默认应用加载并初始化Struts配置文件,配置文件即初始化参数config指定的文件。
默认配置文件被解析,产生一个ApplicationConfig对象存于ServletContext中。
可以通过关键字org.apache.struts.action.APPLICATION从ServletContext中获取ApplicationConfig;
9.5.Struts配置文件中指定的每一个消息资源都被加载,初始化,并存在ServletContext的合适区域(基于每个message-resources元素的key属性),如果key属性没有设置,则为org.apache.struts.action.MESSAGE;
10.6.Struts配置文件中声明的每一个数据源被加载并且初始化,如果没有配置数据源,这一步跳过;
11.7.加载并初始化Struts配置文件中指定的插件。
每一个插件的init()方法被调用;
当默认应用加载完成,init()方法判断是否有应用模块需要加载,如果有,重复步骤4—7步成应用模块的加载。
7.1.4.5.Struts工作流程
图7-10:
Struts工作流程
上图是Struts的工作流程,前边我们提到,所有的请求都提交给ActionServlet来处理。
ActionServlet是一个FrontController,它是一个标准的Servlet,它将request转发给RequestProcessor来处理。
ActionMapping是ActionConfig的子类,实质上是对struts-config.xml的一个映射,从中可以取得所有的配置信息。
RequestProcessor根据提交过来的url,如*.do,从ActionMapping中得到相应的ActionForn和Action。
然后将request的参数对应到ActionForm中,进行form验证。
如果验证通过则调用Action的execute()方法来执行Action,最终返回ActionFoward。
ActionFoward是对mapping中一个foward的包装,对应于一个url。
ActionForm使用了ViewHelper模式,是对HTML中form的一个封装。
其中包含有validate方法,用于验证form数据的有效性。
ActionForm是一个符合JavaBean规范的类,所有的属性都应满足get和set对应。
对于一些复杂的系统,还可以采用DynaActionForm(Struts1.1)来构造动态的Form,即通过预制参数来生成Form。
这样可以更灵活的扩展程序。
ActionErrors是对错误信息的包装,一旦在执行action或者form.validate中出现异常,即可产生一个ActionError并最终加入到ActionErrors。
在Form验证的过程中,如果有Error发生,则会将页面重新导向至输入页,并提示错误。
Action是用于执行业务逻辑的RequsestHandler。
每个Action都只建立一个instance。
Action不是线程安全的,所以不应该在Action中访问特定资源。
一般来说,应改使用BusinessDelegate模式来对Businesstier进行访问以解除耦合。
Struts提供了多种Action供选择使用。
普通的Action只能通过调用execute执行一项任务,而DispatchAction可以根据配置参数执行,而不是仅进入execute()函数,这样可以执行多种任务。
如insert,update等。
LookupDispatchAction可以根据提交表单按钮的名称来执行函数。
当ActionServlet接收到一个客户请求时,将执行如下流程:
图7-11:
Struts响应用户请求的工作流程
1)检索和用户请求匹配的ActionMapping实例,如果不存在,就放回用户请求路径无效的信息。
2)如果ActionForm实例不存在,就创建一个ActionForm对象,把客户提交的表单数据保存到ActionForm对象中。
3)根据配置信息决定是否需要表单验证。
如果需要验证,就调用ActionForm的
validate()方法。
4)如果ActionForm的validate()方法返回null或者一个不包含ActionMessage的
ActionErrors对象,就表示表单验证成功。
验证不成功则发送给用户提交表单的
JSP组件,或者转向其他组件。
这样Action对象不会被创建。
5)ActionServlet根据ActionMapping实例包含的映射信息决定将请求转发给哪个
Action。
如果相应的Action实例不存在,就先创建这个实例,然后调用Action的execute()方法。
6)Action的execute()方法返回一个ActionForward对象,ActionServlet再把客户
请求转发给ActionForward对象只想JSP组件。
7)ActionForward对象指向JSP组件生成动态网页,返回给客户。
7.2.从helloapp开始Struts应用
7.2.1.helloapp的需求
helloapp的需要非常简单,主要是体验Struts开发过程,其内容如下:
●接收用户输入姓名,然后返回字符串“Hello!
”。
●如果用户没有输入姓名就提交表单,将返回错误信息,提示用户首先输入姓名。
●如果用户输入姓名为“Monster”,将返回出错信息,拒绝向“Monster”sayhello。
●为了演示模型组件的功能,本应用使用模型组件来保护用户输入的姓名。
7.2.2.组建Struts框架
Helloapp应用的各个模块组成:
JavaBean组件:
PersonBean,有一个userName属性,代表用户输入的名字。
提供了get/set方法。
这里可以预留save()方法,将数据保存到数据库中(JavaBean组件可以作为EJB或者Web服务前端组件)。
视图:
hello.jsp,提供用户界面,接收用户输入的姓名。
视图还包括一个ActionFormBean,它用来存放表单数据,并进行表单验证。
控制器:
Action类HelloAction,主要任务有,一、进行业务逻辑验证,如果用户输入姓名为“Monster”,将返回错误消息;二、调用模型组件PersonBean的save()方法,保存用户输入的名字;三、决定将合适的视图组件返回给用户。
除了以上的模块,我们还要创建Struts