Result Types
从action生成结果,并返回组用户不同的结果值,不都需要相同的类型.结果"success"可以渲染为一JSP页面,但结果"error"可能需要发送一个HTTP头返回给浏览器.
结果的类型使用"type"属性在结果节点配置.与"name"属性相似,这个属性也有一个默认值-"dispatcher"-将渲染JSPs.大多数的时间,你将使用所提供的结果类型,但有时也可能提供自定义的实现.
Request and Form Data
为了要做出Action应该如何工作的决定,并且提供数据的数据库持久对象,Action可能需要从请求字符串中访问值,并生成数据.
Struts2沿序JavaBean的方式-如果你想访问数据,你需要为字段提供一个getter和setter方法.访问请求字符串和form里的值是一样的.每个请求字符串或是Form里的值都是一个简单的名值对,所以为一个特定的名称赋值,需在action里创建一个setter方法.例如,如果JSP发起了一个请求,"/home.action?framework=struts&version=2",Action需要提供setter方法"setFramework(String frameworkname)",和setter方法"setVersion(int version)".
注意在这个例子中,setter方法不是一定需要一个String的值.默认的,Struts2将从一个String转换成action所需的类型.可以转换所有的原始类型和基础对象类型,而且可以配置你自己的自定义类.Struts2同样可以操作复杂对象中的值,例如,一个名称在Form元素中称为"person.address.home.postcode",值为"2",Struts2将使用同样的调用方式"getPerson().getAddress().getHome().setPostcode(2)".
Accessing Business Services
到现在为止,我们都在讨论如果配置action,如果控制渲染不同的结果返回给用户.对于action是做什么的,是非常重要的一部分,但是,返回它们的结果之前,一些处理需要执行.对于这个,它们需要访问一些不同类型的对象-商业对象,数据访问对象,或是其他资源.
为了提供一个松耦合的系统,Struts2使用称为依赖注入或是控制反转(IOC)的技术.依赖注入可以通过构造函数注入,接口注入和set方法注入来实现.Struts2使用set方法注入.意思是对于action可用的对象,你仅需要提供一个setter.首选的依赖注入框架是Spring框架,可以通过插件配置进来.另一个选择是Plexus,或都如果你喜欢,你可以替换成你自己的实现.
也有一些对象不能用Spring框架管理,比如像HttpServletRequest.这些对象是组合使用setter注入和接口注入来操作.对于每一个非商业对象,都有个相应的口(大家都知道的"aware"接口),Action必须实现这个接口.
注:
WebWork最初有自己的依赖注入框架.它在2.2版时,删除了这个特征,而改用Spring框架替换.最初的组件框架是基于接口的,所以为每个组件接口和接口的实现类,都需要提供.
另外,每个组件都有一个"Aware"接口,为组件提供一个setter.如果接口是"UserDAO",那么Aware接口则为"UserDAOAware"(约定俗成),并且拥有一个方法-一个setter,"void setUserDAO(UserDAO dao);".
拦截器会为必要的接口和setters注入必要的对象注入.
Accessing Data from the Action
有些时候,需要查看被action修改过的对象.有几种技术可以被使用.
很多WEB程序员熟悉的技术是将需要访问的对象放到HttpServletRequest和HttpSession中.这可以通过实现"aware"接口来完成,然后设置对像,使用特定的名称访问.
如果你想使用内建的标签或是引入JSTL支持,访问数据就非常容易了.他们两个都可以通过值栈直接存取action.唯一的工作就是程序员需要给Action里允许被访问的需要存取的对象提供getter方法.
我们将在后面的部分更详细的讨论值栈.
Interceptors
Struts2框架使用拦截器提供了许多特征;例如包含异常处理,文件上传,生命周期回调和验证.拦截器是同Servlet过滤器或是JDKs的代理类相同的概念.它们提供了Action的前处理和后处理的方法.与Servlet过滤器相似,拦截器也可以分层和排序.They have access to the action being executed, as well as all environmental variables and execution properties.
让我们开始讨论拦截器依赖注入.依赖注入到action当中,与我们之前看到的一样,有两种不同的形式出现.有一些拦截器是我们已经提过的:
Spring框架-ActionAutowiringInterceptor拦截器
请求字符串和Form值-ParametersInterceptor拦截器
Servlet基础对象-ServletConfigInterceptor
前两个拦截器独立工作,不需要Action再做什么,但最后一个却是不同的.它同下面的接口协作工作:
SessionAware-通过一个Map来提供对所有Session属性的访问
ServletRequestAware-提供对HttpServletRequest对象的访问
RequestAware-通过一个Map来提供对所有Request属性的访问
ApplicationAware-通过一个Map来提供对所有Application属性的访问
ServletResponseAware-提供对ServletResponse对象的访问
ParameterAware-通过一个Map来提供对所有请求字符串和Form值的访问
PrincipalAware-提供对PrincipleProxy对象的访问;这个对象实现了HttpServletRequest对象的规则和角色的方法, 但提供个代理,允许独立于Action的实现
ServletContextAware-提供对ServletContext对象的访问
为了将正确的数据注入到Action当中,它需要实现必需的接口.
Configuration
如果我们想要能够注入(或任何其他由拦截器提供的功能)到我们的Action,我们需要提供配置.像其他元素一样,大多数的拦截器已经为你配置好了.只需要你的Actions的包继承自"struts-default"包即可.
要配置一个新的拦截器,我们首先需要定义这个拦截器.将<interceptors … />和<interceptor …/>标签直属<package>标签.对于上面提到的Spring框架拦截器,它的配置如下:
1<interceptors>
2 …
3 <interceptor name="autowiring"
4 class="interceptor.ActionAutowiringInterceptor"/>
5</interceptors>
我们同样需要确认将拦截器应用到需要它的Action上.这可以通过两种方法实现.第一种是将拦截器分配给每个Action:
1<action name="my" class="com.fdar.infoq.MyAction" >
2 <result>view.jsp</result>
3 <interceptor-ref name="autowiring"/>
4</action>
使用这种配置,可以被应用到Action上的拦截器没有数量的限制.现在所需要的就是排列拦截器的顺序,它们都将被执行.
第二种方式是为当前的包分配默认的拦截器:
<default-interceptor-ref name="autowiring"/>
这个定义是直属<package ... />标签 的,并且只能有一个拦截器被定义成默认的.
现在已将拦截器配置到了Action映射上了,它将在每个映射的URL请求时被执行.但是这样有一些限制,更多的时候,我们需要将多个拦截器指派给一个Action.事实上,Struts2里基础的功能拦截器有很多,给每个Action指派7,8个拦截器也不是不可能的事.你可以假设一下,为每个Action配置各个拦截器,很快就会变得极难管理.因为这个原因,可使用拦截器栈来管理拦截器.这里有个例子,来自struts-default.xml文件:
1<interceptor-stack name="basicStack">
2 <interceptor-ref name="exception"/>
3 <interceptor-ref name="servlet-config"/>
4 <interceptor-ref name="prepare"/>
5 <interceptor-ref name="checkbox"/>
6 <interceptor-ref name="params"/>
7 <interceptor-ref name="conversionError"/>
8</interceptor-stack>
这个配置节点在<package ... />节点之下.每个<interceptor-ref … />标签引用一个拦截器或是一个在当前拦截器栈之前定义的拦截器栈.
我们已经看过如何将拦截器应用于Action之上,应用拦截器栈没有什么不同.事实上,我们正是使用相同的标签:
1<action name="my" class="com.fdar.infoq.MyAction" >
2 <result>view.jsp</result>
3 <interceptor-ref name="basicStack"/>
4</action>
这种做法对于默认的拦截器同样有效-简单的使用一个拦截器栈要优于使用单个的拦截器.
<default-interceptor-ref name="basicStack"/>
所以在配置实始拦截器和拦截器栈的时候,保证配置所有的拦截器和拦截器栈的名称唯一是非常重要的.
Implementing Interceptors
在你的应用中使用自定义的拦截器为应用提供cross-cutting特征是一个非常好的方式.只需要实现来自XWork框架的一个简单的接口.它只有3个方法:
1public interface Interceptor extends Serializable {
2 void destroy();
3 void init();
4 String intercept(ActionInvocation invocation) throws Exception;
5}
事实上,如果实始化和清除方法不是必需的,可以替代成扩展AbstractInterceptor类.这个类为"destroy"和"init"方法提供了默认的空实现.
ActionInvocation对象提供了对运行环境的访问.它允许访问Action本身,context(对于一个WEB应用,包含请求参数,session参数等),action执行的结果和调用action的方法,并确定action是否已经被调用.
我们已经知道如何来配置拦截器了,配置自定义拦截的方式完全相同.如果你创建了你自己的拦截器,你将同样会考虑创建自定义的拦截器栈.这样你将确认你的应用保持一致,新的拦截器可以处理所有需要他的actions.
Value Stack / OGNL
这个部分包括相关紧密地的二个概念.值栈正确的说应该是栈对象.OGNL则是Object Graph Navigational Language的缩写,提供在值栈里标准的访问对象的方式.
值栈由下面罗列的对像组成:
1.Temporary Objects-在执行期间创建临时对象,并将其放入值栈;一个例子是将当前集合中迭代的值循环的放到JSP标签中
2.The Model Object-如果模型对象正在使用,那么当前模型在action之前放入值栈
3.The Action Object-正在被执行的Action
4.Named Objects-这些对象包括#application,#session,#request,#attr和#parameters还有相应的Servlet范围
访问值栈可以通过许多不同的方式完成.大多数常用的方式是通过JSP提供的标签,Velocity和Freemarker.HTML标签一般被用于访问值栈中对象的属性;控制标签(像if, elseif和iterator)用于表达式;数据标签可以操作栈本身(通过set和push).
在使用值栈的时候,不需要知道目标对象存在于哪个范围之内.如果你想取属性"name"的值,你可以向值栈查询这个属性.每个栈元素,按照提供的顺序,被询问它是否存在property.如果存在,则返回这个值.如果不存在,则下一个元素会被查询.这将继续一直到栈里的元素全被查询.这是一个非常好的特征,你不需要知道这个值是哪里的-action,model或是HTTP请求-你只需要知道如果这个值存在,它就将被返回.
这是downside.如果property是常用的(例如"id"),而且你想从特定的对象(比方说action)取值,也就是说不是值栈中第一个遇到这个property的对象,返回的值可能与你的预期值不同.想要返回一个"id"的值,但它可能来自于JSP标签,临时对象,或是来自模型对象.OGNL正好有一种访问对象的属性的方法,我们可以在这里使用对我们有利的.如果我们知道栈在action中的深度,我们可以使用"[2].id"来替换"id".
事实上,OGNL是一种非常有特色的表达式语言.使用圆点符号对对象进行导航(例如,使用表达式"person.address"来替代"getPerson().getAddress()"),OGNL支持的特征如类型转换,方法调用,集合的处理和创建,projection across ollections,表达式赋值和lambda表达式.完整的语言指南可以查看http://www.ognl.org/2.6.9/Documentation/html/LanguageGuide/index.html.
Result Types
到目前为止,我们只展示了配置Action的结果通过JSP渲染呈递给用户.这是一个方案,但却不是唯一的.事实上,Struts2支持多种类型的结果.它们可以是可视的,或是可以与环境交互.
为action执行的结果配置一个指定的类型,将使用"type"属性.如果没有应用这个属性,默认的"dispatcher"类型将被使用-这将呈递一个JSP的结果.如何配置action看起来像下面这样:
1<action name="my" class="com.fdar.infoq.MyAction" >
2 <result type="dispatcher">view.jsp</result>
3</action>
Configuration
结果类型配置在<package ... />标签内.这个配置与拦截器的配置相似.一个"name"属性提供了一个结果类型的唯一标识,并且"class"标签提供实现类.这还有第三个属性"default"-这允许修改默认的结果类型.如果一个WEB应用基于Velocity而不是JSP,修改默认的将比输入配置信息省时.
1<result-types>
2 <result-type name="dispatcher" default="true"
3 class="….dispatcher.ServletDispatcherResult"/>
4 <result-type name="redirect"
5 class="….dispatcher.ServletRedirectResult"/>
6 …
7</result-types>
Implementing Result Types
与拦截器相似,它可能创建出你自己的结果类型并将它们配置到你的WEB应用中.一些常用的结果类型已经存在,所以,在你创建自己的之前,你应该先看看你想创建的类型是否已经存在.
为了创建一个新的结果类型,实现Result接口即可.
1public interface Result extends Serializable {
2 public void execute(ActionInvocation invocation) throws Exception;
3}
ActionInvocation对象提供了对运行环境的访问,允许一个新的结果类型访问来自刚刚运行的action的信息,也可以访问执行action的上下文.上下文包括了HttpServletRequest对象,可提供对当前请求的输出流的访问.
上一章:Starting Struts2--Core Components(2)
下一章:Starting Struts2--Core Components(4)
PS:唉,进度很慢,第三章还没有结束,不过还好,还差一小部分就能结束了