文章来之:http://www.ibm.com/developerworks/cn/java/j-jenabean.html?S_TACT=105AGX52&S_CMP=tec-csdn
Taylor Cowan (
[email protected]), 高级软件系统工程师, Travelocity
2008 年 6 月 02 日
资源描述框架(Resource Description Framework,RDF)是万维网联盟(World Wide Web Consortium,W3C)提出的有关在 Web 中链接和表示数据的标准。为 Semantic Web 开发应用程序的 Java™ 开发人员需要实现 RDF 属性与 Java 类型的相互转换。Jenabean 使用 Jena Semantic Web 框架的灵活的 RDF/OWL API 持久化 JavaBeans,这使 Java 开发人员更容易、更熟练地完成编写应用程序的任务。
Java 开发人员是幸运的,因为在 Jena 中他们可以利用一种良好的 RDF 框架(请参阅 参考资料)。Jena 提供了一个编写和读取 RDF 的 API,它可以以多种方式进行保存和持久化。如果您对 Jena 还不熟悉,我强烈建议您在阅读本文之前阅读 Philip McCarthy 撰写的 “Jena 简介”。
Jena 的设计目标是可以良好地处理 RDF 数据模型,正如 JDBC 适合处理关系模型一样。数据库应用程序中编写的大量代码都用来保存 Java 对象,还有一些代码用来从数据库中聚集对象。用 Java 代码编写的语义 Web 应用程序面临一个类似的问题:它们必须实现 Java 对象和 RDF 之间的相互转换。因此,开发人员必须编写大量的代码来消除自身模型(一般为 JavaBeans)和 Jena 的以 RDF 为中心的 API 之间的差异。
RDF 和语义 Web
要查看对语义 Web 概念的优秀介绍 — 包括统一资源标识符(Uniform Resource Identifiers,URI)、RDF 和 OWL Web Ontology Language(OWL)— 请参阅 “Web 的未来是语义的”。
本文展示 Jenabean 的 Java-to-RDF 绑定框架(请参阅 参考资料)如何简化上述过程并减少所需的代码量。您将审视一些 Jena 客户端代码并将其与 Jenabean 的基于 JavaBean 的编程模型对比。首先查看一个简单的例子,我将向您展示如何实现以下操作:
将一个 bean 保存为 RDF
将其属性与特定的 RDF 属性绑定
将其与其他对象关联
再次回读 bean
Jenabean 编程模型
考虑清单 1 中的简单 RDF 示例,为方便阅读,它使用了 N-triple(N3)格式(请参阅 参考资料):
清单 1. RDF 示例(N3 格式)
<http://www.ibm.com/developerworks/xml/library/j-jena/>
a dc:Article ;
dc:creator "Philip McCarthy"^^xsd:string ;
dc:subject "jena, rdf, java, semantic web"^^xsd:string ;
dc:title "Introduction to Jena"^^xsd:string .
清单 1 说明了 “Jena 简介” 这篇文章由 Philip McCarthy 撰写并且主题包括 jena、rdf、java 和语义 web。词汇表是 Dublin Core 元数据分类的一部分(请参阅 参考资料)。要使用 Jena 的原始的 Java API 复制这些 RDF 声明,您可能要执行类似清单 2 的工作:
清单 2. 使用原始的 Jena API 断言 RDF 示例
String NS = "http://purl.org/dc/elements/1.1/";
OntModel m = createModel();
OntClass articleCls = m.createClass(NS +"Article");
Individual i = articleCls.createIndividual(
"http://www.ibm.com/developerworks/xml/library/j-jena/");
Property title = m.getProperty(NS + "title");
Literal l = m.createTypedLiteral("Introduction to Jena");
i.setPropertyValue(title,l);
Property creator = m.getProperty(NS + "creator");
l = m.createTypedLiteral("Philip McCarthy");
i.setPropertyValue(creator,l);
Property subject = m.getProperty(NS + "subject");
l = m.createTypedLiteral("jena, rdf, java, semantic web");
i.setPropertyValue(subject,l);
m.write(System.out, "N3");
清单 2 中每个数值断言需要三行代码:
访问属性
创建典型的字母
断言属性关系
这个代码的优点是使用透明、清晰的方式直接映射到 RDF 概念。这种情况与 JDBC 客户端代码相似,其中 API 直接应用到关系模型。
如果花大量时间研究 Jena,您将会认识到普通的面向对象代码和与 Jena API 交互的客户端代码之间存在差异。您将在 Java 对象设计的方法中进行断言,而不是使用 setPropertyValue 方法设置属性。
清单 3 展示了使用 Jenabean 创建相同的断言:
清单 3. 使用 Jenabean 创建相同的断言
Model m = _
Bean2RDF writer = new Bean2RDF(m);
Article article = new Article("http://www.ibm.com/developerworks/xml/library/j-jena/");
article.setCreator("Philip McCarthy");
article.setTitle("Introduction to Jena");
article.setSubject("jena, rdf, java, semantic web");
writer.save(article);
m.write(System.out, "N3");
我在这里另外包含了 m.write(...,"N3") 行。您可以使用本文附带的代码(请参阅 下载)亲自尝试。
清单 2 和 清单 3 有明显的不同,但 Jena 模型产生了几乎相同的变化。它们都创建了 清单 1 中三元组的一个超集。清单 2 通过调用 m.createClass(...) 声明了一篇新的文章。清单 3 通过创建 Article — new Article(...) 类的一个新实例实现同样的操作。没有使用 Jenabean,每个属性断言要求您从模型访问属性、创建一个字母并通过调用 setPropertyValue(...) 将声明断言到模型。使用 Jenabean 的 Bean2RDF 转换器,您可以仅调用 JavaBean 的 setter 方法。清单 3 产生了对大多数 Java 开发人员来说非常熟悉的代码。
OOP 到 RDF 的绑定
如您所见,Jenabean 的主要优势是填补了 Java 对象和 RDF 之间的差异。这允许您使用与域模型相同的 bean 以熟悉的 OOP 风格编写语义 Web 应用程序。但这并不是说 Java 对象和 RDF 完全相同。Java 对象和 RDF 以不同的方式表示数据。一个好的绑定工具必须解决以下三个问题。
对象到 RDF 的阻抗不匹配
通常开发人员只关心将一个本体(ontology)复制为一组 Java 对象。Java 对象和 OWL 类之间不存在一对一的关联。OWL 允许多重继承,允许许多类分享相同的属性并允许属性之间相互继承。另外,像关系数据库管理系统(RDBMS)一样,对象和 RDF 在一些方面相似而在其他方面不同。这使我们需要在 Java 代码中生成并使用 RDF。许多工具采用代码生成方法,这些方法涉及读取 RDF 模式或 OWL 本体并在 Java 语言中复制类和属性。这类工具不能实现从现有的 JavaBean 类中断言普通的 RDF 属性。RDF 绑定工具能让您将一个 JavaBean 属性与一个 RDF 属性 URI 随意关联(假设它们的类型兼容)。
浅(shallow)对深(deep)
任何绑定工具都需要解决的另一个问题是一个对象图(object graph)能够实现多大程度的持久化。将对象保存在一个密集的、具有大量连接的对象图中将导致持久化整个模型。装载也会出现类似的情形。绑定工具必须阻止将完整的 RDF 模型作为 JavaBeans 装载到内存中,但在必要时允许实现这个特性。
循环
循环关系对于 RDF 和对象模型非常常见。在进行持久化和装载时,绑定工具必须能够检测和处理循环。显然,它应该防止无限循环,而且它应该检测以前装载的对象并通过重用这些对象来节省时间。
Jenabean 解决了这三个问题,同时尽量使该过程保持简单。它不需要代码生成阶段、字节码生成步骤,或者实时的字节码处理库。它仅需要 Jena 和它的库。Jenabean 默认使用一种保守策略来保存对象及其直接属性。开发人员必须表明他们希望实现 “深度” 拷贝,以便完整地保存对象及相关内容。或者使用另一种方法,即指定要保存或装载某个实例的特定集合属性。
为 RDF 创建 JavaBeans
Jenabean 的最重要的注释
@Id 帮助 Jenabean 为每个 JavaBean 实例创建一个惟一的 URI。您应该将它放到返回一个惟一的 int 或 String 的 getter 方法中。
@Namespace 被应用到类级别中。它覆盖默认的行为,允许您定义希望使用的名称空间。其值必须是一个有效的 URI,以 / 或 # 结束。
@RdfProperty 将 JavaBean 属性与任意的 RDF 属性相绑定。您只需要注释 getter 方法并将 RDF 属性 URI 作为参数提供给注释。
如果您熟悉 Hibernate 或其他绑定工具,您也许希望知道在哪里发生这些操作。 Jenabean 使用 Java 注释来声明 bean 如何映射到 RDF。正如其他基于注释的绑定层一样,当您的模型由 Java 对象驱动时,Jenabean 最为有用。当然并不会总出现这种情况,但是如果是这样的话,Jenabean 就可以提供帮助。
最简单的工作示例
Jenabean 提供了许多功能来定制 bean 如何序列化为 RDF,但是如果默认设置符合您的要求,那么就可以开始快速编写和读取 bean。让我们创建一个简单的 JavaBean 示例,使它满足所有必需的要求。正如使用 Java Persistence API (JPA) 或 Hibernate 一样,您需要保证对象有惟一的 ID。Jenabean 需要您将一个单独的注释 — @Id — 添加到至少一个 bean 字段,使用它充当惟一标识符。清单 4 展示了这个简单的 bean:
清单 4. 一个简单的可直接保存的 bean
package example;
import thewebsemantic.Id;
public class Person {
private String email;
@Id
public String getEmail() { return email;}
public void setEmail(String email) { this.email = email;}
}
清单 4 为 Jenabean 提供足够的信息来可靠地保存和装载 Person 类实例。您没有必要扩展任何内容或编写 XML 描述符文件。由于电子邮件地址是惟一的,它是有效的 ID。清单 5 展示如何将 Person 实例保存到 Jena 模型:
清单 5. 使用生成的 RDF 保存 Person 类的实例
Model m = ModelFactory.createOntologyModel();
Bean2RDF writer = new Bean2RDF(m);
Person p = new Person();
p.setEmail("[email protected]");
writer.save(p);
m.write(System.out, "N3");
...
<http://example/Person>
a owl:Class ;
<http://thewebsemantic.com/javaclass>
"example.Person" .
<http://example/Person/[email protected]>
a <http://example/Person> ;
<http://example/email>
"[email protected]"^^xsd:string .
Bean2RDF 是一个将对象作为 RDF 编写的 Jenabean 类。它默认情况下是浅(shallow)模式,这意味着它将保存实例和其单一属性。如果 Person 类还没有添加到模型中,它将断言一个新类作为 owl:Class 的实例。注意在清单 5 中 Jenabean 使用example 包作为一个新的本体类的名称空间。第二个断言是一个注释,指明用于创建个体的 Java 类。Person 实例及电子邮件地址都进行了断言。Jenabean 首先为已保存的实例创建 URI。它还处理电子邮件属性并将其断言为一个 string 字母值。
从 Jena 模型中检索 JavaBeans
Java 类型映射
Jenabean 针对所有原语类型及其包装程序继承了 Jena 在 Java 和 RDF 之间的类型映射默认值。Jenabean 具有对 java.util.Date 的额外支持,它映射到 xsd:dateTime。数组属性被映射到 rdf:Seq,但表示多重性的最自然的方式是使用 java.util.Collection<?> 接口。例如,如果 Person 类有许多 Appointment,您将赋给它一个 java.util.Collection<Appointment> 类型的属性。Jenabean 不支持具体的 java.util.Collection 实现,比如 List 或 ArrayList,因为 RDF 不会保证顺序。当然您也可以具有其他 bean 的属性:可以是一个单一的实例、一个数组或一个集合。
使用 RDF 表示的个体需要一个 URI,然而 Java 开发人员倾向于使用惟一的 ID。Jenabean 通过将声明的 ID 字段附加到名称空间(这种情况下默认来自包和类名)来提供帮助。创建好 URI 后,您可以使用 RDF2Bean 从模型中装载信息:
RDF2Bean reader = new RDF2Bean(m);
Person p2 = reader.load(
Person.class,"[email protected]");
Jenabean 也可以装载所有的 Person 实例:
Collection<Person> people = reader.load(Person.class);
这些是在模型中访问 bean 的最简单方法。Jenabean 还支持到 SPARQL(RDF 的 SPARQL 查询语言)结果的绑定。简言之,Jenabean 至少要求 bean 作者指明哪个字段保存的值对于该类型的所有实例是惟一的。保存了 bean 后,将根据类的包和名称为 bean 的类和属性提供默认的 URI。这允许您开始从 Java 层轻松地快速创建 RDF。
指定名称空间和属性
到目前为止,我已经向您展示了 Jenabean 如何根据 bean 的类路径和名称创建默认的 URI。Jenabean 还支持声明您希望使用的名称空间。您可以使用 @Namespace 注释将 bean 映射到特定的名称空间。作为演示,我将使用一个目前未被使用的名称空间,Jenabean 自己的项目 URL:
@Namespace("http://jenabean.googlecode.com/")
public class Person { _
<http://jenabean.googlecode.com/Person/[email protected]>
a <http://jenabean.googlecode.com/Person> ;
注意,这里为 Person 类及其属性(而不是默认包)提供了新的名称空间,该名称空间与作为 @Namespace 注释的参数提供的名称空间匹配。默认情况下,这个名称空间将会用于类及其属性。
在 RDF 的世界中,可以使用常见的属性;否则,无法开发出语义 Web。通过使用常见的属性,您可以使数据具有更多的语义并使其他人更加熟悉数据。如果一个 spider(Web 爬行变体)遇到我从 Jenabean 项目 URL 名称空间中生成的 RDF 片段,则无法利用这个片段。但您可以使用更常见和熟知的断言对 bean 进行简单地修改。FOAF(Friend of a Friend,FOAF)语言(请参阅参考资料)是一种用于链接各类人员的常见词汇表,它为电子邮件地址提供了一个特殊的属性:foaf:mbox。现在您所需做的全部工作是使用 Person bean 中的 @RdfProperty 注释:
@Id
@RdfProperty("http://xmlns.com/foaf/0.1/mbox")
public String getEmail() { return email;}
<http://xmlns.com/foaf/0.1/mbox>
"[email protected]"^^xsd:string .
现在,email 属性将其自身断言为一个 foaf:mbox,它将被其他对您的数据感兴趣的 RDF 代理理解为一个电子邮件地址。
对象关系
在 OWL 和 RDF 世界中,通过对同一属性的多个断言来表达各种基数的关系。Jenabean 通过使用 java.util.Collection 接口极大简化了这一过程。清单 6 扩展 Person 类来支持朋友关系(使用松散的 foaf:knows 方式):
清单 6. 扩展 Person 以支持朋友关系
public Collection<Person> friends = new
LinkedList<Person>();
@RdfProperty("http://xmlns.com/foaf/0.1/knows")
public Collection<Person> getFriends() { return friends;}
public void setFriends(Collection<Person> friends) { this.friends = friends;}
这没有什么值得惊奇的 — 只是使用类型为 Collection<Person> 的新字段 friends,以及相关的 get 和 set 方法。现在您可以创建一个 Person 和多个 friends,并通过将每个朋友添加到 friends 集合来实现关联。@RdfProperty 注释是可选的,但是如果您希望将其绑定到现有的第三方词汇表,那么它非常重要。注释指定您希望在 Jena 模型中将 “friend” 属性绑定到 foaf:knows RDF 属性。清单 7 显示如何使用传统的 JavaBean 技巧创建朋友关系:
清单 7. 使用生成的 RDF 表示朋友关系
Model m = ModelFactory.createOntologyModel();
Bean2RDF writer = new Bean2RDF(m);
Person p = new Person();
p.setEmail("[email protected]");
Person f1 = new Person();
f1.setEmail("[email protected]");
Person f2 = new Person();
f2.setEmail("[email protected]");
p.getFriends().add(f1);
p.getFriends().add(f2);
writer.save(p); // modifies the Jena model
m.write(System.out, "N3");
...
foaf:knows
jb:[email protected], jb@[email protected] .
通过 Bean2RDF.write(...) 保存了简单的 Person bean 后,模型中包含遵守 FOAF 规范的新数据。
检索有序列表
在 RDF 中,没有定义子节点的顺序,所以不能假设 Jena 会以任何特定的顺序检索朋友列表。如果需要使用有序列表,Jenabean 将把 Java 数组映射到 RDF 序列。作为演示,我将为 Person 类提供一组电话号码,表示为一个 String 数组,如清单 8 所示:
清单 8. 包含生成的 RDF 的示例数组属性
private String[] phoneNumbers;
public String[] getPhoneNumbers() { return phoneNumbers;}
public void setPhoneNumbers(String[] phoneNumbers) {
this.phoneNumbers = phoneNumbers;}
jb:phoneNumbers
[ a rdf:Seq ;
rdf:_1 "321-321-3210"^^xsd:string ;
rdf:_2 "321-321-3211"^^xsd:string
] ;
结束语
Jenabean 帮助您开始使用熟悉的 Jenabean 模型读取和编写 RDF。本文阐述了以下基本原理:如何创建、保存并读取简单的 bean。这只是使用 Java 语言编写语义 Web 应用程序过程中的一小部分。访问 Jenabean 项目的主页了解更多相关知识、提供反馈或者寻求帮助(请参阅 参考资料)。