用 jpa2web 生成 Ajax J2EE Web 应用程序

来源:互联网 发布:双拼域名购买 编辑:程序博客网 时间:2024/05/24 03:20
了解并试用新的开放源码工具 — jpa2web — 这种工具可以根据带 JPA 注解的 bean 生成基于 Ajax 的 J2EE Web 应用程序。通过使用 ZK 框架,这个工具生成的应用程序提供一个友好的基于 Ajax 的 Web 用户界面,允许用户添加、删除、搜索、修改和连接与数据库同步的对象实例。

jpa2web 是什么?

Hibernate(见 参考资料)等工具大大简化了 Java 对象与其数据库存储之间的映射;尤其是,很容易给 Java 类加上注解,从而指定对象持久化的方式。开发人员不再需要编写大量数据库集成代码。Hibernate 解决了持久化问题;但是,仍然需要创建 Web 页面来处理这些元素。对于中等规模的 Web 应用程序,典型的开发过程可能是这样的:开发人员首先编写表示某个领域模型的 Plain Old Java Object(POJO),然后创建不同的事务和 Web 用户界面。一部分模型元素常常涉及非事务性数据。客户、国家、地区、职员和公司是业务模型的典型元素,它们由操作员维护。

为什么不生成一个 Web 表示层,让它根据带注解的 bean 创建、添加、列出、删除和搜索这些元素呢?为什么不让这个表示层产生友好的 Ajax 用户体验呢?这就是 jpa2web 工具的主要目标,它采用以下处理流程:

  • 输入:带注解的 POJO bean(和可选的模板)。
  • 输出:一个 Ajax Web 应用程序,它可以处理模型元素并进行持久化。
  • 使用的技术:FreeMarker + ZK + Hibernate(关于这些技术的更多信息,参见 参考资料 中的链接)。

 

关于 ZK
ZK 是一种开放源码的 Ajax Web 框架,用来为 Web 应用程序创建富用户界面,在开发界面时只需编写很少的代码,而且不需要编写 JavaScript 代码。使用 ZK,可以像设计桌面应用程序那样设计 Web 应用程序。ZK 负责客户机和服务器上的 Ajax 处理。开发人员只需创建一个简单的 XML 文件(称为 zul 文件)来指定用户界面,并用选择的语言编写事件处理函数:Java 代码(编译型)、Bean Shell(解释型 Java)、Groovy、Ruby、JavaScript 等。

这个工具主要使用注解驱动的编程方式指定 ORM 映射。可以重用其中的许多注解来定义 Web 界面或创建可修改的原型。

下面几节解释如何使用 jpa2web 把不同复杂程度的 bean 映射到 Ajax Web 用户界面。接下来,说明 jpa2web 算法的工作原理和一些基本指令。最后,描述 jpa2web 的适用范围和以后的改进。

一个简单示例

本文使用一个领域模型示例,一些读者可能很熟悉这个模型。它借鉴了 Bill Burke 和 Richard Monson-Haefel 所写的 Enterprise JavaBeans, 3.0 中的领域模型示例(参见 参考资料 中的链接)。这个领域模型包含一个船只管理类 Ship.java(见 清单 1),这个类是最简单的 POJO:其中只有基本数据类型的成员。


清单 1. Ship.java
                  package com.titan.domain;import javax.persistence.*;@Entitypublic class Ship implements java.io.Serializable{   private int id;   private String name;   private double tonnage;   @Id @GeneratedValue   public int getId() { return id; }   public void setId(int id) { this.id = id; }   public String getName() { return name; }   public void setName(String name) { this.name = name; }   public double getTonnage() { return tonnage; }   public void setTonnage(double tonnage) { this.tonnage = tonnage; }      public String toString() { return name;   }}  

如果希望让 Ship.java 的实例与一个数据库保持同步,那么基本上需要两个用户界面:一个用来添加(或编辑)船只信息,另一个用来列出船只。第一个界面是一个用来输入船只信息(名称和吨位)的表单。第二个界面列出现有的船只及其字段,供用户选择和编辑(见 图 1):


图 1. 船只表单
船只表单

图 1 显示 jpa2web 生成的表单。注意,有三个图标:一个用来创建新的船只,一个列出所有船只(见 图 2),另一个删除已经持久化的船只。单击 OK 按钮,就会把船只信息持久化到数据库中。注意,因为 ID 字段是一个 GeneratedValue,所以它看起来被禁用了。

图 2 显示生成的第二个 Web 页面,这个页面列出现有的船只。用户可以单击一个船只,这时会显示前一个表单,让用户编辑船只的详细信息。


