Java Solaris 加入 SDN 参与讨论 我的社区 注册说明
 
JDK 6.0 API 中文版
 
 
 
 
 
Java API 文档中文版
BeansBinding: 不仅仅用于 GUI?
 
By Fabrizio Giudici, 1/24/08  

一般来说,“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。顾名思义,这个项目负责管理照片的元数据。简言之,它的需求包括:

  1. 系统必须能够管理不同元数据项(例如,两种常见的照片 EXIF 和 IPTC,但是我们也考虑了将来会出现地理签和其他素材),并且管理新的元数据项的代码必须是可插拔的。
  2. 系统必须能够管理不同的元数据源。例如,明显的元数据源就是图像本身(比如 JPEG 或 TIFF),当然还包括 XMP (由 Adobe 发明的 "sidecar" 格式,是一个将原始相片文件一个挨一个放置的外部文件),还应该包含能执行快速查询的永久数据库。
  3. 持久化技术必须是可插拔的——JPA 是最常见的一种技术,但是它必须是专用的;而且底层数据库不应仅限于 RDBMS。
  4. 各个组件必须具有可重用性,因为给它们分配了不同的目标,blueMarine 仅仅是基本的,而 Web 应用程序是另一种可能目标。




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




常见的设计模式

既然模块通常是由 TDD 而开发的,并且已经获得了良好的测试覆盖率,那么我只能着重在持久化领域内做一些试验。

首先让我们看看下面这张图:




图的上部显示了人们较为常用的模式,这可能是受 J2EE 蓝图的影响:

  1. GUI 调用 Business 组件
  2. Business 组件调用 Persistence 组件

从左至右的箭头不仅反映进入请求流程,而且反映各个组件的依赖关系:

  1. GUI 组件依赖 Business 组件
  2. Business 组件依赖 Persistence 组件

这种方法明显受 J2EE 蓝图的影响 —— 实际上,blueMarine 的许多部分针对这种情况进行设计的,从而清楚地反映当启动项目时开发人员 99% 采用 J2EE 体系结构。这里存在两种主要缺陷:

  1. 虽然这种方法很适合 Web 1.0,客户启动每项事务(信息是被“拉取”得到的),但是不管出于何种原因,对企业层发生的改变不会提供任何支持。成熟的 MVC 模式可以处理这种缺陷,实际上富桌面应用程序已经采用这种方法几十年了( Web 2.0 的开发也是应用 MVC 设计模式开始的)。为了避免引入从 Business 层到 GUI 的依赖关系,MVC 使用了 Observable 模式:Business 对象公开 GUI 用于注册和接收更改通知的 “侦听器” API。
  2. 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 为您提供了很大的灵活性。

两个问题

在设计方面存在两个问题,都与当前可用的特定技术有关:

  1. 事务。 如何界定它们? 考虑到用户在 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 在生成的代码中根本没有任何事务。
  2. 不受欢迎的 AWT Thread。在 AWT Thread 中不执行来自 Business Object 或者 Persistence 的更改,但对 Form 的任何更该必须在其中执行;不幸的是,JSR 295 此时还不自动支持这一操作

我的解决办法是添加两个修饰模式:

  1. Transactional Decorator 确保每一个属性更改(从图表的左侧传入)被 begin TX / commit TX 封装(当然,如果希望事务对其产生影响,我们可能考虑在 Business Object 的左侧放置修饰模式,但这只是小的细节问题)。
  2. 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 对象来说这并不是可行的解决方案,基于以下两个原因:

  1. 在这些层中使用 Swing 特定库不好;
  2. 我不喜欢要求继承结构的解决方案。

此时,我已经借助一些速成工具自动生成所需要的样板代码,但我想再试试 CGLIB 方法,即通过生成修饰模式将简单的 POJO 转换成成熟的 JavaBean。在我做此项工作之前,我仔细地搜索了是否已经存在实施这一方法的内容。

目前的情况是这一工作已经通过测试,但尚未形成产品,因为 CGLIB 与 NetBeans RCP 类加载器存在一些冲突。我希望尽快解决这个问题。还有一些我正在着手处理的其他特定设计问题,最突出的就是 blueMarine 的另一个模块。我试图将该模块应用于本模式中,但由于缺乏特定样例而放弃了。但是,这只是一项初步工作,期望将来对我的工作进行更新。

同时,我希望收到您的一些反馈意见。


备注:关于这项工作,我想知道 JSR-295 包的名称 org.jdesktop.beansbinding 是否有什么不妥之处,因为该技术已经超出了桌面应用程序的范围。或许 org.beans.beansbinding 会更好一些?