“云无界、端无边” OGeek 技术峰会 9月17日 南京不见不散! 了解详情
写点什么

集成 Java 内容仓库和 Spring

  • 2008 年 2 月 12 日
  • 本文字数:7683 字

    阅读完需:约 25 分钟

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

在很多情况下,允许第三方访问这些存储数据(随着应用程序的不断扩展,这成为一个典型的需求)是一个漫长而复杂的过程(它们不会在一夜之间完成)。内部存储结构很容易影响 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

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

  • 对节点和属性的读访问。
  • 对属性值的读访问。
  • 输出到 XML/SAX。
  • 支持 XPATH 语法的查询服务。
  1. 级别 2

    级别 2 提供功能:

  • 增加和移除节点和属性。
  • 对属性值的写操作。
  • 从 XML/SAX 输入数据。

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

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

  • 事务 —— 它使仓库有可能与 JMS 或 JDBC 资源一起工作。
  • 版本标定 —— 允许仓库记录节点的不同状态,以备日后检索。规范对于这个主题有相当的篇幅;该特性使得用 JSR-170 作为后端构建一个 CVS 的克隆成为可能。
  • 事件 —— 亦称观察,它允许仓库内发生的任何活动都会被通知给客户端。
  • —— 可以冻结部分树的功能,它可以有效地返回一个只读的子树。

## API 回顾

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

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

这个包还包含了那些组成仓库的单元接口的定义:WorkspaceCredentialsNodePropertyItemNodeProperty的超类)和Valuejavax.jcr.query包负责处理查询,javax.jcr.nodetype包负责定义节点类型。剩余的包负责可选级别的功能,如javax.jcr.versionjavax.jcr.observationjavax.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 还加入了额外的功能(如SessionListenersCustomNode注册),以及一个有趣的捐献来的项目套件,它包括: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,用来统一RepositoryCredentialsWorkspace接口,允许自动注册监听器和自定义名字空间。
  • Spring 声明性事务为那些实现了(可选)事务特性的仓库提供了支持。
  • OpenSessionInView拦截器和过滤器允许每个线程跨不同组件使用同一会话。与JcrTemplate一起,检索、关闭和管理 JCR 会话的工作已经外部化,对于调用者完全透明。

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

配置仓库和 SessionFactory

<bean id="repository" class="org.springmodules.jcr.jackrabbit.RepositoryFactoryBean"><br></br> <!-- normal factory beans params --><br></br> <property name="configuration" value="classpath:jackrabbit-repo.xml"/><br></br> <property name="homeDir" ref="./tmp/repo"/><br></br></bean><br></br>JCR 支持提供RepositoryFactoryBean类配置 Jackrabbit,它需要 JackRabbit 的配置文件和主目录。注意,RepositoryFactoryBean在使用本地文件系统时特别有用;对于服务器环境,仓库可能被注册在 JNDI 中,此时可以使用JndiObjectFactoryBean帮助类(Spring 分发包的一部分)检索它:

<bean id="repository" class="org.springframework.jndi.JndiObjectFactoryBean"><br></br> <property name="jndiName" value="java:comp/env/jcr/myRepository"/><br></br></bean><br></br>或使用 Spring 2.0 的模式名字空间:

<jndi:lookup id="entityManagerFactory" jndi-name="jcr/myRepository"/><br></br>为了简化与 JCR 的工作,模块增加了SessionFactory接口:

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

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

<!-— SessionFactory --><br></br><bean id="jcrSessionFactory" class="org.springmodules.jcr.JcrSessionFactory"><br></br> <property name="repository" ref="repository"/><br></br> <property name="credentials"><br></br> <bean class="javax.jcr.SimpleCredentials"><br></br> <constructor-arg index="0" value="bogus"/><br></br> <!-- create the credentials using a bean factory --><br></br> <constructor-arg index="1"><br></br> <bean factory-bean="password" factory-method="toCharArray"/><br></br> </constructor-arg><br></br> </bean><br></br> </property><br></br></bean><p><!-- create the password to return it as a char[] --></p><br></br><bean id="password" class="java.lang.String"><br></br> <constructor-arg index="0" value="pass"/><br></br></bean><br></br>这个 bean 声明非常简单,唯一需要注意的地方是,密码被提供给SimpleCredential的构造函数:它只接受字符数组,使用 Spring 工厂声明作为一种变通。