图 2. 船只列表
船只列表

引用其他类的类

如果对象引用其他类的对象,那么情况会复杂一些。Cabin.java 就属于这种情况,这个类与 Ship.java 之间存在多对一关系,它也有简单的属性(见 清单 2):


清单 2. Cabin.java
                  package com.titan.domain;import javax.persistence.*;@Entitypublic class Cabin implements java.io.Serializable{   private int id;   private String name;   private int bedCount;   private int deckLevel;   private Ship ship;   @Id @GeneratedValue   public int getId() { return id; }   public void setId(int id) { this.id = id; }    public String getName() { return name; }   public void setName(String name) { this.name = name; }   public int getBedCount() { return bedCount; }   public void setBedCount(int count) { this.bedCount = count; }   public int getDeckLevel() { return deckLevel; }   public void setDeckLevel(int level) { this.deckLevel = level; }   @ManyToOne   public Ship getShip() { return ship; }   public void setShip(Ship ship) { this.ship = ship; }      public String toString() {                  return name;   }}  

jpa2web 生成一个与 Ship.java 的表单相似的表单,但是提供了将给定的船舱与某一船只联系起来的方法。这需要一个按钮,这个按钮会打开一个模态对话框,让用户选择船只。图 3 显示生成的表单,图 4 显示用来选择船只的模态对话框。ZK 可以很轻松地处理模态对话框,所以这种技术非常方便。


图 3. 船舱表单
船舱表单

图 4. 选择船只
选择船只

具有更高多样性的类

现在看一个更复杂的示例。如果类与其他类形成不同的关系,那么怎么办?例如 Customer.java 类(见 清单 3)。这个类与其他类(分别是 CreditCard、PhoneReservation)同时形成一对一、多对多和一对多关系。


清单 3. Customer.java
                  package com.titan.domain;import javax.persistence.*;@Entitypublic class Customer implements java.io.Serializable{   private int id;   private String firstName;   private String lastName;   private boolean hasGoodCredit;   private Address address;   private Collection<Phone> phoneNumbers = new ArrayList<Phone>();   private CreditCard creditCard;   private Collection<Reservation> reservations =      new ArrayList<Reservation>();   @Id @GeneratedValue   public int getId() { return id; }   public void setId(int id) { this.id = id; }   public String getFirstName() { return firstName; }   public void setFirstName(String firstName) { this.firstName = firstName; }   public String getLastName() { return lastName; }   public void setLastName(String lastName) { this.lastName = lastName; }   public boolean getHasGoodCredit() { return hasGoodCredit; }   public void setHasGoodCredit(boolean flag) { hasGoodCredit = flag; }   @OneToOne(cascade={CascadeType.ALL})   public Address getAddress() { return address; }   public void setAddress(Address address) { this.address = address; }   @OneToOne(cascade={CascadeType.ALL})   @IndexColumn(name="INDEX_COL_CC")   public CreditCard getCreditCard() { return creditCard; }   public void setCreditCard(CreditCard card) { creditCard = card; }   @OneToMany(cascade={CascadeType.ALL},fetch=FetchType.EAGER)   @IndexColumn(name="INDEX_COL_PHON")   public Collection<Phone>getPhoneNumbers() { return phoneNumbers; }   public void setPhoneNumbers(Collection<Phone> phones) {        this.phoneNumbers = phones;    }   @ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)   @IndexColumn(name="INDEX_COL_CUS")   public Collection<Reservation> getReservations() { return reservations; }   public void setReservations(Collection<Reservation> reservations) {        this.reservations = reservations;    }      public String toString() {                  return getLastName()+","+getFirstName();   }}  

图 5 说明如何显示清单 3 中的类。这个页面包含多个区域,我们将逐一讨论这些区域。


图 5. 客户表单
客户表单

我们来看看如何显示 CreditCard 关系。

Customer-CreditCard:一对一

这种情况与 Cabin.javaShip.java 之间的关系(多对一)相似,在处理多对一关系时会通过一个按钮打开模态表单。但是,因为 Customer-CreditCard 的关系是一对一,即每个信用卡号只与一个客户相关联,所以打开对话框来选择现有实例是不合适的。而是应该打开一个 CreditCard 表单 对话框,可以在其中输入新信用卡的完整信息(见 图 6):


图 6. 填写信用卡详细信息
填写信用卡详细信息

Customer-Phone:一对多

Customer-Phone 生成一个网格,用户可以在其中输入新电话号码的详细信息。按 Add Row 按钮就会创建一个用于输入新电话号码的新行。

