本文节选了 Michael Yuan 和 Thomas Heute 所著的即将出版 ** JBoss Seam: Power and Flexibility Beyond Java EE 5.0 ** 第一章和第二章,内容有所删减。
什么是 Seam?
JBoss Seam 是“Java EE 5.0 的一个轻量级的框架”。这是什么意思?难道 Java EE(Enterprise Edition) 5.0 本身不是一套“框架吗”?为什么在官方规范之外,还需要另外一个框架?好吧,我们就将 seam 看作是本应该被包括在 Java EE 5.0 中的一个“遗漏的框架”吧。它在 Java EE 5.0 框架的上层,为所有的在企业 Web 应用中的组件提供了一个统一的、易于理解的编程模型。它同样使基于状态的应用和业务流程驱动的应用的开发易如反掌。换句话说,Seam 致力于开发者生产力和应用扩展性。
1. 整合和强化 Java EE 框架
Java EE5.0 的核心框架是 EJB(Enterprise JavaBeans)3.0 和 JSF(JavaServer Faces)1.2。EJB 3.0(以下简称 EJB3) 是基于一个 POJO(Plain Old Java Objects) 的业务服务和数据库持久化的轻型框架。JSF 是一个基于 MVC(Model-View-Controller) 的 Web 应用框架。大多数的 Web 应用都将包含有业务逻辑的 EJB3 组件和 Web 应用前端显示的 JSF 组件。EJB3 和 JSF 虽然互补,但是他们是根据各自的理念设计的独立的框架。例如,EJB3 使用注解(annotation)来配置服务,而 JSF 使用的是 XML 文件。更进一步讲,EJB3 和 JSF 组件在框架层面上是互不敏感的。要整合 EJB3 和 JSF,开发者必须手动地构造 facade 对象(如:JSF 支持 bean),将业务组件与 Web 页面和样板代码(又称 plumbing 代码)联结起来,以便能跨框架调用方法。将这些技术粘合起来是 Seam 的职责之一。
Seam 打破了 EJB3 和 JSF 之间的人工层,它为整合 EJB3 和 JSF 提供了一个一致的,基于注解的途径。只需要个别简单的注解,Seam 中的 EJB3 业务组件就能直接被用来支持 JSF Web 表单或者处理 Web UI 事件。Seam 允许开发者将“同一种东西”——有注解的 POJOs——应用与所有的应用组件。与其他 Web 框架开发的应用相比,Seam 应用概念简洁,同样的功能却需要较少的代码(在 JAVA 和 XML 中)。如果没有耐心,或者想要快速预览,一个 Seam 到底有多简单,你可以现看看本文描述的 hello world 一例。
在 JSP 来说困难的任务,Seam 可以轻易的完成。例如,JSF 头疼的一个问题就是过分依赖 HTTP POST。这使得将一个添加到书签中的 JSF 网页,通过 HTTP GET 访问相当困难。但是有了 Seam,生成一个 REST 网页是非常容易的。Seam 提供了一系列 JSF 组件标签和注解,增加了“web 友好”和 JSF 应用的网页效率。
同时,Seam 拓展了 EJB3 到 POJO 的组件模式, 从 web 层到业务层都有了状态上下文。进一步说,Seam 整合了一系列主要的其他开放源代码框架,例如 jBPM、JBoss Rules(又名 Drools)、JBoss Portal、JBoss Microcontainer 等等。Seam 不仅能将它们“有机结合”起来,而且可以像整合 JSF 和 EJB3 一样强化原有的框架。
Seam 位于 Java EE 5.0 底层,但它的应用并不局限与 Java EE 5.0 服务器。一个 Seam 应用可以部署在 J2EE 1.4 应用服务器和 Tomcat 服务器上。这意味着现在能在 Seam 应用中得到产品化支持。
1 + 1 > 2或许有这样一种误解,认为 Seam 仅仅是将各种不同框架串起来的另外一个集成框架。Seam 提供了它自身管理的状态上下文,允许框架通过注解和 EL(表达式语言)表达式与其他框架进行深度整合。整合的程序来自于 Seam 开发者对第三方框架的认知。
2. 一个为 ORM 设计的 Web 框架
对象关系映射 (ORM) 解决方案在当今企业应用中广为使用。但是,大多数当前的业务和 web 框架并不是为 ORM 设计的,它们并不在整个 Web 交互生命周期——从请求来临到响应完成——管理持久上下文。这就导致了包括可怕的LazyInitializationException在内的各种 ORM 异常,带来了如“数据传输对象(DTO)”等丑陋的伎俩(ugly hacks)。
Gavin King 发明了 Seam,同时他也发明了在世界上广为使用的 ORM 解决方案 Hibernate。为了继承和发扬 ORM 的最佳实践,Seam 进行了重新设计。有了 Seam,就不必再写 DTO,你所做的就是延迟加载。因为扩展后的持久上下文就如同一个自然的高速缓存,可以减少和数据库的交互,ORM 的性能就会被极大地改进。
进一步讲,因为 Seam 整合了 ORM 层、业务层和表示层,开发者就能够在表示层直接展示 ORM 对象,也能把数据库验证注解用于输入表单,以及重新定向 ORM 例外到定制的错误页面。
3. 专为有状态 Web 应用而设计
Seam 是专为有状态 Web 应用而设计的。Web 应用是天生的多用户应用,电子商务应用天生也是有状态的和有事务的。但是,大多数已有 Web 应用框架是面向无状态应用的。开发者必须操作 HTTP 会话(session)对象来管理用户状态,与核心业务逻辑无关的代码不仅会混乱你的应用,而且带来了一系列的性能问题。
在 Seam 中,所有的基础应用组件天生地有状态。它们使用起来要比 HTTP session 容易,因为它们的状态由 Seam 公开管理。没有必要在 Seam 应用中编写引起麻烦的状态管理代码——只需在其组件上注解其做用域、生命周期方法以及其他状态属性,Seam 就会掌管其他 [译者注:指这些组件的生命周期]。Seam 状态组件要比 HTTP 会话(session)能更好的管理用户状态。例如,你能有多个“会话”进行,每个“会话”由在一个 HTTP 会话(session)中一系列的 Web 请求和业务方法调用组成。
进一步说,在 Seam 中,数据库缓存和事务能自动与应用的状态相连。Seam 在内存中自动保存数据库更新,等到对话结束后提交到数据库。内存中的缓存能大大减轻复杂状态应用中数据库的负载。
除了以上这些,Seam 支持整合开源 JBoss jBPM 业务程序引擎,大大提升了 Web 应用中的状态管理。你现在能为一个机构中不同工作人员(诸如客户、经理、技术支持人员等等)的指定工作流程,利用工作流程来驱动应用,而不是依赖用户界面事件处理和数据库。
4. 支持 Web 2.0
Seam 为 Web2.0 应用进行了充分的优化。它给 AJAX(异步 JavaScript 和 XML,增加网页交互的一种技术)提供了多种支持——从内置“零 Javascript”的 AJAX 组件到有 AJAX 支持的 JSF 组件,再到定制的 JavaScript 库,Seam 为浏览器端的 Javascript 对象提供了直接访问 Seam 服务器组件的途径。Seam 提供了一个先进的并发模型,有效的管理来自同一用户的多个 AJAX 请求。
对于 AJAX 应用,不断增长的数据库负载是一个巨大的挑战。与一个非 AJAX 应用相比,一个 AJAX 应用要向服务器发送的更频繁的请求。一但数据库必须响应这些 AJAX 请求,那么数据库就不堪重荷。Seam 中的状态持久上下文正如一个内存中的缓存,它能在会话始末保存信息,最终帮助减少数据库交互。
Web2.0 应用往往为其数据使用复杂关系模型(例如,一个网络交际站点所做的就是处理和显示“用户”之间的关系),对于这些站点,延迟加载对于 ORM 层至关重要。否则,一个简单的查询就能级联地加载整个数据库。正如我们前面所讨论过的,Seam 是现今唯一一个正确支持 Web 应用延时加载的 Web 框架。
5. 依赖双向映射的 Pojo 服务
Seam 是一个“轻量级”框架,因为它使用 POJO(plain old Java objects)作为服务组件。在应用中,POJO 没有使用接口或抽象类来"钩住"组件。当然,问题是如何使 POJO 交互来组成这个应用?它们如何与容器服务(例如,数据库持久化服务)交互?
Seam 通过使用一个流行的、被称作依赖注入 (DI) 的设计模式联结所有 POJO 组件。在这个模式下,Seam 框架管理着所有组件的生命周期。当一个组件需要使用另外一个时,它通过注解(annotation)向 Seam 声明此依赖。Seam 依据应用当前状态得到这个依赖组件,并将它注入到所需求的组件中。
通过拓展依赖注入概念,一个 Seam 组件 A 不但可以构造另外一个组件 B,而且把此组件 B“抛还”给 Seam 以备其他组件(例如组件 C)以后使用。
这类双向依赖管理甚至都广泛的应用于简单的 Seam web 应用中(例如第二章的 hello world 一例)。在 Seam 术语中,我们称这个为“依赖双向映射”。
6. 非常规的配置
[译者注:指以隐式映射为主题,以显式映射为例外的配置方式] 使 Seam 易用的主要设计原则是“非常规的配置”。其思想是为这些组件提供一系列默认行为,开发者只需要在预期行为非默认的时候,显示地配置组件。例如, 当 Seam 将组件 A 作为属性注入到组件 B 时,默认地,组件 A 刚会以组件 B 被注入的属性的名称命名。Seam 里还有很类似的细节。总的结果是 Seam 中配置元数据要比其他 Java 框架简单的多。因此,大多数的 Seam 应用能通过一系列简单的 Java 注解进行充分配置。开发者从减化的复杂度中受益匪浅,最后,与其他 Java 框架相比,用更少的代码实现同样的功能。
7. 避免滥用 XML
或许你已经注意到,Java 注解在表述和处理 Seam 配置元数据时扮演着重要的角色。通过这样的设计使框架更易于操作。
在 J2EE 发展早期,XML 曾经被看作配置管理的“圣杯”。框架设计者将所有的配置信息,包括 Java 类和方法名称都统统丢进 XML 文档,而不考虑对开发者所带来的后果。反省后,发现这是个严重的错误。XML 配置文档太过重复。开发者必须重复代码中已有的信息,从而将配置和代码联结起来。这些重复使应用易于出错(例如,一个拼写错误的类名可能在运行时显示为一个难于调试错误)。缺少合理的默认配置进一步使这一问题复杂化。事实上,在一些框架中,相当数量的样板代码伪装为 XML,可能相当于或者超过实际应用中 JAVA 代码的数量。对于 J2EE 开发者,XML 的滥用通常被称为“XML 地狱”。
Java 社区认识到了 XML 的滥用问题,并且已经非常成功地用 Java 代码中的注解取代了 XML。EJB3 是 Java 官方标准化机构促进 Java 企业组件中注解使用的一项成果。EJB3 完全可选择的使用 XML 文档,它向正确方向迈出了积极的一步。Seam 加入了 EJB3 的注解,为整个 web 应用拓展了基于注解的编程模型。
当然,XML 对于配置数据并非完全不利。Seam 设计者认识到 XML 适用于指定页面流程或者定义业务流程的 web 应用。XML 文档使开发者集中地管理整个 web 应用的工作流程成为可能,同时也反对将配置信息分散于 java 源文件中。工作流程很少能与源代码耦合,因此 XML 文档中并不需要重复键入已存在于代码中的信息。
8. 为测试而设计
Seam 为了易于测试而重新设计。因为所有的 Seam 组件都是注解过的 POJO,它们易于进行单元测试。开发者仅仅通过利用常规的 Java new 关键词来构造实例,然后在测试框架(例如 JUnit 或者 TestNG)中运行任何方法。如果需要测试多个 Seam 组件的交互,开发者则逐个实例化这些组件,然后手动建立它们的相互关系(也就是显示地使用 setter 方法,而不是依靠 Seam 依赖注入功能)。
集成测试整个 Seam 应用比较复杂,因为开发者必须在 Seam 容器中运行应用。Seam 用嵌入的轻量级容器来帮助该类测试。在测试框架中,开发者能按步骤地加载 Seam 容器,然后运行测试。
9. 卓越的工具支持
对于一个聚焦于开发者生产力的应用框架,开发工具的支持至关重要。Seam 发布了一个基于命令行的生成器,称作 SeamGen。SeamGen 类似于 Ruby-On-Rails 中的生成器,它支持诸如从一个数据库生成完整 CRUD 应用的功能,聪明的开发者会通过诸如“编辑 / 保存 / 在浏览器重新载入”的步骤、有测试支持的特性,来改进 web 应用。
但更重要的是,SeamGen 生成项目不依赖于主流的 Java 集成开发环境,如 Eclipse 和 NetBeans。有了 SeamGen,开发者可以随时入门。
10. 让我们开始编码吧
总而言之,Seam 为 JavaEE 应用削减了开发费用,同时,增加了 Java EE 5.0 不具有的强大的新功能。在下节(节选自本书第二章),我们将给您展示一些实际代码例子来阐述 Seam 如何工作的。你能通过网站 http://www.michaelyuan.com/seam/ 下载到本书中所有的例子的源代码。
Seam Hello World
JBoss Seam 是 EJB3 和 JSF 中间的粘合剂,这是 Jboss Seam 最基本的和最广泛的应用。通过被 Seam 管理的组件,Seam 允许这两个框架之间无缝 (不是有意双关的) 的集成。它为整个 web 应用拓展了基于注解的 EJB3 POJO 编程模型。在层与层之间,没有了必需的手动 JNDI 查找,没有了冗长的 JSF 支持 bean 的声明,没有了过多 facade 方法,没有了艰辛的对象传递,快哉!
继续在 Seam 中使用 JavaEE 模式在传统的 java EE 应用中,一些设计模式,例如 JNDI 查找、XML 声明组件、值对象、facade 是被强制使用的。Seam 用基于注解的 POJO 消除了这些人为的需求。但是,当 Seam 应用中真正需要它们的时候,仍然可以自由地使用这些模式。
编写一个 Seam web 应用概念上很简单。你只需要编码出下列组件:
- 实体对象代表数据模型。实体对象可能是 JPA 或者 Hibernate 中的 POJO 对象。它们自动地映射到关系数据库表。
- SF web 页面展示了用户界面。页面通过表单捕获用户的输入,并且显示结果。表单域与其数据显示数据库表,这些表被映射到实体 bean 或者实体 bean 的集合上。
- EJB3 会话 bean 或者注解过的 Seam POJO 可以作为 JSF Web 页面的 UI 事件处理器。它们处理封装在实体 bean 中的用户输入,为下一步(或者页面)生成显示的数据对象。
所有以上组件均由 Seam 自行管理,它们在运行时被自动注入到正确的页面或者对象。例如,当用户单击按钮提交一个 JSF 表单,Seam 就会自动解析表单域并构造一个实体 bean。然后,Seam 将实体 bean 传入同样被 Seam 构造的事件处理器会话 bean 中来处理。开发者不需要在代码中管理组件的生命周期和组件之间的相互关系。依赖处理过程中,没有样板代码和 XML 文件。
本章中,我们使用 hello world 一例来明确展示 Seam 如何粘合一个 web 应用。该例子工作如下:用户能在 web 表单中输入其名字来“问候”Seam。一旦她提交了表单,应用则保存她的名字到一个关系数据库中,并且显示所有已经“问候”过 Seam 的用户。该项目示例在该书下载的源代码中的 HelloWorld 文件夹中。为了建立它,你必须安装 Apache ANT 1.6 版本以上 ( http://ant.apache.org/ )。进入 HelloWorld 目录,运行命令 ant,则会生成 build/jars/helloworld.ear 文件,可以直接拷贝该文件到 Jboss AS 实例的 server/default/deploy 目录下。现在,启动 JBoss AS 并且打开网址 http://localhost:8080/helloworld/。
为了运行本书中的例子,我们建议您使用 JEMS GUI 安装程序安装一个与 Seam 兼容的 JBoss AS。您可以从 http://labs.jboss.com/portal/jemsinstaller/downloads 下载 JEMS 安装程序。如果您需要更多安装 JBoss AS 和应用部署帮助,请参见附录 A,“安装和部署 JBoss AS”
欢迎使用示例作为模板,快速开始你自己 Seam 项目(参见附录 B “使用应用示例作为模板”)。或者,你能使用命令行工具 Seam Gen (参见第四章“快速应用开发工具”) 自动生成项目模板,包括所有的配置文件。本章中,我将花少量的时间来阐释源代码项目中的目录结构。相反,我们将集中讨论代码和配置,这也是开发者建立一个 Seam 应用必需的。如此,我们就能将知识应用到任何一个项目结构,而不需要受模板的限制。
源代码目录一个 Seam 应用由 java 类和 XML 或文本配置文件组成。本书的项目例子中,java 源代码文件在 src 目录中,网页在 view 目录中,所有的配置文件都在 resources 目录中。更多信息请看附件 B, 使用应用示例作为模板。
1. 创建一个数据模型
Helloworld 应用中的数据模型仅仅是一个有 name 和 id 属性的 person 类。注解 **@Entity告诉容器映射该类到一个关系数据库表,每个属性对应表中一个字段,每个 person 实例相当于表中的一条记录。因为 Seam 采用非常规的配置方式,容器为表名和字段中仅仅使用类名和属性名。属性 id 上的@Id和@GeneratedValue** 注解暗示 id 字段是主键,它的值是应用服务器为每个保存到数据库的 peron 对象自动生成。
@Entity<br></br>@Name("person")<br></br>public class Person implements Serializable {<p> private long id;</p><br></br> private String name;<p> @Id @GeneratedValue</p><br></br> public long getId() { return id;}<br></br> public void setId(long id) { this.id = id; }<p> public String getName() { return name; }</p><br></br> public void setName(String name) {this.name = name;}<br></br>}
Person 类中最重要的注解是 @Name,它为这个将要注册于 Seam 中的 Person bean 指定了名称。在其他 Seam 组件中(例如,页面和会话 bean)中,开发者能指直接使用“person”来引用被管理的Person bean。
2. 将数据模型映射到 web 表单
在 JSF 页面中,我们使用 Person bean 来支持表单输入文本域。#{person.name}符号指代名为“person”的 Seam 组件的 name 属性,名为“person”的 Seam 组件是Person实体 bean 的一个实例。
<h:form><br></br>Please enter your name:<br/><br></br><h:inputText value="#{person.name}" size="15"/><br/><br></br><h:commandButton type="submit" value="Say Hello"<br></br> action="#{manager.sayHello}"/><br></br></h:form>
通过以下的实体表单,JSF 页面显示了数据库中所有已经向 Seam 说“hello”的用户。用户名单列表存储在一个名为“fans”的 Seam 组件中,它是一个List 对象。JSFdataTable通过遍历列表,每一行显示一个Person对象。Fan标记是fans列表的迭代子。
<h:dataTable value="#{fans}" var="fan"><br></br> <h:column><br></br> <h:outputText value="#{fan.name}"/><br></br> </h:column><br></br></h:dataTable><br></br>
图 2.1 显示了 Hello World 网页
当用户点击“Say Hello”按钮提交表单,Seam 用输入数据构造了该 person 组件。然后它调用了名为“manager”的 Seam 组件的 sayhello() 的方法 (像这样,#{manager.sayHello}是表单提交按钮的 UI 事件处理器),这就在数据库中保存了person对象并且刷新了fans列表。名为manager的组件是一个 EJB3 的会话 bean, 我们将在下节讨论该话题。
2. 处理 web 事件
Seam 中的名为 manager 的组件是会话 bean ManagerAction,正如该类中 **@Name注解指定的。ManagerAction 类有person和fans两个属性,这两个属性被@In和@Out** 所注解。
@Stateless<br></br>@Name("manager")<br></br>public class ManagerAction implements Manager {<p> @In @Out</p><br></br> private Person person;<p> @Out</p><br></br> private List <Person> fans;
注解 @In 和 @Out 在 Seam 编程模型中处于核心。因此,让我们看看到底它们在这里是做什么的。
注解 @In 告诉 Seam,在此会话 bean 中,执行任何一个方法之前,Seam 就会把由 JSF 表单构造的名为 person 组件赋给该 person 字段(通过依赖注入)。开发者能为 @In 中的注入的组件指定一个任意的名称,但是如果没有指定,如这里所示,Seam 会将同类型以及同名称的组件注入到该字段中。注解 @Out 告诉 Seam,在执行任何方法后,Seam 会将属性 fans 值和属性 person 的值都赋给被 Seam 管理的同名的组件。在 Seam 中,我们将这个操作称作 “依赖抛出”。以此,在 ManagerAction.sayHello() 方法中,我们仅仅需要更新属性 fans 和属性 person 的值,它们会自动显示在页面上。
什么是双向映射在 Seam 文件中,有时你就会看到术语“双向映射”。它指的是被 Seam 管理的组件和 Seam 管理上下之间的注入和抛出。
因为person属性已经通过注入持有了表单数据,sayHello()方法仅仅是通过 JPA EntityManager将它保存到数据库中,JPA EntityManager也是通过@PersistenceContext注入的。当方法返回之后,它便更新了fans和person对象并且把这两个对象抛出。方法sayHello()一般会返回 null,预示着在调用之后,更新的数据模型将在当前的 JSF 页面显示。
@PersistenceContext<br></br> private EntityManager em;<p> public String sayHello () {</p><br></br> em.persist (person);<br></br> person = new Person ();<br></br> fans = em.createQuery("select p from Person p")<br></br> .getResultList();<p> return null;</p><br></br> }
除了一些细节,我们基本完成了。可能你已经注意到,ManagerAction bean 类实现了 Manager 接口。为了符合 EJB3 会话 bean 规范,我需要一个能列出 bean 中所有业务方法的方法。下面是接口 Manager 代码,幸运的是,用任何高级 IDE 工具都能轻松地自动生成这个接口。
@Local<br></br>public interface Manager {<br></br> public String sayHello ();<br></br>}
这就是在 Hello World 例子中需要的所有代码。后面两章小节将涵盖 Seam 应用的其他方法和配置。如果开发者为了自己的小型数据库应用想立即编码和定制 helloworld 项目,那么现在就可以跳过本章的剩余部分。
4. 更易于理解的 seam 编程模型
现在我们已经大致了解了 Hello World 的应用。但是我们还有一些重要的话题继续,例如其他折中途径以及前面代码没有涉及到重要特性,我们将在本节讨论这些话题。它们能帮助开发者对 seam 更深刻的理解,但是如果你没有耐心,可以直接跳过本节,需要的时再来阅读。
4.1 Seam POJO 组件
上例中,我们用一个 EJB3 会话 bean 实现了应用逻辑,但是我们并不局限于 EJB3 组件。事实上,Seam 中任何一个有@Name注解的 POJO 都能被转化为一个可管理的组件。
例如,我们能将ManagerAction转化为一个 POJO,而不是一个 EJB3 session bean。
@Name("manager")<br></br>public class ManagerAction {<p> @In (create=true)</p><br></br> private EntityManager em;<p> ... ...</p><br></br>}
使用 POJO 取代 EJB3 bean 有正反两方面意见,使用 POJO 编程时很简单,因为它们不需要 EJB3 特有的注解和接口(参见上文)。如果你的所有业务组件都是 Seam POJO, 那么你就能不依赖 EJB3 应用服务器,运行你的 Seam 应用(参见 23 章,没有 EJB3 的 Seam)。
但是,POJO 比 EJB3 的功能少,因为 POJO 不能获得 EJB3 容器服务。在不依赖 EJB3 的 Seam 中丧失的 EJB3 服务就包括以下几点:
- @PersistenceContext注入在 POJO 中不在管用。为了在一个 Seam POJO 中得到 EntityManager,开发者不得不在 Seam 配种文件中初始化 EntityManager,然后使用 Seam 注解 @In 将它注入到 POJO 中。
- POJOs 中将不在支持方法级别事务声明(declarative method-level transaction)。相反,你可以配置 Seam 来划分事务,可以从收到 web 请求开始直到响应页面产生结束。
- Seam POJO 不是消息驱动组件。
- 不支持注解为@Asynchronous的方法。
- 不支持容器安全管理。
- 没有事务或者组件级别的持久上下文。Seam POJO 中的所有的持久上下文都是经过拓展的(更多细节请参见 7.1 “默认的对话作用域”)。
- 没有集成容器管理的体系结构(例如,JMX 控制台服务)。
- Seam POJO 方法中没有 Java RMI。
- Seam POJO 不能是注解为@WebService组件。
- 没有 JCA 集成。
所以当在 EJB3 容器中进行部署时,为什么每个人都想使用 POJO 组件?答案就是,POJO 组件对于纯“业务逻辑”组件非常有益。POJO 为其他组件代理了数据访问、消息传递和其他基本功能。例如,我们能使用 POJO 组件操纵 Seam 数据访问对象,这对“业务逻辑”POJO 是非常有用的,因为它们可以在需要的时候,在其他框架中被重用。但是总的来说,它们的应用要比 EJB3 组件少,特别是在中小型应用中。所以,本书的大多数例子我们都使用 EJB3 组件。
4.2 易于测试
我们已经在第一章中提到,Seam 为了不依赖容器的方便的测试,进行了重新设计。在helloworld项目中,我们在测试文件夹中包括了单元测试和集成测试这两个测试用例。在纯 Java SE 环境下,Seam 测试体系模拟了数据库、JSF、Seam 上下文以及其他应用服务器服务,只要运行 ant test 命令就能运行所有的测试。
4.3 基于 Getter 和 Setter 的双向映射
在 Hello World 一例中,我们已经展示了通过成员变量对 Seam 组件进行的双向映射,你也能通过 Getter 和 Setter 方法对组件进行双向映射。例如,以下代码就工作的很好。
private Person person;<br></br>private List <Person> fans;<p>@In</p><br></br>public void setPerson (Person person) {<br></br> this.person = person;<br></br>}<br></br>@Out<br></br>public Person getPerson () {<br></br> return person;<br></br>}<br></br>@Out<br></br>public List <Person> getFans () {<br></br> return fans;<br></br>}
虽然以上的 getter 和 setter 方法看似轻微,利用 getter 和 setter 方法的双向映射真正的价值在于能其加入定制逻辑来操纵双向映射的过程。例如,你可以验证被注入的对象或者快速地从数据库重新得到被抛出的对象。
4.4 避免过度的双向映射
在 Hello World 一例中 , 通过将数据组件作为业务组件的属性,可以轻易的减少或者消除双向映射。在 JSF 页面中,通过这种方式,开发者只需要引用业务组件,而不需要在业务组件和数据组件之间的双向映射。例如,开发者可以修改 ManagerAction 类为以下所述。
依赖双向映射是一个非常实用的设计模式。但是,正如其他设计模式,过度使用就会有害。过度的依赖双向映射让代码变得难以阅读,因为开发者必须理解每个注入的组件出自何处。过度的依赖双向映射也能增加性能消耗,因为双向映射是在运行时进行。
@Stateless<br></br>@Name("manager")<br></br>public class ManagerAction implements Manager {<p> private Person person;</p><br></br> public Person getPerson () {return person;}<br></br> public void setPerson (Person person) {<br></br> this.person = person;<br></br> }<p> private List <Person> fans;</p><br></br> public List<Person> getFans () {return fans;}<p>... ...</p><br></br>}
接下来,我们在页面上引用的属性如下:
<h:form><p>Please enter your name:<br/></p><p><h:inputText value="#{manager.person.name}"/></p><br></br><br/><br></br><h:commandButton type="submit" value="Say Hello"<br></br> action="#{manager.sayHello}"/><br></br></h:form>
... ...
<h:dataTable value="#{manager.fans}" var="fan"><br></br> <h:column><br></br> <h:outputText value="#{fan.name}"/><br></br> </h:column><br></br></h:dataTable>
最后,具有了依赖管理的 Seam 是多用的。通常用数据访问业务组件封装数据是一项好的实践,特别是针对有状态业务组件。
4.5 JSF 中的页面导航
本例中,只有一个页面。每次点击按钮后,JSF 页面会重新显示更新过的数据模型。显然,大多数 web 应用多于一个页面。在 JSF 中,一个用户界面事件处理器能通过返回导航规则名称,决定下一步该显示哪个页面。例如,开发者可以在navigation.xml中定义以下导航规则。
<navigation-case><br></br> <from-outcome>anotherPage</from-outcome><br></br> <to-view-id>/anotherPage.jsp</to-view-id><br></br></navigation-case><br></br>
之后,如果 sayHello() 方法返回一个名为“another page”的字符串,JSF 下一步就该展示 anotherPage.jsp。UI 事件处理器决定了接下来要显示哪个页面,从而为我们带来了有步骤的控制。
4.6 通过 EntityManager 访问数据库
JPA(Java Persistence API)EntityManager管理着关系数据库表与实体 bean 之间的映射。EntityManager 在运行时由应用服务器创建。你能使用注解 @PersistenceContext,注入一个 EntityManager 的实例。
EntityManager.persist()方法将实体 bean 存为与之对应数据表的一条记录。EntityManager.query() 方法运行 SQL 化的查询,并以实体 bean 集合形式从数据库返回数据。更多细节请参考 JPA 文件中关于如何使用 EntityManager 和查询语言。在本书中,我们只用最简单的查询语句。
默认地,EntityManager 将数据存于嵌入的 HSQL 数据库中。如果在本机上运行 Jboss AS,可以通过以下步骤,为 HSQL 数据库开启一个 GUI 控制台:访问 http://localhost:8080/jmx-console/ ,点击database=localDB,service=Hypersonic MBean 服务,之后,点击在 startDatabaseManager 方法下方的“invoke”按钮。你就可以从控制台执行任意 SQL 指令。
5. 配置和打包
下面,我们将转移话题,讨论配置文件和应用程序打包。实际上,你可以通过 Seam Gen 命令行工具,生成几乎所有的配置文档和构造脚本文件。或者你也可以简单的重用在示例中的源文件。所以,如果你想首先学习 Seam 编程技术,但又担心接下来的配置和部署,这个是很正常的。你可以完全放心地跳过本节,需要的时候可以再次阅读。
本节中,我们集中探讨 Seam EJB3 组件配置,JBoss AS 外的 Seam POJO 配置和部署当然是可行的。
大多数 Seam 配置文件都是 XML 文档。但是等等!我们刚才不是承诺 Seam 能让我们摆脱 J2EE 和 Spring 中的 XML 地狱吗?为什么它又有了 XML 文档呢? 是的,XML 文档确实有很多用处。XML 文档非常适合部署阶段的配置(例如 web 应用的根 URL 和后台数据库的定位)。因为它允许我们在部署阶段改变配置而不需要改变和重新编译源代码。它也适合粘合应用服务器中的不同子系统(例如,配置如何让 JSF 组件与 Seam EJB3 组件交互)。XML 文档也非常适合表示层相关内容(例如网页和页面导航流程)。
我们反对在 XML 文档中重复已经存在于 Java 源代码中的信息。开发者很快就会发现,这个简单的 SeamEJB3 应用有多个 XML 配置文档,每个文档那个都非常简短,并且没有一个包含存在于 Java 代码中的信息。换句话说,Seam 中没有“XML 代码”。
进一步讲,XML 文档中的大多数内容都是静态的。所以开发者能在自己的 Seam 应用中轻松地重用这些文档。如何使用示例作为自己的应用模板的介绍,请参见附录 B——使用应用示例作为模板。
我们将用下面几页来详细讲解示例应用的配置文档和打包后的目录结构。如果你没有耐心看下去,而且很满意这个应用模板,你可以跳过以下内容。不管怎样,不再罗嗦, 我们一起来了解 hello world 示例是如何进行配置和打包的。为了构建一个 JBoss AS 的部署 Seam 应用,我们必须将以上所有 java 类和配置文档打包为企业应用程序归档 (EAR)。该例中,EAR 文件是helloworld.ear。它包含了三个 JAR 文件那个和两个 XML 配置文档。
helloworld.ear<br></br>|+ app.war // 包含 Web 页面等 <br></br>|+ app.jar // 包含 Seam 组件 <br></br>|+ jboss-seam.jar // Seam 库 <br></br>|+ META-INF<br></br> |+ application.xml<br></br> |+ jboss-app.xml
> 源代码目录在此项目的源代码中,resources/WEB-INF 目录包含属于 app.war/WEB-INF 目录的配置文档。resources/META-INF 目录包含属于 app.jar/META-INF 和 helloworld.ear/META-INF 的文档。Resources 根目录包含属于根目录 app.jar 的文档。
application.xml 文档列出了在 EAR 中的 JAR 文件,并为该应用指定了根 URL。
<application><br></br> <display-name>Seam Hello World</display-name><p> <module></p><br></br> <web><br></br> <web-uri>app.war</web-uri><br></br> <context-root>/helloworld</context-root><br></br> </web><br></br> </module><p> <module></p><br></br> <ejb>app.jar</ejb><br></br> </module><p> <module></p><br></br> <java>jboss-seam.jar</java><br></br> </module><p></application></p>
jboss-app.xml文档为该应用指定了类加载器,每个 EAR 应用的类加载器应该有一个唯一的名称。这里我们使用应用程序名作为类加载器的名称,以避免重复。
<jboss-app><br></br> <loader-repository><br></br> helloworld:archive=helloworld.ear<br></br> </loader-repository><br></br></jboss-app><br></br>
jboss-seam.jar 是 Seam 发布 Seam 类库。app.war 和 app.jar 文档由我们来建构。所以,下面我们要研究 app.war 和 app.jar。
5.1. WAR 文件
app.war是按照 Web 应用程序归档规范打包的 JAR 文件,它包含页面和标准的 JSF/Seam 配置文档。你还可以将 JSF 特有的类库文件放入WEB-INF/lib目录 (例如jboss-seam-ui.jar)。
app.war<br></br>|+ hello.jsp<br></br>|+ index.html<br></br>|+ WEB-INF<br></br> |+ web.xml<br></br> |+ faces-config.xml<br></br> |+ components.xml<br></br> |+ navigation.xml
web.xml文档是所有 java EE web 应用必需的。JSF 用它来配置 JSF servlet 控制器,Seam 用它来拦截所有的 web 请求。该配置文档的相当标准。
<web-app version="2.4"<br></br> xmlns="http://java.sun.com/xml/ns/j2ee"<br></br> xmlns:xsi="..."<br></br> xsi:schemaLocation="..."><p> <!-- Seam --></p><br></br> <listener><br></br> <listener-class><br></br> org.jboss.seam.servlet.SeamListener<br></br> </listener-class><br></br> </listener><p> <!-- MyFaces --></p><br></br> <listener><br></br> <listener-class><br></br>org.apache.myfaces.webapp.StartupServletContextListener<br></br> </listener-class><br></br> </listener><p> <context-param></p><br></br> <param-name><br></br> javax.faces.STATE_SAVING_METHOD<br></br> </param-name><br></br> <param-value>client</param-value><br></br> </context-param><p> <servlet></p><br></br> <servlet-name>Faces Servlet</servlet-name><br></br> <servlet-class><br></br> javax.faces.webapp.FacesServlet<br></br> </servlet-class><br></br> <load-on-startup>1</load-on-startup><br></br> </servlet><p> <!-- Faces Servlet Mapping --></p><br></br> <servlet-mapping><br></br> <servlet-name>Faces Servlet</servlet-name><br></br> <url-pattern>*.seam</url-pattern><br></br> </servlet-mapping><br></br> <context-param><br></br> <param-name>javax.faces.CONFIG_FILES</param-name><br></br> <param-value>/WEB-INF/navigation.xml</param-value><br></br> </context-param><br></br></web-app>
faces-config.xml文档是 JSF 标准的配置文档,Seam 用它来将其拦截器添加到 JSF 生命周期中。
<faces-config><p> <lifecycle></p><br></br> <phase-listener><br></br> org.jboss.seam.jsf.SeamPhaseListener<br></br> </phase-listener><br></br> </lifecycle><p></faces-config></p>
navigation.xml 文档为多页面应用包含 JSF 页面导航规则。因为 hello world 示例只有一个简单的页面,因此该文档是空的。
components.xml文档包含 Seam 特有的配置选项,除jndi-pattern属性以外,其他都不依赖于应用。该属性必须包括 EAR 文档的名称,以便 Seam 通过其的 JNDI 全名访问 EJB3 bean。
<components ...><p> <core:init</p><br></br> jndi-pattern="helloworld/#{ejbName}/local"<br></br> debug="false"/><p> <core:manager conversation-timeout="120000"/></p><p></components></p>
#### 5.2. Seam 组件 JAR 包
app.jar文档包含所有的 EJB3bean 类(实体 bean 和会话 bean)以及 EJB3 相关的配置文档。
app.jar<br></br>|+ Person.class // entity bean<br></br>|+ Manager.class // session bean interface<br></br>|+ ManagerAction.class // session bean<br></br>|+ seam.properties // empty file but needed<br></br>|+ META-INF<br></br> |+ ejb-jar.xml<br></br> |+ persistence.xml
seam.properties文档这儿是空但必需的,因为 Jboss 要通过它知道此 JAR 文件包含 Seam EJB3 bean 类,并且相应地处理注解。
ejb-jar.xml文档包含额外的配置信息,这些信息能重载或者增补 EJB3 bean 上的注解。在一个 Seam 应用中,它能将所有的 EJB3 类加入 Seam 拦截器。我们能在所有的 Seam 应用中重用该文档。
<ejb-jar><br></br> <assembly-descriptor><br></br> <interceptor-binding><br></br> <ejb-name>*</ejb-name><br></br> <interceptor-class><br></br> org.jboss.seam.ejb.SeamInterceptor<br></br> </interceptor-class><br></br> </interceptor-binding><br></br> </assembly-descriptor><br></br></ejb-jar>
persistence.xml文档为 EJB3 实体 bean 配置了后台数据源。本例中,我们只是使用了被嵌入到 JBoss AS 中默认的 HSQL 数据库 (也就是java:/DefaultDS数据源)。
<persistence><br></br> <persistence-unit name="helloworld"><br></br> <provider><br></br> org.hibernate.ejb.HibernatePersistence<br></br> </provider><br></br> <jta-data-source>java:/DefaultDS</jta-data-source><br></br> <properties><br></br> <property name="hibernate.dialect"<br></br> value="org.hibernate.dialect.HSQLDialect"/><br></br> <property name="hibernate.hbm2ddl.auto"<br></br> value="create-drop"/><br></br> <property name="hibernate.show_sql" value="true"/><br></br> </properties><br></br> </persistence-unit><br></br></persistence><br></br>
这样,以上就是一个简单 Seam 应用所需的所有配置和打包。我们将在以后讨论到本书的更高级的主题时,涵盖更多的配置选项和类库。再次强调一下,Seam 应用入门最简单的方法就是,不要担心这些配置文件,只需要从已有的应用模板做起。
6. 为何这么简单?
这就是 hello world 应用,三个简单的 Java 类,一个 JSF 页面,一组静态配置文件。我们已经有了一个完整的数据库驱动的 web 应用。整个应用只需要的是少于 30 行的 Java 代码,没有一处“XML 代码”。但是如果开发者有 PHP 背景,你可能仍然会问“何以这么简单?我能在 php 中使用更少的代码吗?”
好吧,答案就是 Seam 应用在理论上要比 PHP(或者其他任何一种脚本语言)应用简单的多。Seam 组件模式允许我们,有所控制地,可维护地给应用增加更多的功能。我们很快就会发现,Seam 组件使开发有状态的和有事务的 web 应用变得易如反掌。对象关系映射框架(例如:实体 bean)允许我们将注意力放在抽象数据模型上,而不需要处理数据库特有的 SQL 语句。
本文是基于该书的第一章和第二章。后面的章节中,我们继续讨论如何使用 Seam 组件继续开发复杂的 Seam 应用。参见本书目录来查看本书的所有主题。
请看 Gavin King 的两个访谈录来查看以往关于 Seam 的话题。
- JBoss Seam 1.1 Indepth: An Interview with Gavin King
- JBoss Seam 1.0: rethinking web application architecture
关于作者
Dr. Michael Yuan 是 JBoss Seam: Simplicity and Power Beyond Java EE 5.0 一书的作者。该书探讨了下一代 web 应用框架。他同时也是 Nokia Smartphone Hacks 一书和其他三本技术读物的作者。Michael 潜心研究轻量级企业 web 应用,终端对终端的移动应用开发。你可以通过他的博客联系他。
查看英文原文: Introduction to JBoss Seam - - - - - -
译者简介: 包亮,一名普通的程序员,喜欢敏捷实践,喜欢"懒惰",减少重复,尽可能让工作变得简单。几年来,一直通过网络汲取知识,也希望通过网络将知识与人分享 。志愿参与 InfoQ 中文站内容建设,请邮件至 china-editorial@infoq.com 。
评论