一个管理高尔夫小贴士的例子。
注意看 seam 如何:
- 利用注解定义组件
- 统一组件模型是怎么把应用程序的各层连到一起的
- configuration by exception 给业务逻辑实现带来多高的信噪比
1.Entity class作为back bean
seam中的entity class有两个作用。
其一是数据库读写时的数据传递,Seam不做ORM,那是JPA和hibernate的活。第八章会介绍seam如何启动ORM并管理它的persistence Manager的生命周期。
entity class的第二个作用就是作为back bean(struts中的ActionForm)获取用户输入。如果entity class的类定义中有@Name注解,那她就可以用在JSF view中。你可以直接把form中的输入直接绑定到entity class的属性中,并且JSF会处理必要的类型转换和有效性检查。
Seam会把带有@Name的entity class注册为seam组件(GolfTipe -> tip ),当seam container中有对该组件的请求时,seam就创建该组件对应类(GolfTipe)的实例,把她绑定到语境变量(tip)上放在conversation 语境(entity class的默认scope)中。
例子中还有几个JPA注解:
- @Entity 把类和数据库表连接起来
- @Id 该属性对应的列作为主键
- @GeneratedValue 数据库中自动生成的主键
根据 configuration by exception 语义,类中其他属性自动与同名的列相对应。
2.一个全能的组件
JSF页面和seam的service object之间不需要managed bean做中介。service object能直接响应UI中调用的action。不用担心这会让UI与应用逻辑纠缠在一起,seam本身就是中间层,action组件中不需要引用任何JSF资源。第三章内容,导航规则不再需要用方法的返回值作为指示,因为seam可以用EL表达式的值来干这个事。
TipAction类定义中有@Name注解,Seam会把她做成组件放到event语境中(JavaBean的默认scope),因此可以在UI控件中直接绑定她的方法。
@In,bijection(双向注入,想起来一本小说中看过的一个工具 ),这个注解可以把别的组件连进来。这次的两个组件是JPA 的EntityManager和seam内置的JSF message Manager(其实一般都有他们俩)。这个组件还准备了显示列表用的一个GolfTip Collection,以及获取用户在列表中选择的GolfTip的属性。
这个TipAction包含的内容挺多,作者希望我们注意的是:
除了注解之外,这个类里没有其他基础代码,除了创建状态信息,就是利用JPA的EntityManager在数据库中读取、保存和删除tip。也许最好是把这些放到一个DAO里,作为一个单独的seam组件,但seam并不会把这种架构性的要求强加给你。seam强调的是节俭,你不需要调用serverlet API来获取request参数,也不用设置request和session属性,这些组件仅仅需要实现业务逻辑。
3.把组件交给显示界面
Seam把Entity class的属性和Action component的方法绑定到JSF view中。这里有个facelets模板-golftips.xhtml,value- 和 method-binding 表达式被放到这个页面元素中用于数据显示、获取用户输入以及响应用户操作。下面用这个例子来介绍下JSF view和server端的seam组件交互。
首先看一下提交新tip的输入表单。每个input element 都对应GolfTip 实体类的一个属性。input 元素中 value 属性的值是一个EL 标记(e.g., #{tip.author}),这个EL标记就是value-binding表达式。在JSF生命周期中的某一环节,它会把页面表单中输入的值传给GolfTip 实体类的属性。
seam通过环境变量 tip把使用value-binding表达式的输入控件与GolfTip 实体类的属性关联起来。seam根据@Name创建tip环境变量,并把他放在container中。当JSF页面中的value表达式引用tip环境变量的时候,seam就根据tip找到container中GolfTip的实例。当提交表单时,输入值也会被赋值给Entity 实例的属性。
提交表单的时候,与serverlet API的交互都交给JSF了,seam不再关心,她只处理声明的bindings。注意这个, #{tipAction.add(tip)}。首先,她表明tipAction是这个表单的Action 组件,当用户点击Add按钮时,seam会调用他的add方法。值得一提的是,环境变量tip直接作为参数传给了action method。这个参数化的method-binding表达式是seam对JSF EL的改进。
4.按需取数
Seam之所以这么强大(真的很强大吗?),是因为她挺有眼力见,知道该在什么时候初始化变量。
一段显示数据库中tip记录的列表页面代码。
这里的明星是value表达式#{tips},注意!她不是seam组件。她(tips)对应的是TipAction组件的retrieveAllTips()方法上定义的@Factory注解的value属性。第一次有人找tips的时候,seam调用这个方法初始化tips环境变量,以后再有人找她的时候,seam就直接从container里面把她弄出来,不再去执行那个retrieve方法了。
你没看到retrieve方法的返回值?tips是环境变量,本来也不需要用返回值这么老土的方式啊,没看到在tips属性定义上面有个@DataModel。另外,属性上面有@Out的也是环境变量。这里之所以用了@DataModel,是因为要在view层显示一个可点击的tips列表,所以需要告诉seam把这个collection封装成JSF的DataModel。对,tips不是List<GolfTip>,是封装好的DataModel。
5.能点击的列表
注解中的scope是ScopeType.PAGE,seam会根据指示把tips collection放在JSF的component tree中,因此在那个页面中调用的任何action都能访问这个collection。
页面中每个golf tip旁边的删除链接(绑定了#{tipAction.delete})的实现,就是靠JSF component tree对tips的传递实现的。当用户点击delete按钮的时候,data model和JSF component tree一起重建,在JSF处理用户点击事件时,data model的内部指针被放在选中行上。这时候,@DataModelSelection就发挥作用了(它和@DataModel一起使用,用于数据选择)。她从DataModel中读取选中行,并注入给其所在属性。所有action 方法所要做的工作就是把这个选中的GolfTip传给JPA的EntityManager,由她去做底层的数据库操作。
看到没有,还是不需要敲那些基础代码。
6.测试
测试拖累了你,测试很无聊,很慢。程序员都知道!但是,测试很必要,不测不行。因此,任何没有提供测试环境的框架都是不完整的。seam是完整的,而且seam的测试很简单。
为了集成JSF action的测试,seam搞了个测试基类,她会给你安排一个独立运行的Java EE环境,执行JSF的生命周期。seam测试框架的底层是TestNG,一个现代化的(能用annonation)单元测试框架!其实TestNG没逼着你非继承个测试基类,是seam要用这种方式启动嵌入式JavaEE环境和JSF环境。
一大堆测试代码及其解释。EL在这里也能用!
打完收工!