Customer-Reservation:多对多

Add.. 按钮在列表框中添加一个新预订,可以用一个选择器模态对话框收集所选的预订。图 7 显示完成后的表单,其中已经添加了电话号码和预订:


图 7. 填写客户电话信息
填写客户电话信息

对窗口进行修饰

目前,您可能不喜欢生成的窗口的外观。有两个主要问题:字段标签显示 Java 属性名,而且它们出现的次序看起来很乱。可以用 jpa2web 工具的定制注解解决这些外观问题。例如,在 Cabin 类(清单 4)中添加 MemberField 注解,会让表单有更好的外观(见 图 8)。对比修饰后的版本和原来的版本(图 3)。标题现在包含有意义的名称,字段的次序现在也更有意义了。


图 8. 修饰后的船舱表单
修饰后的船舱表单

清单 4. Cabin.java 中的修饰注解
                @Entitypublic class Cabin implements java.io.Serializable{   @Id @GeneratedValue @MemberField(order=1,showName="ID")   public int getId() { return id; }    public String getName() { return name; } @MemberField(order=2,showName="Cabin Name")   @MemberField(order=2,showName="Number of Beds")   public int getBedCount() { return bedCount; }   @MemberField(order=3,showName="Deck Level")   public int getDeckLevel() { return deckLevel; }   @ManyToOne @MemberField(showName="Ship")   public Ship getShip() { return ship; }   ....}

其他修饰性特性包括当某个字段可以接受一系列预定义值时,生成组合框而不是文本框。这也要使用 MemberField 注解来实现。还可以定义许多其他注解来定制生成的窗口的外观和感觉。

jpa2web 应用程序的运行情况

现在已经了解了 jpa2web 应用程序的性质,接下来看看它的运行情况。按照以下步骤用 Apache Tomcat 运行 jpa2web 应用程序:

  1. 从 Sourceforge 下载 jpa2web。
  2. 将下载文件解压到磁盘上的一个文件夹(下面将这个位置称为 [dir])。
  3. 从 Apache 站点下载 Tomcat 并安装在一个文件夹中(下面将这个位置称为 [tomcatdir])。
  4. 将 [dir]/zklibs 和 [dir]/jboss_libs 中的 JAR 文件复制到 [tomcatdir]/lib。
  5. 下载适当的 JDBC 驱动程序并将 .jar 文件复制到 [tomcatdir]/lib。
  6. 在 [dir]/modelsrc 文件夹下面编写领域模型源文件(带 JPA 注解的 bean)。
  7. 修改 [dir]/templates/hibernate-cfg.xml 文件以连接数据库(参见以下代码示例):
<hibernate-configuration><session-factory name="thefactory"><property name="hibernate.connection.driver_class">net.sourceforge.jtds.jdbc.Driver</property><property name="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</property><property name="hibernate.hbm2ddl.auto">update</property><property name="hibernate.connection.url">jdbc:jtds:sqlserver://127.0.0.1:1433/test</property><property name="hibernate.connection.username">sa</property><property name="hibernate.connection.password">*</property><property name="hibernate.max_fetch_depth">0</property><#list windows as win><mapping class="${win.mainClass.name}" /></#list></session-factory></hibernate-configuration>

  1. 修改 [rootdir]/jpa2web-loader.xml 文件,包含希望生成的文件的包。
  2. 在 build-tomcat.xml ANT 文件中修改 Tomcat 的路径并执行默认目标:$ ant -buildfile build-tomcat.xml
  3. 现在,在 [dir]/dist 文件夹中会生成 Web 应用程序的 Web 文件夹,还将它复制到 Tomcat 的 webapps 目录中。可以通过 http://localhost:8080/sampleapp.war(或相似的 URL)访问这个应用程序。

生成器过程

尽管详细解释 jpa2web 生成器的工作方式超出了本文的范围,但是下面会简要讨论一下生成器引擎背后的主要思想和方法。

输入很简单:一系列带 JPA 注解的类。读取这些源文件并构建 bean 及其相互关系的内部表示(称为装载器)。jpa2web 只实现了 EntityLoader(它读取 JPA 注解),但是可以实现其他装载器(例如,读取 XML 映射配置文件的装载器)。然后,将这个结构用作生成器的输入,生成器还以一系列 FreeMarker 模板作为输入(关于 FreeMarker 模板的更多信息参见 参考资料)。使用模板使 jpa2web 能够以非常灵活的方式生成窗口。图 9 说明这个过程的各个阶段。生成器的输出是一个 Web 应用程序的 WAR 文件夹,其中包含与每个带注解的 bean 对应的窗口,以及用来选择窗口的索引。


