|
一般来说,“Beans 绑定”是一种 Java 技术,它允许将纯 JavaBean 类中的一些属性自动绑定到 GUI 表单中。针对 beans 绑定的库才出现仅仅几年,但 JSR-295 正式规范的发行使该技术的普及程度与日俱增。Sun 官方规范 JSR-295 及其参考实现(大写的 "Beans Binding")还得到了 NetBeans 6 的支持。但不用担心:本文不是关于 NetBeans 和 BeansBindings 的另一篇教程,因为 围绕这一主题的文章已经不少了 (即便如此,在NetBeans RCP 项目中使用 JSR-295 似乎也没什么可说的)。我的观点是,将 Beans Binding 应用于其他一些用途,而不是仅仅用于表单的绑定。您知道,我喜欢将这些素材用于不同的事情上,而不是用于已经设计完成的内容。
需要提醒的是,本项工作此时仍处于开展之中。
上下文: blueMarine Metadata
让我们先从下面的上下文开始,该上下文是 blueMarine 中的一个新子项目,名称为 blueMarine Metadata。顾名思义,这个项目负责管理照片的元数据。简言之,它的需求包括:
- 系统必须能够管理不同元数据项(例如,两种常见的照片 EXIF 和 IPTC,但是我们也考虑了将来会出现地理签和其他素材),并且管理新的元数据项的代码必须是可插拔的。
- 系统必须能够管理不同的元数据源。例如,明显的元数据源就是图像本身(比如 JPEG 或 TIFF),当然还包括 XMP (由 Adobe 发明的 "sidecar" 格式,是一个将原始相片文件一个挨一个放置的外部文件),还应该包含能执行快速查询的永久数据库。
- 持久化技术必须是可插拔的——JPA 是最常见的一种技术,但是它必须是专用的;而且底层数据库不应仅限于 RDBMS。
- 各个组件必须具有可重用性,因为给它们分配了不同的目标,blueMarine 仅仅是基本的,而 Web 应用程序是另一种可能目标。

我特别看好可重用性,因为元数据素材在接下来的几个月将发展为有意义的素材,但是现在我还不打算告诉你更多的细节(是的,有点捉弄人 ;-))。此刻,我想指出 NetBeans 与 Beans Binding 集成对于快速书写 GUI 表单具有重大意义。

常见的设计模式
既然模块通常是由 TDD 而开发的,并且已经获得了良好的测试覆盖率,那么我只能着重在持久化领域内做一些试验。
首先让我们看看下面这张图:

图的上部显示了人们较为常用的模式,这可能是受 J2EE 蓝图的影响:
- GUI 调用 Business 组件
- Business 组件调用 Persistence 组件
从左至右的箭头不仅反映进入请求流程,而且反映各个组件的依赖关系:
- GUI 组件依赖 Business 组件
- Business 组件依赖 Persistence 组件
这种方法明显受 J2EE 蓝图的影响 —— 实际上,blueMarine 的许多部分针对这种情况进行设计的,从而清楚地反映当启动项目时开发人员 99% 采用 J2EE 体系结构。这里存在两种主要缺陷:
- 虽然这种方法很适合 Web 1.0,客户启动每项事务(信息是被“拉取”得到的),但是不管出于何种原因,对企业层发生的改变不会提供任何支持。成熟的 MVC 模式可以处理这种缺陷,实际上富桌面应用程序已经采用这种方法几十年了( Web 2.0 的开发也是应用 MVC 设计模式开始的)。为了避免引入从 Business 层到 GUI 的依赖关系,MVC 使用了 Observable 模式:Business 对象公开 GUI 用于注册和接收更改通知的 “侦听器” API。
- Business 对象依赖 Persistence 技术。由于采用该技术将迫使您使用特定技术,所以此项技术不好。通常人们通过引入厂和接口取消耦合限制来解决这个问题,虽然该方法能发挥作用,但增加了复杂性。更好的解决方案是采用 Inversion Of Control,让 Persistence 依赖 Business。此时,Business 和 Persistence 之间的交互可以通过侦听器实施某种镜像 MVC 模式,因此 Persistence 接收来自 Business 的更改通知。
我还没有描述 Persistence 成为修改原创者并调用 Business 进行修改的能力,因为考虑到通常不会出现数据库;但是,在本样例中,Persistence 可能是一个图像文件,如果在磁盘上使用另一种编辑工具就会发生这种情况。因此复制 MVC 有更多的好处。
针对 Persistence 的 Beans Binding
现在谈谈我的观点。Beans Binding 是在没有请求写入特定侦听器的情况下,主要作为 GUI 和 Business 对象实施 MVC 的可选方法;而 Beans Binding 利用标准的 java.beans.PropertyChangeListener 在 Java 运行时间内实施,从而提出所有涉及对象都遵循 JavaBeans 规范这一附加要求(记住, “真正”的 JavaBean 不同于 POJO,因为它必须对每种适当更改提供合适的时间通知)。这样做有些麻烦,我在本帖的结尾处会加以解决,但是我希望这只是一个样板代码问题,而不是设计问题。
但是,如果 Business 和 Persistence 之间的关系可以用某种 MVC 表示,我们为什么不同样用 Beans Binding 来接管 Business 和 Persistence 之间的关系呢?
我在 blueMarine Metadata 子项目的最初概述中早就完成了这项工作,如下图上部所示:

