Seam通过将实体类的属性和action组件的方法绑定到JSF视图中的元素来连接goft tips应用的不同层。图1.6显示的是golf tips用户交互界面,在这页面的背后是一个facelets模板,golftips.xhtml,该模板可以将值和方法绑定表达式关联到这个页面的元素进行数据输出、捕获表单输入和对用户动作进行响应。利用这个图来讲解JSF视图是如何与服务器端的Seam组件进行交互的。
注意 文件扩展名.xhtml表明这个文件是一个Facelets模板。Facelets是一个用于JSF的视图处理器,创建它的目的就是避免JSF和JSP生命周期的错误匹配。Facelets是Seam应用的首选视图技术,并且本书从头到尾都使用它。
首先我们将注意力集中在页面底部的表单上,这个表单是用来提交一个新tip的表单。每一个input元素都使用EL表达式(例如,#{tip.author})将其绑定到GolfTip实体类的属性上。当用于input元素的值属性时候,EL符号就作为值绑定表达式。作为JSF生命周期的一部分它捕获了input元素的输入值并将其转换为GolfTip实体类的实例。下面是生成表单的JSF模板的部分代码(稍微有些削减):
Seam通过上下文变量tip使作用在input域上的值表达式和GolfTip实体类建立关系。作用在GolfTip实体类上的@Name注解将该类绑定到了tip上下文变量。当tip上下文变量被JSF模板(#{tip.*})中的值表达式所引用时候,Seam初始化GolfTip类并且以变量名tip将其保存到Seam容器中。所有引用tip上下文变量的值表达式都绑定到相同的GolfTip实例。当提交表单的时候,输入值被转换成未保存的实体类实例的属性中。
让我们来看下form被提交后发生了什么。使用与JSF的集成Seam,任何与Servlet API的交互都被抽象出去了。取而代之的是,你通过声明绑定来完成交互。提交按钮的action属性指定了方法绑定表达式#{tipAction.add(tip)},表明TipAction这个组件作为这个表单的action 组件,当点击提交按钮的时候,就会调用add()方法。请注意这个方法表达式实际上是将与tip上下文变量相关的GolfTip实例作为唯一的参数传入到action 方法,这样实际上可以使该方法获取表单数据。Seam提供了参数化的方法绑定表达式,这是对JSF的一点改进。当方法执行完成后,tips的列表被刷新并且页面被重新渲染。
使Seam如此强大的是它包含了一个按需初始化变量的机制。图1.6的上半部分使用了下面的标签展示了在数据库中的tips集合数据:
这段代码的重点就是#{tips}值表达式。注意tips并不是golf tips应用中Seam组件的名字。然后,它在列表1.2中TipAction类的retrieveAllTips()方法上的注解@Factory的属性引用了它。这个方法的目的就是在请求它的时候初始化tips上下文变量值。后续对这个相同变量的请求就是返回这个前面被取过的值,而不是再次触发这个方法再执行一遍。
不对,等下儿!retrieveAllTips()方法没有返回值啊。这个值是如何传回到视图被渲染的呢?这里就有些小技巧。在执行完这个方法后,Seam输出带有@Out或者@DataModel注解的组件属性值到视图。Seam注意到了TipAction组件的tips属性有@DataModel注解。这就告诉了Seam不仅要输出tips上下文变量的值,而且要把该值包装到一个JSF 的DataModel实例中。视图对这个包装好的集合进行迭代并渲染数据网格(data grid)。将tips集合包装到DataModel中的原因就是要使可以点击的列表支持删除功能。
注解的scope是指定的是ScopeType.PAGE,它命令Seam将tips集合保存到JSF组件树中。因为数据模型被保存到JSF组件树中,所以来自这个页面的任何JSF action都可以获取tips集合。
#{tipAction.delete}方法表达式绑定到每一个golf tip的相邻的删除连接上,这要得益于通过JSF组件树来传播数据的tips数据模型。每当用户点击其中一个删除按钮的时候,数据模型就会随着JSF组件树重建。当JSF处理事件的时候,数据模型的内部指针就会定位到被激活行的索引位置。这里与@DataModelSelection注解对应,利用其作为@DataModel注解的补充。@DataModelSelection从数据模型中读取当前行数据(GolfTips实例)并注入到它所注解的属性中。所有action方法要做的就是要将被选中的GolfTip实例传给JPA EntityManager,让它将该实例从基础数据库中删除。
再次,与JSF设计蓝图相比,action组件中没有任何基础架构的代码。
所有所剩下的就是写个点到点的快速测试,以确保我们可以保存一个新的tip并且可以随后获得它。
在开发领域一贯使Java EE开发人员开发缓慢的多数情况就是测试。既使你从来不写测试,你也仍然在测试。你每次重新部署你的应用或者重启应用服务器来看你最新修改的结果都是在测试你的代码。这个方式测试又慢有烦人。目前,测试已经是任何应用开发的一个主要部分,并且没有框架是完全可以让“你在容器之外”进行测试的环境。Seam通过一个测试类再次展示了它的简单性,这个测试类能够处理所有Seam应用中所需要的集成测试。
为了能够轻而易举的进行JSF action的集成测试,Seam提供了一个基础测试类,该类创建了一个非常棒的Java EE环境来执行在test cases中JSF生命周期。测试的基础设施是由TestNG驱动,这是一个能够使用注解配置的最新的单元测试框架。尽管TestNG不需要你继承基础测试类,但是Seam的测试框架利用这种方法来建立必须的fixture,以启动内嵌的JavaEE环境和JSF上下文。
列表1.3中的测试类GolfTipsTest模拟了对golf tips页面的初始请求以及随后添加新的tip的表单提交。测试代码的调用几乎和应用部署中使用的一样。
列表1.3对JSF视图的初始渲染和随后来自页面的JSF action 触发都进行了测试。第一个请求是一个HTTP GET请求,它模拟了在浏览器中请求golf tips页面。这部分的测试验证了在render response阶段的取到的tips,Seam对DataModel进行了适当的处理,但是基于这个模型的集合是空的。第二部分的测试模拟了用户提交表单来创建新的tip。Update Model Values阶段完成了将输入值绑定到值表达式。方法表达式被绑定到提交按钮,然后显式的被调用。由于Seam自动地将Invoke Application阶段包装在事务中,所有你没必要担心开始和结束事务。最后,在Render Response阶段,测试了当tips被取回后,只找到一个tip并将作者的名字放到消息中以展示给用户。这个测试有意进行了简化,当然,这里还有许多其他需要验证的。这里主要侧重于展示使用简单的test框架进行一个Seam 应用测试是多容易以及你如何利用EL表达式完成断言。
希望golf tips应用已经使你对Seam是如何简化应用以及通过依靠一个集中式的容器、注解、配置例外和统一表达式来节省时间有个大体上的认识,这就是Seam的精华所在。在你开始沿着Seam之旅走下去并成为Seam专家之前,我现在想先大致告诉你下Seam还提供了些什么。