图 9. 生成过程
生成过程

经过简化的生成器伪代码如下:


生成器算法
                for each bean in the model:  -create a form window called fully.classified.className.zul:    for each simple field      if (field is String) render textbox      if (field is int/byte/short) render integerbox      if (field is boolean) render checkbox      if (field is double/float) render doublebox/floatbox    for each OneToOne member:      render a button that will open a form to create an instance of the destiny class    for each ManyToOne member:      render a button that will open a list to choose an instance of the destiny class    for each OneToMany member:      render a table with the details of the destiny class and buttons         to add and delete rows    for each ManyToMany member:      render a listbox with the elements mapped,       and a button that will open a list to choose an instance of the destiny class  -create a window called list.fully.classified.className.zul   which lists all of the instances  -add the annotated class to the mapping area of hibernate.cfg.xml  -add links to a menu bar with the two windows generated for this bean

以上算法只是建立映射的一种方法,还有许多其他方法和解决方案,适用于不同的场景。通过使用其他注解,可以在运行时调整生成过程本身。

结束语

还有其他一些工具能够根据某种输入生成 Web 应用程序代码,其中许多是模型驱动体系结构(MDA)的完整实现。jpa2web 工具是一种轻量的注解驱动的代码生成工具,而不是 MDA 的完整实现。

在根据用户输入生成 Web 应用程序代码的工具中,最流行的可能是 AndroMDA,它可以生成使用不同框架(比如 Struts、JSR 和 Spring)的应用程序。AndroMDA 以模型作为输入(通常采用 UML/XMI 格式)并根据一些基本指令生成应用程序。

要想开发能够在大型部署中使用的生成器,开发人员必须解决几个问题。对于产生复杂的方向图的类模型,jpa2web 显然无法处理,尤其是在存在复杂的循环引用的情况下。到编写本文时,jpa2web 不支持一对多关系内的一对多和多对多(但是这个实现是可行的,jpa2web 发展计划可能会包含这个特性)。无论如何,必须进一步研究如何为复杂的模型生成窗口。不适合使用 jpa2web 的最明显的情况包括,创建事务性更强的 Web 应用程序,以及用户界面不与类模型直接对应的场景。

尽管存在一些限制,在许多情况下 jpa2web 仍然是一个有用的工具。通过使用 jpa2web,只需提供带注解的 bean,就能够为非事务性元素快速生成 Web 界面。还可以用它在数据库中创建必要的对象实例以执行测试,从而避免编写复杂的实体创建脚本。jpa2web 的输出只能作为需要进一步开发的原型,不应该作为最终的应用程序。生成的代码相当简单,修改其行为以及添加检验功能和额外的处理也很容易。但是,对于更大型的应用程序,jpa2web 的适用范围受到事务、安全性、并发性和多层约束的限制,需要做一些额外的集成工作。

计划实现的改进包括支持元素间关系更复杂的领域模型,添加定制的注解以便更好地控制生成的用户界面,以及从其他 Ajax 工具箱(比如 Google 的 GWT、OpenLaszo 或 Echo2)生成代码。还可能扩展生成器的输入类型,可能使用 XML 文件输入模型。如果您有兴趣帮助扩展 jpa2web 或希望进一步了解它的功能,请与作者联系。



参考资料

学习
  • 您可以参阅本文在 develperWorks 全球网站上的 英文原文。

  • 了解关于 POJO(Plain Old Java Object) 的更多信息。

  • 了解 Hibernate。

  • 了解 ZK。

  • 阅读 Bill Burke 和 Richard Monson-Haefel 所写的 “Enterprise JavaBeans, 3.0” 一书(O'Reilly,第 5 版,2006 年)。

  • 了解 FreeMarker 模板。

  • 了解关于 MDA(Model Driven Architecture) 的更多信息。

  • 了解 JSR-000220 Enterprise JavaBeans 3.0。

  • 了解 ZK 框架。

  • 查看 ZK Live Demo。

  • 了解 Red Hat Middleware, Hibernate hbm2ddl。

  • 阅读 AndroMDA, an Open Source MDA Generator。

  • 熟悉 Java 注解。

  • 了解 Apache Struts Framework。

  • 了解 JavaServer Faces 技术。

  • 了解强大的 Spring Framework。

  • Google Web Toolkit 可以帮助开发人员用 Java 语言构建 Ajax 应用程序。

  • 了解 OpenLaszlo,这是一种用来构建富 Internet 应用程序的开放源码平台。

  • 了解 Echo2。 
原创粉丝点击