struts 2.0架构目标

对于一个特定的代码库而言,要谈它的架构目标是很困难的。通常在开发之前,其最终目标就写进了文档中;但这是理想化的状态,当开始开发之后,代码往往就会向着另外的方向发展。然后就有了代码库的典型特征;它们很难被发现,在不同的package或者特性之间有可能表现出不一致,并且更像是脱离计划之外的演变产物。
在本章中,我们将要讲述Struts 2代码库中五种这样的特征。从2002年的代码库演变起始——从最开始的WebWork,到WebWork分离成为WebWork2和XWork,再到最后的Struts 2,这些架构思想一直延续到了今天。
概念分离
以下这些不同层次的功能都是Web开发人员应当了解的:
    ♦ 在请求/响应周期中,所完成的核心任务是执行特定于每个Action的逻辑功能。
    ♦ Action要完成逻辑功能或者访问资源的话,必须要访问或者持有业务对象。
    ♦ 在把HTML中基于字符串的值转换成原始数据类型或其他对象类型,以及把视图对象转换成业务对象或者数据表表示的过程中,这期间需要完成多种转化,映射和变换。
    ♦ 有一些横切(cross-cutting)的目的是为成组的action,或者应用中的所有action提供功能的。

在Struts 2的架构中,以上的每一种概念都是被单独分离的。功能和逻辑不再是Action独享的。让我们看一下上面提到的概念是如何被分离的:
    ♦ 每个Action的逻辑(Per-Action Logic) ——这是最简单的概念;每一个Action都要为它所提供的逻辑或功能负责。
    ♦ 访问/持有业务对象(Accessing/Obtaining Business Objects )—— Struts 2 使用了依赖注入机制来把Action所需的对象注入到Action中去。
    ♦ 转化/映射/变换(Translation/Mapping/Conversions )——这三个概念两两之间都有着细微的区别,但它们都为Action核心逻辑提供了辅助功能。类型的转化和变换是由框架本身完成的。在Action开始进行处理之前,HTML中的字符串值就被转化成了基本类型,然后注入进Action里面——所需的东西就齐备了。映射是通过特定的拦截器完成的。我们需要通过某种方式对Action进行配置,让它拥有一个领域模型,并正确指定HTML中相应的字段,这样框架就会把UI与领域模型进行映射。它甚至还可以贯穿一个对象图。
    ♦ 横切的概念(Cross-cutting Concerns )——横切功能主要是由拦截器提供的。开发人员可以实现拦截器,然后让它们横切所有的Action,横切特定Package中的Action,或者还可以选择所要横切的Action。 另外一个横切的概念是UI布局。在这里Struts 2提供了被称作“主题”的支持标签。不同的主题可以提供不同的布局选项,并可以应用到每一个独立的标签中,或是整个应用程序(把该主题设置为默认值)。

松耦合
WebWork早期的目标之一就是提供一个松耦合的框架。WebWork2.0版本更强化了这一点,把代码拆分成了两个项目:XWork——一个通用的命令框架;WebWork——XWork的针对Web的接口。WebWork在架构上做出的根本性变化创造了一种共生的关系。我们现在所知的“WebWork”实际上是WebWork和XWork的合并。 

XWork作为一个独立的项目,可以作为其他项目的一部分加以利用——事实也是如此。Swingwork7就是这样一个项目。它是基于Swing的MVC框架,在底层使用了XWork。另外一个例子是一个JMS前端,它在Web UI中执行或共享XWork的action。这些都是高度松耦合的精彩示例。Struts 2也是XWork的一个受益者。
松耦合的思想在Struts 2中得到了更好的传播,它贯穿了整个框架的始终——从最开始Action的处理过程到最后一步结束。实际上,在Struts 2中几乎没有什么是不可以被配置的——我相信这是Struts 2中最强有力的地方之一,但也是最虚弱的地方之一。
松耦合的配置有一些常见示例:
    ♦ 把URL映射到Action
    ♦ 把不同的Action结果映射到被渲染的页面
    ♦ 把处理过程中发生的异常映射到被渲染的异常页面

