集成Java内容仓库和Spring

保存各种信息对于应用程序来说非常平常,大多数时候它们是保存在关系数据 库中。数据库处理规范数据类型十分在行,但是在处理如图像、文档等二进制数 据时却不是那么得心应手。尽管可以用文件系统作为替代——而且它们还提供了 更好的性能。但它们既没有提供用于搜索信息的查询语言,也没有提供表示关系 或事务的概念。

在很多情况下,允许第三方访问这些存储数据(随着应用程序的不断扩展,这 成为一个典型的需求)是一个漫长而复杂的过程(它们不会在一夜之间完成)。 内部存储结构很容易影响API架构,以及信息检索与遍历的方式。

什么是JSR-170

幸运的是,被称为Java内容仓库(Java Content ReposiTory,JCR)的JSR- 170,试图以独立于具体实现的方式解决这些(以及其它)问题。即,不论底层资 源(如,数据库,本地或虚拟文件系统)是什么,API都将相同。在数据存储之上 ,JCR提供诸如访问粒度控制、版本控制、内容事件、全文检索和过滤等内容服务 。由Day Software领导的JSR-170背后的专家组令人印象深刻,包括内容管理系统 (CMS)提供商Vignette、Hummingbird Ltd.、Stellent和通用Java驱动解决方案 提供商,如BEA Systems、IBM和Oracle。该规范很可能成为内容管理和文档存储 方面事实上的标准。

经过几乎2年半的努力,工作最终于2005年6月完成,在javax.jcr包中,API包 含了大约50个类(主要是接口和异常)。2006年早些时候,发布了初始1.0版本的 参考实现(JackRabbit)。

JSR-170概览

Java内容仓库建立在仓库(除了是“用于安全地保存货物的地点”的通常含义 之外)概念之上,它提供了几个操作数据的特性。仓库使用“树结构”保存信息 ,如下图,树由节点和属性组成。圆圈代表节点,方框代表属性。1个节点有且只 有1个父亲,有任意数目的孩子(子节点)和任意数目的属性。1个属性有且只有 一个父亲(它是节点),它没有子节点,由一个名字和一个或多个值组成。属性 值的类型可以是:布尔(Boolean)、日期(Date)、双精(Double),长整 (Long),字符串(String)或流(Stream)。只有属性可以被用来存储信息, 节点则被用来创建树内部的“路径”。在某种程度上,这棵树类似文件系统的结 构,节点是目录,属性是实际的文件。

仓库的功能被划分为几个“兼容性”级别,每个级别提供一组特定的特性:

级别 1对于所有实现,级别1是必须的,它提供对仓库的读访问,简而言之:

对节点和属性的读访问。

对属性值的读访问。

输出到XML/SAX。

支持XPATH语法的查询服务。

级别 2级别2提供写功能:

增加和移除节点和属性。

对属性值的写操作。

从XML/SAX输入数据。

值得注意的是,JCR的实现并不要求达到级别2或者更高层次。因此与只读仓库 一起工作也是完全符合规范的。

可选级别“可选”级别包含一些高级特性,它并不是读写仓库所必需的,但确 是真正为JSR-170增色的部分。这个级别包括(除了其它之外):

事务—— 它使仓库有可能与JMS或JDBC资源一起工作。

版本标定—— 允许仓库记录节点的不同状态,以备日后检索。规范对于这个 主题有相当的篇幅;该特性使得用JSR-170作为后端构建一个CVS的克隆成为可能 。

事件—— 亦称观察,它允许仓库内发生的任何活动都会被通知给客户端。

锁—— 可以冻结部分树的功能,它可以有效地返回一个只读的子树。

API回顾

使用JSR-170时,建议使用来自javax.jcr包的接口。这样,更换JCR实现时会 容易些,不会有任何代码的变动。

API的核心类是Session,它代表客户端和仓库之间的连接,使用连接活跃其上 的workspace名和所提供的credentials进行定义。Session包含读(级别1)和写 (级别2)方法;使用底层仓库不支持的功能时将抛出异常。

