Struts2
深入浅出 STRUTS 2
当我们说起WebWork的时候,我们实际上说的是两个项目——XWork和WebWork。XWork是一个通用的命令框架,它提供了很多核心的功能,例如actions,验证和拦截器,它可以完全独立于执行上下文运行,并提供了一个内部的依赖注入机制,用来做配置和工厂实现的管理。
而WebWork则是一个完全独立的上下文。它用Web应用中运行所需的上下文把XWork包装起来,并提供了可以简化Web开发的特定实现。Struts2的目标很简单——使Web开发变得更加容易。为了达成这一目标,Struts2中提供了很多新特性,比如智能的默认设置、annotation的使用以及“惯例重于配置”原则的应用,而这一切都大大减少了XML配置。Struts2中的Action都是POJO,这一方面增强了Action本身的可测试性,另一方面也减小了框架内部的耦合度,而HTML表单中的输入项都被转换成了恰当的类型以供action使用。开发人员还可以通过拦截器(可以自定义拦截器或者使用Struts2提供的拦截器)来对请求进行预处理和后处理,这样一来,处理请求就变得更加模块化,从而进一步减小耦合度。模块化是一个通用的主题——可以通过插件机制来对框架进行扩展;开发人员可以使用自定义的实现来替换掉框架的关键类,从而获得框架本身所不具备的功能;可以用标签来渲染多种主题(包括自定义的主题);Action执行完毕以后,可以有多种结果类型——包括渲染JSP页面,Velocity和Freemarker模板,但并不仅限于这些。最后,依赖注入也成了Struts2王国中的一等公民,这项功能是通过Spring框架的插件和Plexus共同提供的,与PicoContainer的结合工作还正在进行中。
为什么要选择Struts2呢?
1.基于Action的框架
2.拥有由积极活跃的开发人员与用户组成的成熟社区
3.Annotation和XML配置选项
4.基于POJO并易于测试的Action
5.与Spring,SiteMesh 和Tiles的集成
6.与OGNL表达式语言的集成
7.基于主题的标签库与Ajax标签
8.多种视图选项 (JSP,Freemarker,Velocity和XSLT)
9.使用插件来扩展或修改框架特性。
基于Action的框架基于Action的框架把Servlet和JSP的概念合并到了一起。它的想法是把对当前用户所见的页面请求的处理动作,分拆成处理逻辑和表现逻辑,让它们各司其职。
在这个模式中,Servlet是控制器,集中处理所有的客户端页面请求。它把所请求的URL与被称为Action的工作单元映射到一起。Action的工作就是通过访问HTTP会话、HTTP请求和表单参数等调用业务逻辑,最后把响应映射到以POJO(plain old java object)形式存在的模型上,来完成特定的功能。最后,Action返回的结果会通过配置文件映射到JSP页面上,JSP会渲染视图并显示给用户。Struts2是一个基于Action的MVC Web框架。
基于组件的框架
像提交表单或者是点击链接这样的事件,都与代表组件的类的方法或者是特定的监听类,有着一对一的映射关系。基于组件的框架还有一个好处,那就是你可以在多个Web应用之间重用组件。JSF,Wicket 和 Tapestry等都是基于组件的框架。
伟大的均衡器——Ajax
当用户界面与Ajax结合以后,Web浏览器就可以只在必需的时候,才会向服务器发起请求,获得少量的信息。服务器返回的结果都是被格式化或者处理过的,页面会直接把结果显示出来,然后用户就可以在浏览器中看到变化。因为只有发生变化的那一块区域会被重新渲染,而不是整个页面进行刷新,所以对于用户来说,响应速度就变得更快了。
当一个Ajax用户界面调用基于Action的框架时,这个Action框架的反应机制就和基于组件的框架非常相似。实际上,这二者的结合为我们带来了耦合度更低、可重用性更高的系统。同样的操作,可以为Ajax组件提供JSON、XML或者HTML片段视图,又可以和其他操作组合,为非Ajax的用户界面提供HTML视图。
从全局的角度来看 ,Struts2是一个pull(拉)类型的MVC(或者MVC2)框架,它与传统类型的MVC框架的不同之处就在于在Struts2中,Action担任的是模型的角色,而非控制器的角色,虽然它的角色仍然有些重叠。“pull”的动作由视图发起,它直接从Action里拉取所需的数据,而不是另外还需要一个单独的模型对象存在。
配置
在配置Struts2之前,你首先要把发行版下载下来,或者在Maven2的“pom.xml”文件中声明如下的依赖:
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>2.0.6</version>
</dependency>应用程序的依赖库只需要在项目的“pom.xml”文件中通过groupId、artifactId和version进行定义即可。在使用一个制品之前,Maven会从本地和远程的库中进行查询,查询的范围包括本地的缓存、网上其他组织提供的库以及ibiblio.com这个标准的库。如果在远程的库中找到了所需的制品,Maven就会把它下载到本地的缓存中,以供项目使用。当请求所需要的制品时,这个制品相关联的依赖也会同样被下载到本地来(假设在该制品的“pom.xml”文件中对所有依赖都依次进行了声明)。
Struts2是用Maven2来构建的,在pom文件中,它定义了所有依赖的配置项。您可以访问http://maven.apache.org来获得关于Maven2的更多信息。Struts2的配置可以分成三个单独的文件
FilterDispatcher是一个servlet过滤器,它是整个Web应用的配置项,需要在“web.xml”中进行配置:
<filter>
<filter-name>action2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>action2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
如果是配置一个最基本的Web应用的话,这样就足够了。剩下的就是自定义一些Web应用的执行环境和配置应用中的组件。其中前者主要通过“struts.properties”来完成,而后者是在“struts.xml”中进行配置的。我们下面来看一下这两个配置文件的细节。struts.properties文件这个文件提供了一种更改框架默认行为方式的机制。在一般情况下,如果不是打算让调试更加方便的话,你根本无须更改这个文件。在“struts.properties”文件中定义的属性都可以在“web.xml”文件的“init-param”标签中进行配置,或者通过“struts.xml”文件中的“constant” 标签来修改.
在Struts2-Core的jar发行版中,有一个默认的属性文件,名为“default.properties”。如果要对属性进行修改的话,只需要在项目的源代码目录下,创建一个叫做“struts.properties”的文件,然后把想要修改的属性添加到文件中,新的属性就会把默认的覆盖掉了。
在开发环境中,以下几个属性是可能会被修改的:
1.struts.i18n.reload = true——激活重新载入国际化文件的功能
2.struts.devMode = true ——激活开发模式,以提供更全面的调试功能。
3.struts.configuration.xml.reload = true——激活重新载入XML配置文件的功能(这是为Action准备的),当文件被修改以后,就不需要重新载入Servlet容器中的整个Web应用了。
4.struts.url.http.port = 8080——配置服务器运行的端口号(所有生成的URL都会被正确创建)
struts.xml文件“struts.xml”文件中包含的是开发Action时所需要修改的配置信息
因为这是一个XML文件,所以最开始的元素就是XML版本和编码信息。接下来则是XML的文档类型定义(DTD)。DTD提供了XML文件中各个元素所应使用结构信息,而这些最终会被XML解析器或者编辑器使用。
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEstrutsPUBLIC
"-//ApacheSoftwareFoundation//DTDStrutsConfiguration2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package
name="struts2"
extends="struts-default"
namespace="/struts2">
…
</package>
</struts>
我们现在看到了<struts>标签,它位于Struts2配置的最外层,其他标签都是包含在它里面的。
Include标签:
<include…/>是<struts>标签的一个子标签,它可以把其他配置文件导入进来,从而实现Struts2的模块化。它的“file”属性定义了要导入的文件的名称——该文件要和“struts.xml”一样有着相同的结构。比如说,如果要把一个记账系统的配置分解的话,你可能会把记账、管理和报表的功能各自组织到不同的文件中:
<struts>
<includefile="billing-config.xml"/>
<includefile="admin-config.xml"/>
<includefile="reports-config.xml"/>
…
</struts>
当我们导入文件时,一定要注意导入的顺序。因为从文件被导入的那个点开始,该文件中的信息才能被访问到,也就是说,如果要使用另外一个文件中所定义的标签,那么该文件就必须要在被引用之前就配置好。
有些文件需要显式导入,有些则会被自动导入。“struts-default.xml”和“struts-plugin.xml”就属于后者。它们都包括有结果类型、拦截器、拦截器堆栈、包(package)以及Web应用执行环境(也可以在“struts.properties”中配置)的配置信息。二者的区别在于,“struts-default.xml”提供的是Struts2的核心配置信息,而“struts-plugin.xml”则描述了特定插件的配置信息。每个插件的JAR中都要有一个“struts-plugin.xml”文件,该文件会在系统启动时被装载。The Package Tag:
<package…/>标签是用来把那些需要共享的通用信息——例如拦截器堆栈或URL命名空间——的配置组织在一起的。它通常由Action的配置组成,但也可以包括任何类型的配置信息。它还可以用来组织管理各自独立的功能——它们也可以被进一步拆分到不同的配置文件中。
这个标签的属性包括有:--name ——开发人员为这个Package指定的唯一的名字。
--extends —— 当前这个Package所继承的Package的名字,被继承的Package中所有的配置信息(包括Action的配置)都可以在新的命名空间下,新的Package里面被使用。
--namespace ——命名空间提供了从URL到Package的映射。也就是说,如果两个不同的Package,其命名空间分别为“package1”和“package2”,那么URL差不多就是“/myWebApp/package1/my.action” 和“/myWebApp/package2/my.action”这样的形式。
--abstract ——如果这个属性的值为“true”,那么这个Package就只是一个配置信息的组合,也就无法通过Package的名字来访问其中配置的Action。
只有继承了正确的父Package,那么你才能用到所需的预先配置好的特性。在大多数情况下,我们都应该继承“struts-default.xml”
还有两个标签也可以和<struts>标签一起使用,它们是<bean … />和<constant … />。这两个标签为重新配置框架提供了更高级的方式.
Actions
在大多数的Web应用框架中,Action都是一个最基本的概念,也是可以与用户发出的HTTP请求相关联的最小的工作单元。
在Struts2中,Action可以以几种不同的方式来工作。
单个结果
Action最常用也是最基本的用法就是执行操作后返回单个结果。这种Action看上去就是这样的:class MyAction { public String execute() throws Exception { return "success"; } }
但首先,这个Action类不需要继承其它类,也不需要实现其他接口。这样的类就是一个简单的POJO。
其次,在这个类中有一个名为“execute”的方法。这个方法名是依照惯例命名的,如果你想用其他名字的话,那么只需要在Action的配置文件中做出更改。无论方法名是什么,它们都被认为会返回一个String类型的值。Action的配置文件会将该Action的返回代码与要呈现给用户的结果进行匹配。另外,该方法还可以在需要的时候抛出异常。
下面是最简单的配置信息:
<actionname="my"class="com.fdar.infoq.MyAction">
<result>view.jsp</result>
</action>
“name”属性提供了执行Action所对应的URL地址,在这里就是“my.action”。“.action”的扩展名是在“struts.properties” 4文件中配置的。“class”属性指定了要执行的action所对应的类的全限定名。多个结果现在情况稍微复杂了一些,Action需要根据逻辑运算的结果,来生成多个结果。下面的代码和刚才那个类看上去很像:
class MyAction { public String execute() throws Exception { if( myLogicWorked() ) { return "success"; } else { return "error"; } } }
因为这里有两个结果,所以就为每一种不同的情况来配置要呈现给用户的结果。配置文件就变成了如下的样子:
<action name="my" class="com.fdar.infoq.MyAction" >
<result>view.jsp</result>
<resultname="error">error.jsp</result>
</action>
我们可以看到在result节点中多了“name”属性,实际上这个属性是一直都存在的,如果开发人员没有显式指定它的值,那么它的默认值就是“success”1.Action方法返回一个字符串——这个返回的字符串与“struts.xml”的一个action配置相匹配。例子中已经演示这一种方式。
2.使用Codebehind插件——当使用这个插件的时候,它会将Action的名字和Action返回的结果字符串进行连接来得到视图模板。比如说,如果URL是“/adduser.action”,而Action返回了“success”,那么要渲染的页面就是“/adduser-success.jsp”。更多信息请参见http://struts.apache.org/2.x/docs/codebehind-plugin.html。
3.使用@Result注解——action类可以用@Results和@Result注解来标注多个不同的结果。Action所返回的字符串需要与所注解的结果之一相匹配。
4. 方法返回一个Result类的实例——Action不必一定要返回一个字符串,它可以返回一个Result类的实例,该实例应当是已经配置好可使用的。结果类型
Action生成并返回给用户的结果可能会有多个值,而且也可能是不同的类型。“success”的结果可能会渲染一个JSP页面,而“error”的结果可能需要向浏览器发送一个HTTP头。结果类型(本章中稍后会详细讨论)是通过result节点的“type”属性来定义的。和“name”属性一样,这个属性有一个默认值——“dispatcher”——用来渲染JSP。大多数情况下,你只需要使用默认的结果类型就可以了,但是你可以提供自定义的实现。
请求和表单类型
Action为了执行操作,并为数据库持久化对象提供数据,就必须要访问请求字符串和表单中的数据。Struts2采用了JavaBean的风格——要访问数据的话,就给字段提供一个getter和setter,要访问请求字符串和表单也是一样的道理。每一个请求字符串和表单的值都是一个简单的名/值对,所以要设定一个特定名称的值的话,就要为它提供一个setter。比如,如果一个JSP调用了“/home.action?framework=struts&version=2”这样一个请求,那么action就应该提供如下两个setter:“setFramework( String frameworkName )”和“setVersion( int version )”。
访问业务服务
Struts2使用了名为依赖注入——又名控制反转——的技术来降低系统的耦合性。依赖注入可以通过构造器注入,接口注入和setter注入来实现。Struts2中用的是setter注入。这就是说,你只需要提供一个setter,对应的对象就可以被Action使用了.Struts2推荐的依赖注入框架是Spring框架,并通过插件对它进行配置。你还可以使用Plexus,或者是提供自定义的实现。还有些对象是不受Spring框架管理的,例如HttpServletRequest。这些对象是通过setter注入和接口注入混合处理的。对于每一个非业务的对象而言,都有一个对应的接口(也就是“aware”接口),需要action对其进行实现。
当必需的接口和setter齐备以后,拦截器就会对对象的注入进行管理了。
从Action中访问数据
在有些情况下我们需要查看被Action修改过的对象。有好几种技术可以帮助我们做到这一点。
对于开发人员而言,最常用的方法就是把所需要访问的对象放到HttpServletRequest或者HttpSession里面。这可以通过实现“aware”接口(让依赖注入来工作),并设置为可以通过要求的名称来访问的方式来达到。
如果你打算使用内建的标签库或者是JSTL支持的话,访问数据就会更简单了。这两种方法都可以通过值栈来访问Action。开发人员唯一要另外做的就是在Action里面为所要访问的对象提供getter方法Interceptors(拦截器)
Struts2中提供的很多特性都是通过拦截器实现的,例如异常处理,文件上传,生命周期回调与验证。拦截器从概念上来讲和Servlet过滤器或者JDK 的Proxy类是一样的。它提供了一种对Action进行预处理和事后处理的功能。和Servlet 过滤器一样,拦截器可以被分层和排序。它还可以访问所执行的Action和所有的环境变量与执行属性。让我们从依赖注入来开始对拦截器的介绍。正如我们已经看到的一样,依赖注入可以多种不同的实现方式。下面就是对应于不同实现方式的拦截器:
--Spring 框架——ActionAutowiringInterceptor拦截器--请求字符串和表单值—— ParametersInterceptor拦截器
--基于Servlet的对象——ServletConfigInterceptor拦截器前两种拦截器可以独立工作,不需要Action的帮助,但是最后一种不同,它是在以下几种接口的辅助下工作的:
--SessionAware ——通过Map来提供对所有session属性的访问
--ServletRequestAware——提供对HttpServletRequest对象的访问
--RequestAware ——通过Map来提供对所有request属性的访问
--ApplicationAware ——通过Map来提供对所有applicatin属性的访问--ServletResponseAware ——提供对HttpServletResponse对象的访问
--ParameterAware ——通过Map来提供对所有request string和表单数据的访问
--PrincipalAware ——提供对PrincipleProxy对象的访问;该对象实现了HttpServletRequest对象的有关principle 和role的方法,但是它提供了一个Proxy,因此所有的实现都是独立于Action的。
--ServletContextAware ——提供对ServletContext对象的访问
如果要把数据注入到Action中去,那么对应的数据就需要实现必需的接口。
配置
如果要在Action中激活依赖注入功能(或其他任何由拦截器提供的功能),就必须要对Action进行配置。和其他元素一样,许多拦截器都已经提供了默认的配置项。你只需要确认一下Action所在的Package继承了“struts-default”package。
在配置一个新的拦截器之前,首先要对它进行定义。<interceptors…/>和<interceptor…/>标签都要直接放到<package>标签里面。像我们上面提到的那些拦截器,它们的配置项就是这样的:
<interceptors>
…
<interceptorname="autowiring"
class="…xwork2.spring.interceptor.ActionAutowiringInterceptor"/>
</interceptors>
我们同时还要确保Action中应用了所需的拦截器。这可以通过两种方式来实现。第一种是把拦截器独立的分配给每一个Action:
<actionname="my"class="com.fdar.infoq.MyAction">
<result>view.jsp</result>
<interceptor-refname="autowiring"/>
</action>
在这种情况下,Action所应用的拦截器是没有数量限制的。但是拦截器的配置顺序必须要和执行的顺序一样。
第二种方式是在当前的Package下面配置一个默认的拦截器:
<default-interceptor-refname="autowiring"/>
这个声明也是直接放在<package…/>标签里面,但是只能有一个拦截器被配置为默认值。
现在拦截器已经被配置好了,每一次Action所映射的URL接到请求时,这个拦截器就会被执行。但是这个功能还是很有限的,因为在大多数情况下,一个Action都要对应有多个拦截器。实际上,由于Struts2的很多功能都是基于拦截器完成的,所以一个Action对应有7、8个拦截器也并不稀奇。可以想象的到,如果要为每一个Action都逐一配置各个拦截器的话,那么我们很快就会变得焦头烂额。因此一般我们都用拦截器栈(interceptorstack)来管理拦截器。下面是struts-default.xml文件中的一个例子:
<interceptor-stack name="basicStack"><interceptor-ref name="exception"/>
<interceptor-refname="servlet-config"/>
<interceptor-refname="prepare"/>
<interceptor-refname="checkbox"/>
<interceptor-refname="params"/>
<interceptor-refname="conversionError"/>
</interceptor-stack>这个配置节点是放在<package … /> 节点中的。每一个 <interceptor-ref … />标签都引用了在此之前配置的拦截器或者是拦截器栈。
我们已经看到了如何在Action中应用拦截器,而拦截器栈的用法也是一模一样的,而且还是同一个标签:
<actionname="my"class="com.fdar.infoq.MyAction">
<result>view.jsp</result>
<interceptor-refname="basicStack"/>
</action>
默认拦截器的情况也是一样的——只需要把单个拦截器的名字换成拦截器栈的名字就可以了。
<default-interceptor-refname="basicStack"/>
由上面的种种情况可以得出,当配置初始的拦截器与拦截器栈时,必须要确保它们的名字是唯一的。实现拦截器
在应用程序中使用自定义的拦截器是一种优雅的提供跨应用特性的方式。我们只需要实现XWork框架中一个简单的接口,它只有三个方法:
publicinterfaceInterceptorextendsSerializable{
voiddestroy();
voidinit();
Stringintercept(ActionInvocationinvocation)throwsException;
}如果我们不需要执行其他初始化或清理动作的话,还可以直接继承AbstractInterceptor。这个类对“destroy”和“init”方法进行了重写,但在方法中没有执行任何操作。ActionInvocation 对象可以用来访问运行时环境,以及Action本身;上下文(包括了Web应用的请求参数,session参数,用户Local等等);Action的执行结果;还有那些调用Action的方法并判断Action是否已被调用。
值栈与 OGNL
OGNL的全称是Object Graph Navigational Language(对象图导航语言),提供了访问值栈中对象的统一方式。值栈中的对象构成及其排列顺序如下所示:
1. 临时对象——在执行过程中,临时对象被创建出来并放到了值栈中。举个例子来说,像JSP标签所遍历的对象容器中,当前访问到的值就是临时对象
2.模型对象——如果模型对象正在使用,那么会放在值栈中action的上面
3.Action对象——正在被执行的action
4.固定名称的对象(NamedObjects)——这些对象包括有#application,#session,#request,#attr和#parameters,以及相应的servlet作用域
访问值栈可以有很多方法,其中最常用的一种就是使用JSP,Velocity或者Freemarker提供的标签。还有就是使用HTML标签访问值栈中对象的属性;结合表达式使用控制标签(例如if,elseif和iterator);使用data标签(set和push)来控制值栈本身。访问值栈可以有很多方法,其中最常用的一种就是使用JSP,Velocity或者Freemarker提供的标签。还有就是使用HTML标签访问值栈中对象的属性;结合表达式使用控制标签(例如if,elseif和iterator);使用data标签(set和push)来控制值栈本身。
结果类型
“type”属性被用来配置Action的结果类型,如果没有配置该属性的话,那么默认类型就是“dispatcher”,它将会渲染一个JSP结果并返回给用户。下面就是这样的Action配置:
<actionname="my"class="com.fdar.infoq.MyAction">
<resulttype="dispatcher">view.jsp</result>
</action>结果类型的配置项在<package … />标签内部,和拦截器的配置看上去很相似。“name”属性提供了结果类型的统一标识,“class”属性描述了实现类的全限定名。它多出了第三个属性:“default”——通过这个属性可以修改默认的结果类型。如果一个Web应用是基于Velocity而不是JSP的话,那么把默认结果类型修改一下,就可以节省很多配置的时间。
<result-types>
<result-typename="dispatcher"default="true"
class="org.apache.struts2.dispatcher.ServletDispatcherResult"/>
<result-typename="redirect"
class="org.apache.struts2.dispatcher.ServletRedirectResult"/>
…
</result-types>
实现结果类型
和拦截器一样,你也可以创建自己的结果类型并在Web应用中加以配置。Struts2中已经有了很多常用的结果类型,所以在创建自己的结果类型之前,你应该检查一下你想要的类型是否已经存在。
创建一个新的结果类型需要实现Result接口。
publicinterfaceResultextendsSerializable{
publicvoidexecute(ActionInvocationinvocation)
throwsException;
}
ActionInvocation 对象可以用来访问运行时环境,新的结果类型还可以通过它来访问刚刚执行的Action以及Action执行的上下文。在上下文中包括有HttpServletRequest对象,可以用来访问当前请求的输入流。结果和视图技术
。我们在前面的小节中看到过,如果没有“type”属性或者该属性的值是“dispatcher”的话,那么最后就会渲染JSP页面。在Struts 2应用中,有其他三种技术可以用来替代JSP:
1.Velocity Templates
2.Freemarker Templates
3.XSLT Transformations