稍微少见一些的Struts 2特有的示例有:
    ♦ 在不想用Spring的情况下配置业务对象工厂
    ♦ 改变URL与Action类的映射方式
    ♦ 添加新的Action结果类型
    ♦ 为新的框架功能添加插件
    ♦ 通过拦截器配置框架层的功能

松耦合系统的好处是广为人知的——增加易测试性,更容易扩展框架功能,等等。但是这里还有一点坏处。强大的配置管理功能,尤其是拦截器在其中至关重要的作用,使得开发人员已经很难理解某个Action的执行过程。这一点在调试时尤为突出。一个不了解内部流程的开发者很难提高调试的速度或效率,因为他根本就无法理解这中间发生了什么。这种问题可能简单如拦截器的配置错误,甚或是拦截器的配置顺序不对。如果我们能够充分理解处理过程中的每一个细节,那么很快就能找到解决方案了。
易测试性 
在过去的几年里,单元测试已经成为了软件开发领域内的事实标准。测试不仅能够保证逻辑的正确性,而且在开发所要测试的类的时候(如果在此之前则更好),通过编写单元测试还可以得到更简洁强壮的设计。
Struts2的前身——WebWork,就是在这种环境下开发的。在与框架元素保持松耦合的情况下,测试变得更加简单。Action、拦截器、结果、对象工厂以及在Web应用开发过程中开发出的其他组件,都可以不依赖于框架进行测试。
因为Action和拦截器是最常用的,所以下面我们来看一下怎么样对它们进行测试。
Actions
Actions在框架中一般都是通过“execute()”方法被调用的,在更改配置以后,任何返回String类型值的方法都可以用来调用Action。站在易测试性的角度来看,这已经无法更简单了。
我们先来看一个例子。下面这个action被用来增加一个数值:
public class MyAction {
private int number;
public int getnumber() { return number; }
public void setNumber( int n ) { number = n; }
public String execute() {
number += 10;
return “success”;
}
}
因为Action都是POJO,所以单元测试只需要实例化一个Action,调用其中的方法,然后确保返回的结果与所期待的一致。Action中的数据资源都是通过setter方法来提供的。所以Action中所需的数据都可以被直接赋值。
在我们的例子中需要两条断言——一个用来判断“execute”方法的输出,一个用来验证我们所期望的Action的状态。下面是单元测试代码:
public class myActionTest extends TestCase { 


public void testExecute() {
MyAction action = new MyAction();
Action.setNumber(5);
assertEquals("success", action.execute());
assertEquals(15,action.getNumber());
}
}
对于资源来说情况就复杂一些了。我们可以用类似于JMock8的第三方库来提供资源的mock实现,然后测试Action与资源之间的交互是否正确。
虽然例子是用JUnit来写的,但是我们同样可以使用TestNG或者其他的测试框架。
拦截器
在构建拦截器时,测试会稍稍复杂一些。但是在代码框架中也为其提供了额外的帮助。下面是两个使用拦截器的场景。
第一个是拦截器在使用时与ActionInvocation对象交互。在执行完以后,你可以通过断言拦截器本身的状态来对逻辑处理进行验证。在这个场景中,拦截器的测试方式和action一模一样。实例化拦截器;创建一个ActionInvocation的mock实现,用它来测试拦截器;调用intercept方法;然后断言所期望发生的变化。所断言的可能是拦截器本身,也可能是方法调用的结果,抑或是可能会抛出的异常。
第二个场景是拦截器与运行环境或者是与拦截器栈中的其他拦截器交互。这种情况下,测试需要通过ActionProxy类与Action交互,同时断言也需要访问拦截器本身没有权限访问的其他环境对象。
XWork库为JUnit测试提供了XWorkTestCase类,为TestNG测试提供了TestNGStrutsTestCase和TestNGXWorkTestCase,用以帮助测试拦截器。它们都为ConfigurationManager,Configuration,Container和ActionProxyFactory类的实例提供了测试实现。XWork
同时还提供了其他一些类,比如XWorkTestCaseHelper和MockConfiguration。
现在我们已经有了建立测试环境的底层架构,测试本身就变得容易了——只需要按照第一个场景中的步骤执行就可以了。唯一的区别就是在调用拦截器的intercept()方法时,同时还需要调用ActionProxy的execute方法,例如下面的代码:
ActionProxy proxy =
actionProxyFactory.createActionProxy(NAMESPACE,NAME,null);
assertEquals("success", proxy.execute());
在这个场景中,测试代码可以断言的期望值包括:Action的结果,Action的值或是值栈中的值。下面的方法可以在Action的执行前或者执行后获取这个Action对象:
MyAction action = (MyAction)proxy.getInvocation().getAction();
下面的方法可以用来得到值栈:
proxy.getInvocation().getStack()
模块化
当应用的规模变大以后,把程序分拆成各个模块的重要性就不言而喻了。这样一来,我们就可以把一个项目中开发的功能或者新的框架特性打包,并且在其他项目中重用。Struts 2已经把模块化作为了体系架构中的基本思想,开发人员可以独立工作,然后在其他人的工作基础上进行构建。
下面是将应用程序模块化的几种方法:
    ♦ 将配置信息拆分成多个文件——它本身不会对应用程序的分包造成影响,但是配置信息按照功能的界限进行拆分以后,管理起来就容易了很多,也减小了开发的难度
    ♦ 把自包含的应用模块创建为插件——所有用来提供某一种特性的功能组成——包括Action、拦截器、拦截器栈、视图模板(除了JSP以外)等等——都可以被打包在一起,并

9 config browser插件的文档地址为[url]http://struts.apache.org/2.x/docs/config-browser-plugin.html[/url]


    作为独立的插件发布。例如配置浏览器插件9,该插件提供了一个完整的模块,该模块可以为所在的应用程序添加一个用来查看配置信息的Web接口。
    ♦ 创建新的框架特性——与特定应用无关的新功能可以组织成插件,应用到多个应用中。

从技术角度来讲,这几种模块化的方式其实是一样的——它们都有同样的配置元素(除了名字可能不同以外,在使用插件时,“struts-plugin.xml”文件会作为配置文件被自动加载),同样的目录结构,并且可以包括同样的框架和应用元素。
上面的两种插件类型的唯一区别在于你从怎么概念上去理解它们,在发行包中放入的是哪些元素和配置文件。
额外的配置元素
因为插件可以为内部的框架功能提供替代的实现方式,所以需要做一些额外的配置。这些配置元素可以在“struts.xml”配置文件中使用,也用在了“struts-default.xml”文件中,但是它们更常用的情况是用来配置插件。
在插件中,替代的实现方式是分两步来配置的:
    1. 使用 <bean … />标签来定义替换的接口实现,并用一个唯一的key来标识
    2. 使用<constant … />标签来在多种可能的接口实现中进行选择

下面我们来看看每一步的具体细节。
<bean … />标签可以用来为扩展点提供实现信息。下面的例子是“struts-default.xml”配置文件中一个对象工厂的配置:
<bean name="struts"
type="com.opensymphony.xwork2.ObjectFactory"
class="org.apache.struts2.impl.StrutsObjectFactory" />
在配置项的属性中包含了用来在Struts 2里创建和使用替代对象实现(alternate object implementation)的所有信息。这些属性包括:
    ♦ class ——类的全名

    ♦ type ——类所实现的接口
    ♦ name ——每个type的唯一简称
    ♦ static ——是否把静态对象方法注入到类实例中
    ♦ scope ——实例的作用域,可以是“default”,“request”,“session”,“singleton”或“thread”
    ♦ optional—— 当值为“true”时,即使创建类实例的过程中发生错误,也会继续装载。

然后,开发人员可以用<constant … />标签来选择使用哪一个配置。这里只有两个属性——“name”属性提供了扩展点的名字,这个扩展点是在新的实现中会被更改的,“value”属性的值是在<bean … />标签中配置的唯一标识。
<constant name="struts.objectFactory" value="plexus" />
<constant … />标签是把一个新值赋给已知属性的一种方式,但并不是唯一的方式。新值还可以在“web.xml”文件的“init-param”中修改,或者作为一个名-值对写入“struts.properties”配置文件。
如果你不是在开发插件,而是在普通的“struts.xml”文件中使用这些技术,那么还有捷径可走。把一般来说会放在<bean … />标签中的类名放进在<constant … />标签中——这样就避免了使用<bean … />标签。
下表列出了可配置的扩展点对应的接口与属性名。
接口     属性名     Scope     Description
com.opensymphony.
xwork2.ObjectFactory    struts.objectFactory     singleton    创建在框架中用到的对象——Action,结果,拦截器,业务对象等等。
com.opensymphony.
xwork2.
ActionProxyFactory     struts.
actionProxyFactory     singleton    创建ActionProxy
com.opensymphony.
xwork2.util.     struts.
objectTypeDeterminer    singleton    判断一个map或者collection

ObjectTypeDeterminer    中的key和元素是什么
org.apache.struts2.
dispatcher.mapper.
ActionMapper     struts.mapper.class     singleton    从请求中得到ActionMapping并从ActionMapping中得到URI
org.apache.struts2.
dispatcher.multipart.
MultiPartRequest     struts.multipart.
parser     per request     解析 多部件 请求(文件上传)
org.apache.struts2.
views.freemarker.
FreemarkerManager     struts.freemarker.
manager.classname     singleton    载入和处理Freemarker 模板
org.apache.struts2.
views.velocity.
VelocityManager     struts.velocity.
manager.classname     singleton    载入和处理 Velocity模板

<constant … /> 标签和 “web.xml”文件中的“init-param”不仅仅可以用来定义扩展点属性。“struts.properties”文件中的所有属性都可以通过二者进行修改。
惯例重于配置
惯例重于配置是Rails带入主流应用开发中的概念。它不是提供那些对于各个应用而言都很相似的配置文件,而是假定在绝大多数情况下,开发人员都会遵守特定的模式。这种模式具有足够的通用性,所以可以被认为是一种开发惯例,框架会默认使用这种模式,而不是为每一个新的应用都提供配置。在默认情况下,开发人员就不必再管理种种配置信息了。如果有的需求与惯例的配置信息不同,那么还可以根据需求进行修改,把默认模式覆盖掉。
Struts 2采用了这个概念。松耦合在给Struts 2带来高度灵活性的同时,也带来了配置上的高度复杂性。惯例在这二者之间做出了平衡,为我们提供了简洁而高效的开发者体验。
Struts 2中“惯例重于配置”的应用可以通过以下几个例子来说明: 
    ♦ 隐式的配置文件加载——不需要显式配置“struts-default.xml”和“struts-plugin.xml”(对每一个插件而言)文件,它们会被自动加载。
    ♦ Code Behind插件——在使用code behind 插件时,它会混合使用action名和结果字符串在结果模板中进行自动搜索,所以“/user/add.action” 这个action的“success”结果会返回“/user/add-success.jsp”结果模板,“error”结果会返回“/user/add-error.jsp”结果模板。
    ♦ 默认的结果和结果类型—— 在配置Action的时候,如果使用默认的“success”结果和JSP结果类型的话,就不需要对它们进行配置
    ♦ 织入(Wiring)Spring业务服务——在安装了Spring框架插件以后,就不必为每个Action所需的Spring提供的业务服务进行配置,这些 业务服务会被自动织入到Action里。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值