这个包还包含了那些组成仓库的单元接口的定义:Workspace,Credentials, Node,Property,Item(Node和Property的超类)和Value。javax.jcr.query包 负责处理查询,javax.jcr.nodetype包负责定义节点类型。剩余的包负责可选级 别的功能,如javax.jcr.version、javax.jcr.observation、javax.jcr.lock。 一个有趣的包是javax.jcr.util,它包含一个ItemVisiTor的实现,它源自GOF( 四人帮,Gang of Four)撰写的著名的设计模式中的访问者模式(VisiTor- pattern)接口。

JSR-170实现

Google和SourceForge会列出好几页的JSR-170实现,但是它们中的大多数都处 于alpha阶段,没有发布任何版本。以下是一个可以自由下载的项目列表,它们已 经被作者使用过:

Jackrabbit

它是JSR-170的参考实现,Apache基金的一部分,提供级别1,2和可选功能。 在撰写本文时,它已经经过孵化阶段并有一个官方公开的发布版本,该版本被认 为足够稳定,可以被用在产品环境。此外,Jackrabbit也被用来作为Day Software(JSR-170的领导者)的商业产品的基础。除了实现JSR-170中定义的所 有特性,JackRabbit还加入了额外的功能(如SessionListeners或CustomNode注 册),以及一个有趣的捐献来的项目套件,它包括:JCA连接器、taglib、WebDAV 接口、虚拟文件系统和JDBC后端。JackRabbit的许可证是Apache 2.0。

eXo JCR

它是eXo platoform的一部分,包含规范要求的所有强制特性和几个可选特性 。最近一次的版本发布(1.0RC7)是2006年6月22日,基于规范的最终草案2。eXo JCR支持JDBC兼容数据库,如MySQL、DB2或HSQL(它是缺省的)作为后端存储,它 是双许可的(GPL和商用),最终版的发布日期尚未确定。

Jeceira

与Jackrabbit和eXo JCR相比,它是相对较新的项目。它实现了级别1和2的一 些需求,只在写操作时,支持来自可选级别的观察功能。不幸的是,这个项目处 于未完成阶段,在过去的9个月没有新版本发布。然而它被Magnolia(一个流行的 基于java的CMS,与作为JSR-170参考实现的Jackrabbit类似)使用。在最终版发 布时,它计划包含所有级别的功能,发布时间目前尚不确定。Jeceira的许可证是 Apache 2.0,使用HSQL数据库作为它的存储引擎。

JCR模块

Spring Modules的一部分,JCR模块的主要目标是:以一种类似Spring主分发 包中ORM包的方式,简化使用JSR-170 API进行开发。特点如下:

JcrTemplate,允许执行JcrCallback和异常处理(将需检查的JCR异常转换成 不需检查的Spring DAO异常)。这个模板实现了来自JCR Session的绝大部分方法 ,可以简单地作为替换物使用。此外该模板知道线程绑定的会话,这个会话可以 跨几个方法使用,这在使用事务型仓库时非常有用。

ReposiToryFacToryBean,它配置、启动和停止仓库实例。因为JSR-170并没有 说明仓库配置的标准方式,需要注意实现在这个方面的不同。这个支持包含预定 义的用于Jackrabbit和Jeceira的FacToryBean,以及一个可以很容易支持其它仓 库的抽象基类。

SessionFacTory,用来统一ReposiTory,Credentials和Workspace接口,允许 自动注册监听器和自定义名字空间。

Spring声明性事务为那些实现了(可选)事务特性的仓库提供了支持。

OpenSessionInView拦截器和过滤器允许每个线程跨不同组件使用同一会话。 与JcrTemplate一起,检索、关闭和管理JCR会话的工作已经外部化,对于调用者 完全透明。

本文将使用参考实现(Jackrabbit),由于JCR模块使用的是javax.jcr接口, 因此改变实现根本就是一个配置的问题。让我们一步一步地来看看在Jackrabbit 之上如何使用Java内容仓库,以及如何让Spring模块来帮助完成这一工作。

配置仓库和SessionFacTory     

JCR支持提供ReposiToryFacToryBean类配置Jackrabbit,它需要JackRabbit的 配置文件和主目录。注意,ReposiToryFacToryBean在使用本地文件系统时特别有 用;对于服务器环境,仓库可能被注册在JNDI中,此时可以使用 JndiObjectFacToryBean帮助类(Spring分发包的一部分)检索它:

 