JcrTemplate

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

<bean id="jcrTemplate" class="org.springmodules.jcr.JcrTemplate"><br></br>    <property name="sessionFactory" ref="jcrSessionFactory"/><br></br>    <property name="allowCreate" value="true"/><br></br></bean><br></br>模板定义非常简单,类似来自 Spring 框架的其它模板类,如HibernateTemplate

例子

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

public Node importFile(final Node folderNode, final File file, final String mimeType, <br></br> final String encoding) {<p> return (Node) execute(new JcrCallback() {</p><br></br>           <br></br> /**<br></br> * @see org.springmodules.jcr.JcrCallback#doInJcr(javax.jcr.Session)<br></br> */<br></br> public Object doInJcr(Session session) throws <br></br> RepositoryException, IOException {<br></br>  <br></br>       <br></br> JcrConstants jcrConstants = new JcrConstants(session);<br></br>        <br></br> //create the file node - see section 6.7.22.6 of the spec<br></br> Node fileNode = folderNode.addNode(file.getName(), <br></br> jcrConstants.getNT_FILE());<br></br>  <br></br> //create the mandatory child node - jcr:content<br></br> Node resNode = fileNode.addNode(jcrConstants.getJCR_CONTENT(), <br></br> jcrConstants.getNT_RESOURCE());<br></br>   <br></br>     resNode.setProperty(jcrConstants.getJCR_MIMETYPE(), mimeType);<br></br>     resNode.setProperty(jcrConstants.getJCR_ENCODING(), encoding);<br></br> resNode.setProperty(jcrConstants.getJCR_DATA(), new FileInputStream(file));<br></br> Calendar lastModified = Calendar.getInstance();<br></br> lastModified.setTimeInMillis (file.lastModified ());<br></br> resNode.setProperty(jcrConstants.getJCR_LASTMODIFIED(), lastModified);<br></br>             <br></br> session.save();<br></br>             <br></br> return resNode;<br></br> }<br></br> });<br></br>}主要区别是:代码被包装在一个 JCR 模板中,它将我们从不得不使用的 try/catch 语句块(因为IORepository的需检查异常)和处理会话(和事务,如果有的话)清除工作中解放出来。值得提及的是硬编码字符串,如“jcr:data”,是通过JcrConstants工具类解析出来的。它知道名字空间的前缀变化,并提供一种干净的方式处理 JCR 常数。正如你看到的,我只是使例子更加健壮,但是对于实际业务代码影响最小。

事务支持

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

<bean id="jcrTransactionManager" class="org.springmodules.jcr.jackrabbit.LocalTransactionManager"><br></br> <property name="sessionFactory" ref="jcrSessionFactory"/><br></br></bean><br></br>为了声明事务划分,我与上述事务管理器 bean 声明一起使用标准 Spring 类:

<!-- transaction proxy for Jcr services/facades --> <bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"><br></br> <property name="proxyTargetClass"><br></br>        <value>true</value><br></br>     </property>
<property name="transactionManager" ref="jcrTransactionManager"/><br></br>     <property name="transactionAttributes"><br></br>     <props><br></br>        <prop key="save*">PROPAGATION_REQUIRED</prop><br></br>       <prop key="*">PROPAGATION_REQUIRED, readOnly</prop><br></br>     </props><br></br>     </property><br></br></bean> <bean id="jcrService" parent="txProxyTemplate"><br></br>    <property name="target"><br></br>        <bean class="org.springmodules.examples.jcr.JcrService"><br></br>            <property name="template" ref="jcrTemplate"/><br></br>        </bean><br></br>    </property><br></br></bean>如果要求一个 JTA 管理器,一个简单而优雅的解决办法是使用来自 Jackrabbit 捐献包的 jca 连接器。为了使用 jca,你并不需要一个应用服务器,因为你可以用一个可插入的 JCA 容器,如 Jencks 。JCA 容器的配置已经超出本文的范围,但是你可以参考 JCR 模块例子中使用 Jencks 的例子。

TransactionAwareRepository

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

<bean id="transactionRepository" class="org.springmodules.jcr.TransactionAwareRepository"><br></br>     <property name="allowNonTxRepository" value="true"/><br></br>     <property name="targetFactory" ref="jcrSessionFactory"/><br></br></bean>transactionRepository bean 可以被用作一个普通的 JCR 仓库,不关心底层机制或线程绑定会话、事务性或非事务性(如果有事务,关闭会话时要提交事务)。

可选特性支持侦测

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

SessionHolder类被内部不同组件使用,主要被事务管理器用来操作会话,SessionHolderProviderSessionHolderProviderManager处理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.JackRabbitSessionHolderProvider<br></br>缺省,SessionHolderProviderManagerJcrSessionFactory内部使用,因此在工厂启动时,任何客户化实现可以被获取,并与合适的仓库一起使用。但是,通过设置JcrSessionFactory中的SessionHolderProviderManager,可以很容易的切换到一个不同的发现策略。一个可替代的发现服务是ListSessionHolderProviderManager,它接收一组提供者列表,可以方便地使用自定义提供(如测试)。

<bean id="listProviderManager" class="org.springmodules.jcr.support.ListSessionHolderProviderManager"><br></br>     <property name="providers"><br></br>         <list><br></br>            <bean class="org.mycompany.jcr.CustomHolderProvider"/><br></br>            <bean class="org.springmodules.jcr.jackrabbit.support.JackRabbitSessionHolderProvider"/><br></br>            <bean class="org.springmodules.jcr.support.GenericHolderProvider"/><br></br>         </list><br></br>     </property<br></br></bean>
<bean id="jcrSessionFactory" class="org.springmodules.jcr.JcrSessionFactory"><br></br>        ...<br></br>    <property name="sessionHolderProviderManager" ref="listProviderManager"/><br></br></bean>注意,每个仓库一个提供者。如果列表包含多个工作于同一仓库的提供者,顺序将非常重要,因为先匹配的先使用。

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 的看起来一片光明。

查看英文原文: Integrating Java Content Repository and Spring

2008 年 2 月 12 日 23:135611
用户头像

发布了 255 篇内容, 共 50.4 次阅读, 收获喜欢 8 次。

关注

评论

发布
暂无评论
发现更多内容

架构师0期 | 架构师是怎样炼成的?

刁架构

极客大学架构师训练营

架构师训练营 第一周 学习总结

一雄

学习 极客大学架构师训练营 第一周

C02-商业模式与架构设计

卜卜兔

作业1 餐卡系统设计

Geek_2e7dd7

学习总结

Geek_2e7dd7

UML练习1-食堂就餐卡系统设计

一剑

ARTS-week3

王钰淇

ARTS 打卡计划

架构师训练营第一周总结

Cloud.

极客大学架构师训练营

作业一:食堂就餐卡系统设计

Geek_36d3e5

如何成为一个架构师

_MISSYOURLOVE

极客大学架构师训练营

架构师思维

林昱榕

极客大学架构师训练营

ReentrantLock 公平锁和非公平锁源码分析

张sir

Java 多线程 Java 25 周年

食堂就餐卡系统设计

wyzwlj

极客大学架构师训练营

Week 01 命题作业

卧石漾溪

极客大学架构师训练营

食堂就餐卡系统设计

stardust20

架构训练营-食堂就餐卡管理系统

架构师训练营第一周学习总结

jiangnanage

架构设计

食堂就餐卡系统架构设计

Cloud.

架构师训练营第一周学习总结

不谈

重新定义失败

史方远

个人成长 随笔杂谈

虽则悲欢不尽相同

zhoo299

随笔

程序员如何破除「迷茫」

顿晓

学习 程序员 架构 迷茫

架构训练营-第一课总结

第一周总结

芒夏

极客大学架构师训练营

随遇而安的适配器模式 | Spring 中的适配器

大头星

Java spring 面试 设计模式 Java 25 周年

架构设计文档学习总结

jason

平台化服务的基石:隔离与交互策略模型

孤岛旭日

企业架构 用户权限 权限系统

食堂就餐卡系统设计

赵龙

第三季已经起航,送你一份活动手册吧

赵新龙

写作 社群

第01周命题作业-食堂就餐卡系统架构设计

Jaye

极客大学架构师训练营

架构师训练营-第一课学习总结

King

学习 感悟 极客大学架构师训练营

首届腾讯云大数据峰会暨Techo TVP开发者峰会

首届腾讯云大数据峰会暨Techo TVP开发者峰会

集成Java内容仓库和Spring_Java_Costin Leau_InfoQ精选文章