现在,Form、Domain Object 和 Persistence 相互之间完美地取消了耦合限制 —— UI Binder 和 Persistence Binder 这两个特定服务胜任此项任务,方法是采用 JSR 295 使它们保持同步。任何对象的任何变化将适当地反映到其他对象中:这会在下列情况中发生:用户在表单中键入一些内容(更新内容会被传递到 Business 和Persistence 对象中),以及 Persistence 中会发生一些改变(更新内容会被传递到 Business 和Form 中);当然这些更改源于 Business Object (例如,计时器触发)并且其他两个对象也会得到更新。
要牢记,Business Object 必须是被动的,例如 Value Object,但是它也可以包含商业逻辑(例如,属性的改变可能源于一些计算);另一方面,"Binder" 是只能调用一次的 Service,还需要对它进行设置。在对象状态的演变中它们就不再起更多作用。我喜欢这种方法,因为相对于“被动 Object”而言,它克服了“主动 Service”的传统模式的缺陷。相反,每一个对象是主动的,其设计仅受域模型驱动。
同样,在给出的例子中 Persistence 对象在 Domain Bean(某些属性)基础上建模,但这并不是一种约束。例如,在 blueMarine 中,我已经实现了持久化模块,其中每个 JavaBean 的每种属性在单独表格中存储为 tuple ( “metadata group”、“metadata property name”、“value”)。Beans Binding 为您提供了很大的灵活性。
两个问题
在设计方面存在两个问题,都与当前可用的特定技术有关:
- 事务。 如何界定它们? 考虑到用户在 Form 中键入内容,并且希望所做的更改是持久性的,我们应该按照下列方法处理:用户键入内容,但是 Beans Binding 并不立即传递更改内容;相反,当用户按 “Save” 按钮时,系统就会生成这样的序列:“press Save -> begin TX -> propagate changes -> commit TX”。另一种方法( 我希望该方法用于 blueMarine )是,用户在表单中键入内容时将每一个更改自动提交给数据库(顺序为:type -> begin TX -> propagate changes -> commit TX)。不幸的是,用于 NetBeans RCP 的 NetBeans Matisse 项目当前所支持的 JSR 295 不允许设置任何自定义事务标定(问题只有在 JSR 295与 “Swing Application Framework”, JSR 296 协同应用时才能解决) —— 实际上,使用 NetBeans RCP 在生成的代码中根本没有任何事务。
- 不受欢迎的 AWT Thread。在 AWT Thread 中不执行来自 Business Object 或者 Persistence 的更改,但对 Form 的任何更该必须在其中执行;不幸的是,JSR 295 此时还不自动支持这一操作。
我的解决办法是添加两个修饰模式:
- Transactional Decorator 确保每一个属性更改(从图表的左侧传入)被 begin TX / commit TX 封装(当然,如果希望事务对其产生影响,我们可能考虑在 Business Object 的左侧放置修饰模式,但这只是小的细节问题)。
- AWT Thread Decorator 确保每个属性的更改(从图表的右侧传入)由
SwingUtilities.invokeLater() 执行。
当然我们不希望手动为这些修饰模式编写代码。我们可以使用 java.lang.reflect.Proxy 生成动态代理,但是不幸的是,这需要一个接口,而 Java Beans 还没有这样的接口 ( 强烈抗议:为什么 JavaBeans 规范没有考虑到这类接口?)。CGLIB 库能提供解决方案,允许通过字节码操作从具体的类开始,创建动态代理:
package it.tidalwave.metadata.persistence.jpa;
import java.lang.reflect.Method; import java.util.logging.Logger; import javax.persistence.EntityManager; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy;
public class TransactionalBeanFactory { public EntityManager entityManager; public <T> T createTransactionalDecorator (final T object) { final Enhancer e = new Enhancer(); e.setSuperclass(object.getClass()); e.setCallback(new MethodInterceptor() { public Object intercept (final Object obj, final Method method, final Object[] args, final MethodProxy proxy) throws Throwable { boolean demarcateTransaction = ...;
if (demarcateTransaction) { entityManager.getTransaction().begin(); } final Object result = proxy.invokeSuper(obj, args);
if (demarcateTransaction) { entityManager.getTransaction().commit(); } return result; } }); return (T)e.create(); } }
类似方法可以用于 AWT Thread Decorator。
您是不是正考虑 AOP,当然,本文基本上会实现一个方面。初于简便,我喜欢用 CGLIB 通过手动方式操作而不是借助 AspectJ ,因为我不想给我的项目添加又一个复杂的库(还要使用新的语言)。
关于样本代码?
众所周知,对所有侦听器恰当实现 JavaBeans 是一个难点。而对于 UI,只需从 SwingLabs 扩展 AbstractBean 类,这项工作 90% 已经完成,但是对于 Business 和 Persistence 对象来说这并不是可行的解决方案,基于以下两个原因:
- 在这些层中使用 Swing 特定库不好;
- 我不喜欢要求继承结构的解决方案。
此时,我已经借助一些速成工具自动生成所需要的样板代码,但我想再试试 CGLIB 方法,即通过生成修饰模式将简单的 POJO 转换成成熟的 JavaBean。在我做此项工作之前,我仔细地搜索了是否已经存在实施这一方法的内容。
目前的情况是这一工作已经通过测试,但尚未形成产品,因为 CGLIB 与 NetBeans RCP 类加载器存在一些冲突。我希望尽快解决这个问题。还有一些我正在着手处理的其他特定设计问题,最突出的就是 blueMarine 的另一个模块。我试图将该模块应用于本模式中,但由于缺乏特定样例而放弃了。但是,这只是一项初步工作,期望将来对我的工作进行更新。
同时,我希望收到您的一些反馈意见。
备注:关于这项工作,我想知道 JSR-295 包的名称 org.jdesktop.beansbinding 是否有什么不妥之处,因为该技术已经超出了桌面应用程序的范围。或许 org.beans.beansbinding 会更好一些?
|