或使用Spring 2.0的模式名字空间:

为了简化与JCR的工作,模块增加了SessionFacTory接口:

public interface SessionFacTory {public Session getSession() throws ReposiToryException;public SessionHolder getSessionHolder(Session session);}

SessionFacTory隐藏了实现内部的认证细节,因此一旦配置完成,使用同一证 书的会话可以很容易的被检索出来。为了利用实现的特性(没有涵盖在规范中的 ),这个接口允许检索SessionHolder。它是一个JCR模块特定类,主要被用于事 务和会话管理(通过一种可用于每个JCR实现的缺省、通用实现),但是它不支持 可选特性或自定义特性(如JackrabbitSessionHolder,它支持Jackrabbit的事务 基础结构)。JCR模块提供一种简易、透明的方式来发现SessionHolder实现(这 些我将在以后详细提到),使之很容易地插入对JSR-170其它兼容库的支持。

SessionFacTory的缺省实现是JcrSessionFacTory,它要求一个进行工作的仓 库和证书。

这个bean声明非常简单,唯一需要注意的地方是,密码被提供给 SimpleCredential的构造函数:它只接受字符数组,使用Spring工厂声明作为一 种变通。

JcrTemplate

JcrTemplate是JCR模块的核心类之一,它提供了与JCR会话一起工作的方便方 法,将调用者从必须处理的打开和关闭会话、事务回滚(如果底层仓库提供)、 以及处理其它特性中的异常等工作中解放出来:

     

模板定义非常简单,类似来自Spring框架的其它模板类,如 HibernateTemplate。

例子

既然仓库已经配置了,接下来看看“Spring化”的例子之一,它来自 Jackrabbit的wiki页:

public Node importFile(final Node folderNode, final File file, final String mimeType,   final String encoding) {  return (Node) execute(new JcrCallback() {   /**   * @see org.springmodules.jcr.JcrCallback#doInJcr (javax.jcr.Session)   */   public Object doInJcr(Session session) throws   ReposiToryException, IOException {       JcrConstants jcrConstants = new JcrConstants(session);   //create the file node - see section 6.7.22.6 of the spec   Node fileNode = folderNode.addNode(file.getName(),             jcrConstants.getNT_FILE());   //create the mandaTory child node - jcr:content   Node resNode = fileNode.addNode(jcrConstants.getJCR_CONTENT (),             jcrConstants.getNT_RESOURCE());     resNode.setProperty(jcrConstants.getJCR_MIMETYPE(), mimeType);     resNode.setProperty(jcrConstants.getJCR_ENCODING(), encoding);   resNode.setProperty(jcrConstants.getJCR_DATA(), new FileInputStream(file));   Calendar lastModified = Calendar.getInstance();   lastModified.setTimeInMillis (file.lastModified ());   resNode.setProperty(jcrConstants.getJCR_LASTMODIFIED(), lastModified);   session.save();   return resNode;   }  });}

主要区别是:代码被包装在一个JCR模板中,它将我们从不得不使用的 try/catch语句块(因为IO和ReposiTory的需检查异常)和处理会话(和事务,如 果有的话)清除工作中解放出来。值得提及的是硬编码字符串,如“jcr:data” ,是通过JcrConstants工具类解析出来的。它知道名字空间的前缀变化,并提供 一种干净的方式处理JCR常数。正如你看到的,我只是使例子更加健壮,但是对于 实际业务代码影响最小。

事务支持

使用JCR模块的一个好处就是能将Spring事务基础设施(包括声明性和编程性 )应用于Java内容仓库。JSR 170将事务支持视为可选特性,并没有强制一个标准 的方式来暴露事务钩子,因此每个实现可以选择不同的方法。在本文撰写时,只 有Jackrabbit支持事务(在它的大部分操作中),它通过为每个JcrSession暴露 一个javax.transaction.XAResource做到这一点。JCR模块提供 LocalTransactionManager用于本地事务:

 

为了声明事务划分,我与上述事务管理器bean声明一起使用标准Spring类:

        true                  PROPAGATION_REQUIRED     PROPAGATION_REQUIRED, readOnly                             

如果要求一个JTA管理器,一个简单而优雅的解决办法是使用来自Jackrabbit 捐献包的jca连接器。为了使用jca,你并不需要一个应用服务器,因为你可以用 一个可插入的JCA容器,如Jencks。JCA容器的配置已经超出本文的范围,但是你 可以参考JCR模块例子中使用Jencks的例子。

TransactionAwareReposiTory

对于要求普通JCR代码的应用程序,JCR模块允许用直接使用JCR API的代码, 透明地使用事务驱动会话。 此时,可以使用TransactionAwareReposiTory,它有 一个参数是JcrSessionFacTory。这样,在使用Session.login()(它接收定义在 JcrSessionFacTory中的参数)创建任何新会话时,如果发现有线程绑定的会话, 就将返回它。注意:如果使用事务,JCR会话就是事务性的,否则你必须手动设置 属性allowNonTxReposiTory为true,配置如下,要不然将抛出一个异常:

     

transactionReposiTory bean可以被用作一个普通的JCR仓库,不关心底层机 制或线程绑定会话、事务性或非事务性(如果有事务,关闭会话时要提交事务) 。

可选特性支持侦测

为了最大化代码重用,但仍然允许插入可选特性,如用于不同JCR实现的事务 支持,JCR模块使用SessionHolder接口(前面已经提到),同时还有 SessionHolderProvider和SessionHolderProviderManager接口。用户一般不用与 它们打交道,因为它们是框架内部使用的;但是,它们代表了JCR模块主要的扩展 点。

SessionHolder类被内部不同组件使用,主要被事务管理器用来操作会话, SessionHolderProvider和SessionHolderProviderManager处理sessionHolder创 建的方式以及提供者是如何被个别使用的。缺省将使用 ServiceSessionHolderProviderManager,它利用JDK 1.3 Service Provider的自 动发现特性。管理器将在类路径中搜索META- INF/services/org.springmodules.jcr.SessionHolderProvider条目,它包含了 SessionHolderProvider实现的完整限定名。Jackrabbit支持就是这样配置的, JCR模块的分发包中包含一个META- INF/services/org.springmodules.jcr.SessionHolderProvider(译注:原文有 误,没有给出后面的文件名)文件,它只有一行:

org.springmodules.jcr.jackrabbit.support.JackRabbitSessionHold erProvider

缺省,SessionHolderProviderManager被JcrSessionFacTory内部使用,因此 在工厂启动时,任何客户化实现可以被获取,并与合适的仓库一起使用。但是, 通过设置JcrSessionFacTory中的SessionHolderProviderManager,可以很容易的 切换到一个不同的发现策略。一个可替代的发现服务是 ListSessionHolderProviderManager,它接收一组提供者列表,可以方便地使用 自定义提供(如测试)。

                                     </property     ...  

注意,每个仓库一个提供者。如果列表包含多个工作于同一仓库的提供者,顺 序将非常重要,因为先匹配的先使用。

Java内容仓库的未来

尽管JSR-170已经于2005年5月完成,Java内容仓库的工作并没有终止。JSR- 283,官方的后继者,将聚焦于功能增强,如联邦,remoting,客户端/服务器协 议映射和扩展内容模型的能力。同时还存在着一些JSR之外的想法和项目:绑定/ 映射框架,它可以将java类转换为一个JCR树,反之亦然(类似ORM,后端用Java 内容仓库替代数据库),建构于JCR之上的WebDAV服务器(参见Jackrabbit的捐献 包),以及其他。已经出现了用于不同产品的JSR-170连接器,如Alfresco、BEA Portal Server和IBM Domino。

至于JCR模块,路线图包括用于几个实现的Acegi安全集成,支持Spring 2.0名 字空间模式(它将减少XML的配置)和与其它JCR实现集成。很显然,JCR的看起来 一片光明。

爱的力量大到可以使人忘记一切,却又小到连一粒嫉妒的沙石也不能容纳

集成Java内容仓库和Spring

相关文章:

你感兴趣的文章:

标签云: