`

Java SE 6 — 更好的 JPA、更好的 JAXB 和更好的批注处理

 
阅读更多
作者:Dustin Marx

用整合了批注处理和 JAXB 2.0 的 Java SE 6 来改善基于 JPA 的应用程序部署体验。

2007 年 9 月发布

最近发布的 Java SE (标准版) 6 为 Java 编程语言和平台的某些部分带来了更大的便利和可用性。具体来说,Java SE 6 使 Java 开发人员可以更方便地应用 JAXB(用于 XML 绑定的 Java 体系结构)和批注处理来进行开发工作,包括 Java 持久性 API (JPA)。

本文将指导您利用 Java SE 6 对 JAXB 和批注处理的支持来简化基于 JPA 的应用程序的部署,特别是针对不熟悉底层 Java 代码而要部署应用程序的用户。在此过程中,我们还将展示一些 Java SE 6 特性。

Java 6 合众为一

Java 6 不仅推出了新工具和特性,还将改进的特性也纳入了其标准工具集中。下表展示了 Java 6 如何纳入根据以前使用这些技术的实际经验而完善的特性。表中包括 Java Community Process (JCP) 中管理 J2SE 5 和 Java SE 6 所依据的 Java 规范 (JSR),以及管理表中列出的其他技术所依据的 JSR。

特性 J2SE 5.0(“Tiger”)JSR 176 Java SE 6(“Mustang”)JSR 270
JAXB 参考实现 通过 JWSDP(Java Web 服务开发包)分别获得 JSR 222(“用于 XML 绑定的 Java 体系结构 2.0”)
批注处理

JSR 175(“用于 Java 编程语言的元数据工具”)

“apt”工具,用于处理这些新批注
JSR 269(“可插拔批注处理 API”)
JPA JSR 220(企业 JavaBeans 3.0)*

在 JAXB 和批注处理两种情形中,分别用于这些概念的工具已经集成到标准 Java SE 6 工具集中。JAXB 参考实现 (RI) 先前作为 Java Web 服务开发包 (JWSDP) 的一部分下载,独立于 J2SE 下载。类似地,在 Java SE 6 之前,用非常复杂的 apt 工具(批注处理工具)进行批注处理,这一工具独立于 Java 编译器。现在,Java SE 6 本身提供了 JAXB 参考实现(包括其 xjc 编译器),且 javac Java 编译器提供了一个易于使用且更精巧的批注处理框架。

尽管 JPA 是针对改进的企业 JavaBeans (EJB) 3.0 的 Java EE(企业版)规范的产物,但 Java 持久性 API 可以用于 Java 的 SE 和 EE 版本。在 Java SE 6 之前,JPA 没有正式引入 Java SE,但 JPA 规范将 Java SE 支持作为所有 JPA 提供方的要求。此外,传闻 Java 7 将正式纳入 JSR 220 持久性机制。

JPA、批注处理和 JAXB 如何一起工作

JPA 规范允许开发人员将 Java 类批注为实体,这些实体可以保存到一个数据存储库中,在实际的 Java 源代码中提供其他元数据信息。JPA 同样允许开发人员和应用程序部署人员在外部 XML 文件中指定同样的对象关系映射数据。开发人员和部署人员可以选择在特定情况中使用 XML 文件覆盖源代码内的批注或使用 XML 映射完全覆盖任意源代码批注。

开发人员喜欢源代码内的批注,因为元数据与它所描述的代码位于同一位置。在较大的项目中,应用程序部署团队与开发团队可能不会总是同一组人员,在这 种情况中,使用外部描述的对象关系映射元数据覆盖源代码内的批注有优势。选择外部定义的对象关系映射数据而不是源代码内批注的其他原因包括:风格偏好(普 遍选择外部配置而不是代码内配置)、经常更改批注值,以及更改配置而不需要重新编译源代码。

因为开发人员普遍喜欢源代码内批注,但在外部表示对象关系元数据更有优势,如果有一个能将源代码内的批注转化为外部 XML 格式的工具,这将非常有用。JPA 规范描述了源代码内批注和表示这一元数据的外部 XML 格式,从而明确描述了这一元数据转换的源格式和目标格式。

要将源码批注转换为 XML,必须读取批注及其值。这就是 Java 6 内置的批注处理支持的用武之地。读取批注及其值后,需以 JPA 规范中 XML 模式中规定的 XML 格式输出。JAXB 处理这一工作。JAXB 参考实现的 xjc 编译器将创建 Java 类,该类将输出对于 JPA 的 XML 模式有效的正确 XML。图 1 简单说明了工具如何使用批注处理和 JAXB 参考实现将源代码内的 Java 批注转换为描述同一 JPA 映射元数据的 XML 数据。

图 1
图 1. 本文所提出的使用 Java 6 SE 的 JPA 批注处理工具

JPA 规范允许 XML 元数据选择性地覆盖源代码内批注或完全覆盖所有批注。XML 文件中指定了覆盖方法,而不是在源代码中。

Java 持久性 API 补充简介

这里我们有必要对 JPA 进行一些补充介绍,以说明所提出的批注处理器的需求,并解释所涉及的 JPA 批注。(要更全面地了解 JPA,请参见 OTN 上提供的众多优秀 JPA 资源。)JPA 参考实现由 Oracle Corporation 作为 TopLink Essentials 单独提供或与 GlassFish 捆绑,GlassFish 是 Java EE 应用服务器的开放源代码参考实现。GlassFish 持久性页面提供了更多 JPA 详细信息。

Java 持久性 API 是作为 EJB 3.0 规范的一部分推出的,用于更简单的 EJB 实体开发。针对 EJB 3.0 推出 JPA 后,JPA 的一个主要优势是可用于 Java EE 或 Java SE 环境中。利用 JPA,我们最终实现了 Java SE 和 Java EE 通用的、基于标准的数据库访问方法。

对于 EJB 3.0,EJB 现在是普通的 Java 类,没有可实现的特定接口或可扩展的母类。只需按此方法对类进行批注,几乎任何现有的 Java 类都可以转换为实体。这可通过直接“修饰”代码的代码内批注或使用外部 XML 配置文件或结合两者来完成。后一种使用外部 XML 配置文件的方法更容易让人想起带 XML 格式部署描述符的早期版本的 EJB。

JPA 规范允许通过代码内批注和/或 XML 配置文件来批注或修饰 JPA 类。本文提出并部分构建了一个批注处理器,用于将代码内 JPA 批注转换为对应的 XML 表示。这一示例不仅演示了代码内批注如何用于开发外部配置文件,更重要的是提供一个真实示例来展现 Java 6 的功能和灵活性。它特别强调了 Java 6 内置的对批注处理和与 JAXB 2.0 实施的 Java 到 XML 绑定的支持。

Java 中的批注处理

Java 5 才开始引入批注,但此前已在 Java SE 、Java EE 和用于 Java SE 和 Java EE 的许多其他第三方产品和框架中广泛应用。写批注和使用批注作为 Java 元数据机制的原因众多,无穷无尽。

Java 5 中的批注处理:引入批注

J2SE 5 中引入了批注,作为一种在 Java 语言源代码中直接指定元数据的标准方法。Java 批注背后的概念和语法类似于 Javadoc 风格的标记和 XDoclet 风格的标记。J2SE 5 提供了一个单独的工具用于处理这些新推出的批注,其名称为批注处理工具 (apt)。

2004 年,OTN 发布了一系列 Jason Hunter 撰写的关于在 Jave 5 中编写、使用和处理批注的文章(“充分利用 Java 的元数据”)。此系列的第一篇文章介绍了 Java 批注,第二篇文章介绍了如何创建自定义批注。第三篇文章介绍了在 Java 5 中使用 apt 的批注处理。

结合 J2SE 5 批注处理参考实现使用的四个主要程序包都是 com.sun.mirror 下的子程序包。它们是 apt、declaration、type 和 util。此处值得强调的是,J2SE 5 中的 apt 工具使用以“com.sun.mirror”开头的程序包。

Java 6 中的批注处理:内置到 javac

Java SE 6 对批注处理作出了几处改进。最显著的改进之一中将批注处理纳入为 javac 编译器的一个组成功能。要处理批注的开发人员不再需要运行单独的工具(如 apt),可以转而直接使用 javac 编译器。

只需对 J2SE 5 的 javac 和 Java SE 6 的 javac 运行“javac -help”,就能轻松找出 Java 6 对 javac 的改动。javac 的“help”文档所增加的内容中最值得注意的就是有关批注处理的选项。Java SE 6 中的批注处理新选项包括下表中简要介绍的选项。

Java 6 javac 批注处理选项 说明
-processor 用于明确确定批注处理应当在其上运行的类。这将是我们着重讨论的方法,尽管在处理 Java 代码中的批注时可以改为使用自动发现。
-proc 选项标志,允许编译代码的用户指定在正常的 Java 源代码编译时是否不应执行批注处理或在进行批注处理时不进行正常的编译。
-processorpath 批注 javfac 处理器应搜索带要处理的批注的类的路径。
-A<key>[=value] 从 javac 命令向批注处理器传递选项的选项。值是可选的。
-XprintRounds 非标准选项,提供有关批注处理器处理轮次的详细信息。这包括有用的信息,如批注处理器处理的输入文件,和每一轮所找到的和可能要处理的所有批注。
-XprintProcessorInfo 提供批注处理器要处理的批注的列表

上面最后列出的两个选项是非标准的(注意它们以“-X”开头),但在使用 Sun 提供的 javac 的批注处理中非常有用。javac classpath 选项(-cp 或 -classpath)也用于批注处理。

有关 Java 6 javac 编译器的其他详细信息可以在这里找到。注意前一个 URL 中的“windows”,可以用“solaris”替换,用于查看与 Solaris 相关的 javac 文档。在上面的 URL 末尾添加“#processing”可指示浏览器直接转至有关批注处理的 javac 内容。

Java SE 6 批注处理中使用的主要程序包为 javax.annotation.processing 和 javax.lang.model(和子程序包)。javax.tools 程序包还提供了几个在批注处理中有用的类。

javax.annotation.processing 程序包是 Java SE 6 的新增内容,对编写批注处理器非常有用。图 2 显示了简化的类图,演示了该程序包中几个重要接口和类的关系。

图 2
图 2. 主要批注处理接口和类

处理器接口(avax.annotation.processing 程序包)是在批注处理中使用的接口。较之直接实施该接口,我们通常编写批注处理器来扩展 AbstractProcessor 类(同一 javax.annotation.processing 程序包)。process 方法是处理器接口中要清楚说明的最重要的方法,由扩展 AbstractProcessor 的具体类实现。批注处理器通过这一方法提供用于处理的批注。该方法还向自定义处理器提供有关当前轮次的处理的信息,扩展实施处理器接口(可能扩展 AbstractProcessor)。

图 2 中显示的类为 marx.apt.jpa 程序包的一部分,是专为本文开发的类。这些类部分实现一个批注处理器,该处理器主要用于编写一个基于代码内 JPA 批注的对象关系映射 XML 描述符文件。

下一个代码清单(清单 1)说明了为处理器接口实现定义 process 方法的过程,这一处理器接口实现的目的是专门处理 JPA 相关的批注。除说明 process 方法外,还展示了一些包含类结构。

清单 1

/**
* Creates orm.xml file based on annotations in JPA-based code.
*/
@SupportedAnnotationTypes("javax.persistence.*")
@SupportedSourceVersion(RELEASE_6)
public class AnnotationEntityProcessor extends AbstractProcessor
{
// . . . other code . . .

/**
* Process JPA-specific annotations in Java entity classes.
*
* @param aAnnotations Matching annotations to be processed.
* @param aRoundEnvironment Annotation processing round environment.
* @return
*/
@Override
public boolean process( Set<? extends TypeElement> aAnnotations,
RoundEnvironment aRoundEnvironment )
{
processingEnv.getMessager().printMessage(
Diagnostic.Kind.NOTE,
"Entered AnnotationEntityProcessor.process ...");

Set<? extends Element> elements = aRoundEnvironment.getRootElements();

for (Element element: elements)
{
handleRootElementAnnotationMirrors(element);
}

if (aRoundEnvironment.processingOver())
{
processingEnv.getMessager().printMessage(
Diagnostic.Kind.NOTE,
"EntityProcessor processing completed." );

preparePersistenceUnitMetadata();
prepareFixedEntityMappings();

writeMappingXmlFile();
}

return true;
}

// . . . other code . . .
}

尽管 handleElementAnnotationMirrors(Element) 方法隐藏了大部分的实际批注处理,该代码清单中仍对其中几项进行了说明。代码清单中显示这个类确实扩展了 AbstractProcessor 并且覆盖了 process 方法。

经过有趣的转变后,该批注处理类拥有了自己的一系列批注。 @SupportedAnnotationTypes("javax.persistence.*") 和 @SupportedSourceVersion(RELEASE_6) 批注提供了关于 AnnotationEntityProcessor 类的元数据信息,通常用于修饰用户可能编写的任何批注处理类。在本示例中,第一个批注说明该批注处理器处理所有 JPA 相关的批注。javax.persistence.* 将批注处理器限于 JPA 相关批注,因为该程序包中定义了所有 JPA 相关批注,且仅有 JPA 相关批注。第二个批注,顾名思义,指定了所处理源代码的受支持的 Java 版本(本例中为 Java SE 6)。

上面清单中修饰处理器代码的第三个批注是 @Override 批注。这是 J2SE 5 中支持的一个内置批注,用于确保某个类正确覆盖了其父类的方法。在这种情况下,它有助于确保 AnnotationEntityProcessor 的 process 方法实现确实覆盖了 AbstractProcessor 的 process 方法。

此外,AbstractProcessor 类还提供了所有派生类到 ProcessingEnvironment(同一程序包)的访问。ProcessingEnvironment 是批注处理器到几个有用项的钩子,这些项包括批注处理的定制文件处理程序 (Filer)、批注处理的定制消息程序 (Messager) 以及到提供批注处理实用程序的 Elements 接口(javax.lang.model.util 程序包)的访问。上面的代码清单演示了使用 AbstractProcessor 提供的 processEnv 域访问 Messager(getMessager 方法)以记录信息(printMessage 方法)。

清单 1 中的代码同样使用传入的有关轮次的环境的信息以访问该轮次(getRootElements() 方法)的根元素。在这种情况下,根元素将是正在处理的 Java 类实体。

要处理的 JPA 批注

我们批注处理器要处理的批注是将 Java 类修饰为实体的批注以及提供关于这些实体类的对象关系映射元数据的批注。OTN 提供了这些 Java 持久性 API 批注的完整列表, 并提供了每个 JPA 批注的有用的详细信息。当然,JPA 规范(EJB 3.0 规范从核心 EJB 3.0 规范分出来,专注于 JPA)详细说明了第 8 章(“元数据批注”)和第 9 章(“用于对象/关系映射的元数据”)中预期的批注。最后,Javadoc 创建的这些批注(接口)的程序包的 API 文档提供了在每个批注接口的描述注释中使用众多 JPA 批注的示例。这里提供了 Javadoc 文档。

清单 1 中的代码包括它自己的批注 @SupportedAnnotationTypes("javax.persistence.*"),指示这一特定的批注处理器支持那些批注。尽管我能 将每个 JPA 批注表达为结合 @SupportedAnnotationTypes 批注单独处理,但使用星号作为通配符更简单。

最 重要的 JPA 批注之一是 @Entity,该批注用于将普通的 Java 类修饰为可持久的实体。以下代码片断(表 2)说明了处理用这一属性将类标记为实体的 Java 输入源文件的方法。这是清单 1 中调用的 handleRootElementAnnotationMirrors 方法的部分代码清单。

清单 2

/**
* Process the provided Element for its JPA-related annotations.
* It is expected that the Element provided will be one of the "root elements"
* encountered when JPA classes are processed by the annotation processor.
* This will ensure that annotations such as @Entity and @NamedNativeQuery
* will be at this level.
*
* @param aElement A "root" element (JPA-decorated class).
*/
private void handleRootElementAnnotationMirrors(Element aElement)
{
processingEnv.getMessager().printMessage(
Diagnostic.Kind.NOTE,
"Entered handleElementAnnotationMirrors("
+ aElement.getSimpleName() + ") method..." );
final String simpleName = aElement.getSimpleName().toString();
List<? extends AnnotationMirror> annotationMirrors =
aElement.getAnnotationMirrors();
marx.jpa.persistence.jaxb.Entity entity = this.of.createEntity();

for (AnnotationMirror mirror: annotationMirrors)
{
final String annotationType = mirror.getAnnotationType().toString();

if ( annotationType.equals(javax.persistence.Entity.class.getName()) )
{
populateEntity(entity, mirror, simpleName);
furtherPopulateEntity( entity, aElement );
}
else if ( annotationType.equals(
javax.persistence.NamedNativeQueries.class.getName()) )
{
createNativeNamedQuery(mirror);
}
else if ( annotationType.equals(
javax.persistence.DiscriminatorColumn.class.getName()) )
{
addDiscriminatorColumnToEntity(entity, mirror);
}
else if ( annotationType.equals(
javax.persistence.DiscriminatorValue.class.getName()) )
{
addDiscriminatorValueToEntity(entity, mirror);
}
else if ( annotationType.equals(
javax.persistence.Inheritance.class.getName()) )
{
addInheritanceToEntity(entity, mirror);
}
else if ( annotationType.equals(
javax.persistence.SequenceGenerator.class.getName()) )
{
addSequenceGeneratorToEntity(entity, mirror);
}
else if ( annotationType.equals(
javax.persistence.PersistenceContext.class.getName())
|| annotationType.equals(
javax.persistence.PersistenceContexts.class.getName())
|| annotationType.equals(
javax.persistence.PersistenceUnit.class.getName())
|| annotationType.equals(
javax.persistence.PersistenceUnits.class.getName()) )
{
processingEnv.getMessager().printMessage(
Diagnostic.Kind.MANDATORY_WARNING,
"AnnotationType " + annotationType +
" should map to the persistence.xml file instead of orm.xml.");
}
else // Flag any JPA annotations available but not handled
{
processingEnv.getMessager().printMessage(
Diagnostic.Kind.MANDATORY_WARNING,
"AnnotationType " + annotationType + " not handled currently." );
}
} // end of for loop over annotationMirrors

// An @Entity annotation was encountered and is ready to be added to
// the JAXB EntityMappings element as a sub-element.
if ( (entity.getName() != null) && (!entity.getName().isEmpty()) )
{
this.em.getEntity().add(entity);
}
}

清单 2 展示 AnnotationMirror 接口的强大功能,该接口的 Javadoc 注释将其描述为特定批注的表示。这段代码从 AnnotationMirror 获取批注类型并适当地处理不同的批注类型。

清单中还出现了一些与 JAXB 相关的代码(与称为“entity”的本地实体变量相关的代码),稍后我将详述 JAXB 的内容。这里有个有趣的边注为 isEmpty() 方法,Java 6 中将该方法加入 Java 的字符串类,在本清单的列表的末尾使用了该方法。这个新方法消除了检查长度大于零的字符串或空字符串的需求。

在清单 2 中,最后的“else if”语句检查根元素上与基于 JPA 的应用程序的 persistence.xml 文件关联的批注。因为这些批注要写入 persistence.xml 文件而非 orm.xml(称为对象关系映射文件),这个特定处理器不需要对它们做任何事情。我们可以使用 @SupportedAnnotationTypes 处理器级别批注在 javax.persistence 中仅说明那些我们要处理的批注(对应于 orm.xml 文件中项)来避免我们的批注过程使用所有这一切,但那将是一个很长的注释列表。仅仅指定 javax.persistence.* 以获得所有与 JPA 相关的批注,用于处理和筛选这些不应用于 orm.xml 文件的批注中相对小的一部分,这样更为简单。

清单 2 中最后的“else”块用于简化找出这一工具可能能够处理但出于某些原因不处理的批注的过程。如果代码执行到了这一步,这意味着处理器类上的 @SupportedAnnotationTypes 批注公布该处理器支持该特定的批注,但并未编写 handleRootElementAnnotationMirrors 方法来等待和处理该批注。如果我们尚未编写代码来处理该批注或者将来的 JAP 版本中增加了一个新批注,可能出现这种情况。

清单 2 中的 handleRootElementAnnotationMirrors 方法调用了几个其他方法来构建 JAXB 对象,用于输出 JPA 对象关系映射文件。这里我们将详细讨论其中一个小示例,但大部分示例遵循相同的常规样式,提取源代码内批注信息,然后将该信息存储在 JAXB 对象中,稍后将使用这些 JAXB 对象写 ORM XML 文件。

AnnotationMirror 的威力

清单 2 中代码调用的方法之一是 populateEntity 方法。清单 3 中列出了该方法的代码。

清单 3

/**
* Accept an Entity object that corresponds to an Entity XML element and
* populate the given Entity element with its name as acquired from the
* in-source @Entity annotation or use entity class' name as default name if
* it is not explicitly specified in the annotation.
*
* @param aEntity XML Entity element for which entity name should be supplied.
* @param aMirror In-code Entity's annotation mirror.
* @param aSimpleName Name of class holding @Entity annotation.
*/
private void populateEntity( marx.jpa.persistence.jaxb.Entity aEntity,
final AnnotationMirror aMirror,
final String aSimpleName )
{
processingEnv.getMessager().printMessage(
Diagnostic.Kind.NOTE,
"Entered createEntity(Entity,AnnotationMirror,String)..." );

try
{
Map<? extends ExecutableElement, ? extends AnnotationValue> mirrorMap =
aMirror.getElementValues();

String entityName = aSimpleName; // Use element's name by default

for (Map.Entry mirrorEntry : mirrorMap.entrySet())
{
String mirrorKey = mirrorEntry.getKey().toString();

// The name() attribute of the Entity annotation will only be
// available if it was explicitly set when that annotation was used.
// If it was not explicitly set, this attribute will not be available.
if ( mirrorKey.equals(ANNOTATION_KEY_NAME) )
{
// The string-formatted entity name includes double quotes
// that must be removed before storing names in XML.
entityName = trimDoubleQuotes(mirrorEntry.getValue().toString());
}
}

aEntity.setName(entityName);
}
catch (Exception ex)
{
processingEnv.getMessager().printMessage(
Diagnostic.Kind.ERROR,
"createEntity: " + ex.getMessage() );
}
}

清单 3 中的 populateEntity 方法接受一个 JAXB 生成的类,最终用于在对象关系 XML 映射文件中输出 Entity 元素。该代码清单中范围全面,因为处理器类还访问 Entity 批注接口,即 javax.persistence.Entity。将 JAXB 生成的 Entity 类纳入其范围可以方便地区分 JAXB 生成的用于写 Entity 元素的类和 JPA Entity 批注。

清 单 3 还展示了在处理用于构建 orm.xml 文件的 JPA 批注中多次重复使用的样式。传入 populateEntity 的批注镜像通过 getElementValues() 方法将其元素作为 Map<? extends ExecutableElement, extends AnnotationValue> 提供。常规批注“? extends ExecutableElement”指明 Map 的“key”部分满足 ExecutableElement 接口。此外,ExecutableElement 还用于代表批注类型元素。常规批注“? extends AnnotationValue”指明返回的 Map 的“value”部分表示一个批注的值。因此 AnnotationMirror.getElementValues() 方法用于返回注释元素和该 Map 中各自的注释值。示例 JPA 注释处理器代码中都使用这项技术来获得批注和其值,以便写入到 XML 映射文件中。

使用 Java SE 6 的强大批注建模(前面的代码清单中已经进行部分展示)使得源代码中的所有 JPA 批注都可用于我们的处理器。接下来,我们将使用 JAXB 将从源代码内批注检索到的所有数据写成一个 XML 映射文件。

有关批注处理的其他信息

有几个很好的关于使用 J2SE 5 和 apt 工具的批注处理的参考。正如前面提到的,OTN 发布了一系列标题为“充分利用 Java 的元数据”的文章,第三部分(“高级处理”)主要讨论使用使用 apt 的 Java 5 批注处理。

Java SE 6 的基于 Javadoc 的 API 文档中包括了关于批注处理中使用的重要的类的文档,它提供了一些使用 Java SE 6 的批注处理的最佳文档。尤其是,Javadoc 生成的针对处理器(在 javax.annotation.processing 程序包中)接口的 API 文档在主要部分中包含了一些非常有用的文本,用于描述接口本身和批注处理如何在 Java SE 6 中工作。

最后,Java SE 6 分发包中的 JDK 目录下的 sample/javac/processing/src 目录中有一个批注处理器类示例。这个特定的批注处理器提供了一个在处理器中应用 Visitor 设计模式以及使用 ElementScanner6 类的示例。

Java 6 与用于 XML 绑定的 Java 体系结构 (JAXB)

尽管 JAXB 已经出现很多年了,但 Java SE 6 是第一个将 JAXB 实现整合到其标准配置中的 Java 版本。起初,Java Web 服务开发包 (JWSDP) 是 JAXB 参考实现的主要来源。之后,JAXB RI(参考实现)就可以独立下载了。现在,可以直接从 Java SE 6 下载中获得 JAXB RI,无需再下载 JAXB。

判断下载的 Java SE 6 中是否包含 JAXB 2.0 参考实现的最简单方法是用 -version 选项运行 XJC 编译器。可以将 xjc 编译器所在目录 (<JDK_1.6.x_HOME>/bin) 放入 Path 变量中,或切换到该目录,然后键入命令:xjc -version。在我的计算机上,我看到以下版本消息:

xjc version "JAXB 2.0 in JDK 1.6" 
JavaTM Architecture for XML Binding(JAXB) Reference Implementation, (build JAXB 2.0 in JDK 1.6)

如上述输出消息所示,JAXB 2.0 参考实现 (RI) 与从 Sun Microsystems 的网站下载的 JDK 1.6 明确关联。

在 Java SE 发行版本中提供 JAXB 实现有几个好处。在 Java SE 6 推出之前,需要经过几个步骤才能获得和正确安装 JAXB 参考实现来供 Java 应用程序使用。先将 JAXB 参考实现与 Java SE 发行版本分别下载,然后将其解压缩,再正确设置 JAXB_HOME 环境变量。而且,所有使用 JAXB 生成的类以及运行时绑定 XML 的 Java 应用程序在编译和运行时都需要包含 JAXB 库。Java SE 6 就不再需要这些步骤了,这是因为 Java SE 6 标准安装本身就包含 JAXB 参考实现。

请注意,更新版本的 JAXB RI 或 JAXB 规范的其他实现仍需在 Java SE 6 之外单独下载。例如,在本文撰写时,只有 JAXB RI 2.0 是包含在 Java 6 SE 中的,JAXB RI 2.1.4 则需要单独下载。此外,除了参考实现外,还有其他 JAXB 实现。这些版本包括 TopLink 的 JAXB 实现(TopLink 11g 预览版包括一个符合 JAXB 2 的实现)和 Apache 的 JaxMe 2

JAXB 2.0 的优点

因为 Java SE 6 包含的是 JAXB 2.0 而非老版本,所以这一新版本可以为我们提供更多好处。下面简要介绍一下 JAXB 2.0 比 JAXB 1.0 优越的地方,然后介绍如何应用 JAXB 输出由我们处理的源代码内批注生成的 XML 文件。

JAXB 2.0 参考实现除了与 Java 6 集成外,JAXB 2.0 参考实现相比于 JAXB 1.0 参考实现最大的优势在于 JAXB 生成的类结构要简单得多。在 JAXB 2.0 RI 之前,JAXB xjc 编译器会为要建模的每个 XML 元素创建许多不同的接口类和实现类。JAXB 2.0 RI 通过用“值类”替代接口和实现显著减少了所生成类的数量。

JAXB 2.0 中没有 bgm.ser 文件让您劳心费神;每一个根据 XML 模式文件建模的结构都只有一个类。除了与 XML 模式结构对应的 Java 类外,JAXB 2.0 RI 另外生成的类有 ObjectFactory 类(也是 JAXB 1.0 的一部分)和 package-info.java。如果不需要什么特别的映射或定制,则 JAXB 2.0 参考实现不需要 jaxb.properties 文件,所用的实现就是参考实现。

JAXB 2.0 与 JAXB 1.0 相比另一个明显的区别是 JAXB 2.0 引入了 JAXB 特有的批注。由于 JAXB 1.0 先于 J2SE 5 推出,因此这个 JAXB 的较早版本不支持批注也就不足为奇了。新引入的批注也是导致 JAXB 2.0 生成如此少的类(或更整洁)的部分原因。JAXB 2.0 中的批注支持还使得对所生成的 Java 类的定制比使用外部绑定的配置文件方法或为 JAXB 更改源 XML 模式文件更简单。对我们来说,由于我们不需要定制针对 JPA XML 模式生成的类,因此我们不需要使用这些定制方法。

与定制 Java 类 JAXB 生成相关,我们使用 JAXB 2.0 的特殊方法的好处之一就是我们不需要为使用 orm_1_0.xsd 模式而做什么特殊处理。如果我们只能使用 JAXB 1.0 RI,那我们就不得不使用一个定制的绑定文件来告诉 xjc 编译器如何处理我们模式中命名为“class”但与 Java 的 Object.getClass 方法冲突的属性。

JAXB 2.0 RI 自动将生成的用于处理“class”属性的方法命名为诸如“clazz”之类的名称,这样 get/set 方法就不会与 Object 的同名方法相冲突了。JAXB 2.0 RI 生成的类用 @XmlAttribute(name="class", true) 批注这些“clazz”属性,从而将其映射到模式中称为“class”的属性。因此,JAXB 2.0 RI 不仅简化了 Java 对象与现有 XML 模式的绑定的定制,而且它的 xjc 编译器还在需要定制的时候自动绑定。而在使用 JAXB 1.0 RI 时,我们就不得不自己定制 Java 绑定,而且还得使用一个定制文件(或更改源 XML 模式),而非在生成的代码上添加批注。

JAXB 2.0 的最后一个好处是它支持从 Java 类生成 XML 模式。在 JAXB 2.0 之前,JAXB 只是用于从源 XML 模式或类似的 XML 定义文档(如 DTD 和 RelaxNG)生成 Java 类。但对于 JAXB 2.0,自动生成可在相反方向进行 — 从现有 Java 类生成 XML 模式。我们在这里不深入研究这个特性,这是因为我们只需要根据需要生成与现有 XML 模式映射的 Java 类的功能,而该功能从 JAXB 面世之初就有了。

JAXB 2.0 在我们的批注处理器中的实际应用

当不需要定制时,JAXB 参考实现用起来非常简单。但有时必须使用定制,JAXB 则详细解释了如何进行这样的定制。在我们的示例中,将用于生成 Java 类的 XML 模式的元素命名为在 JAXB 生成的 Java 类中能轻松识别的名称。因为默认的 JAXB 约定适合我们的情况并为简单起见,在我们示例中将不使用这些定制方法。

从源 XML 模式生成 Java 类的过程十分简单。第一步是识别 JAXB 生成的类所需要的 XML 模式。因为我们最终是要为 JPA 应用程序创建一个 orm.xml(或相似名称的)对象关系映射文件,所以我们需要找到描述该映射文件结构的 XML 模式文件。我们可以在几个地方找到这个文件。最容易想到的是 JPA 规范本身。具体来说,可在 EJB 3 规范的“Java 持久性 API”的 10.2 小节中找到 XML 模式。在 http://java.sun.com/xml/ns/persistence/ 可以更方便地获得该模式的 XSD 文件形式 (orm_1_0.xsd)。

JAXB 实现提供了一个“绑定编译器”来创建 Java 类,这些类可以与创建它们所用的 XML 模式绑定。针对 JAXB 参考实现键入“xjc -help”可以获得有关该 JAXB 参考实现的绑定编译器的使用和帮助信息。对于我们示例来说,我们只需要知道三件事情:我们生成绑定 Java 类要用到的 XML 模式,要把这些类放入的 Java 程序包,以及要把生成的 Java 类放入的目标目录。

如果我们假定要把所有 JAXB 生成的类放入一个名为 marx.jpa.persistence.jaxb 的 Java 程序包中,并把这些生成的类写入 C:/jpaJaxb/src 目录中,那么我们可以使用以下 JAXB 绑定编译器命令:xjc -p marx.jpa.persistence.jaxb -d C:/jpaJaxb/src orm_1_0.xsd。JAXB 参考实现的绑定编译器由“xjc”可执行文件调用。选项 -p 提供要把生成的 Java 类放入的程序包;选项 -d 指定要把包含这些生成的类的文件放入的目标目录。为 xjc 绑定编译器提供的最后一个参数是这些生成的 Java 类所基于的 XML 模式。

请注意,如果 xjc 绑定编译器命令找不到指定的模式,将显示类似“the system cannot find the file specified:unknown location”的错误消息。如果根本就没有提供 XML 模式文件,则显示类似“grammar is not specified”的消息。

下表是 JAXB 1.0 RI 和 JAXB 2.0 RI 绑定编译器命令在没有设置 -verbose 选项或 -suppress 选项时运行的输出。该输出显示了 JAXB RI xjc 绑定编译器生成的文件,而且可以从该表明显看出,JAXB 1.0 RI 比 JAXB 2.0 RI 生成的文件多。该表底部显示了所有生成文件总大小的差异,其差异大大超过所生成文件数量的差异。

JAXB 1.0 RI (1.0.6) JAXB 2.0 RI(在 JDK 1.6 中)

xjc 对应 orm_1_0.xsd 文件生成的文件。

(在用 -p 选项指定的子目录结构中创建的所有文件)

解析模式……

编译模式……

impl/AssociationOverrideImpl.java

impl/AttributeOverrideImpl.java

impl/AttributesImpl.java

impl/BasicImpl.java

impl/CascadeTypeImpl.java

impl/ColumnImpl.java

impl/ColumnResultImpl.java

impl/DiscriminatorColumnImpl.java

impl/EmbeddableAttributesImpl.java

impl/EmbeddableImpl.java

impl/EmbeddedIdImpl.java

impl/EmbeddedImpl.java

impl/EmptyTypeImpl.java

impl/EntityImpl.java

impl/EntityListenerImpl.java

impl/EntityListenersImpl.java

impl/EntityMappingsImpl.java

impl/EntityMappingsTypeImpl.java

impl/EntityResultImpl.java

impl/FieldResultImpl.java

impl/GeneratedValueImpl.java

impl/IdClassImpl.java

impl/IdImpl.java

impl/InheritanceImpl.java

impl/JAXBVersion.java

impl/JoinColumnImpl.java

impl/JoinTableImpl.java

impl/LobImpl.java

impl/ManyToManyImpl.java

impl/ManyToOneImpl.java

impl/MapKeyImpl.java

impl/MappedSuperclassImpl.java

impl/NamedNativeQueryImpl.java

impl/NamedQueryImpl.java

impl/OneToManyImpl.java

impl/OneToOneImpl.java

impl/PersistenceUnitDefaultsImpl.java

impl/PersistenceUnitMetadataImpl.java

impl/PostLoadImpl.java

impl/PostPersistImpl.java

impl/PostRemoveImpl.java

impl/PostUpdateImpl.java

impl/PrePersistImpl.java

impl/PreRemoveImpl.java

impl/PreUpdateImpl.java

impl/PrimaryKeyJoinColumnImpl.java

impl/QueryHintImpl.java

impl/SecondaryTableImpl.java

impl/SequenceGeneratorImpl.java

impl/SqlResultSetMappingImpl.java

impl/TableGeneratorImpl.java

impl/TableImpl.java

impl/TransientImpl.java

impl/UniqueConstraintImpl.java

impl/VersionImpl.java

impl/runtime/ValidatorImpl.java

impl/runtime/Util.java

impl/runtime/UnmarshallableObject.java

impl/runtime/ValidatingUnmarshaller.java

impl/runtime/XMLSerializable.java

impl/runtime/InterningUnmarshallerHandler.java

impl/runtime/SAXUnmarshallerHandler.java

impl/runtime/IdentityHashSet.java

impl/runtime/ContentHandlerAdaptor.java

impl/runtime/UnmarshallerImpl.java

impl/runtime/ValidatableObject.java

impl/runtime/GrammarInfo.java

impl/runtime/UnmarshallingEventHandlerAdaptor.java

impl/runtime/PrefixCallback.java

impl/runtime/UnmarshallingEventHandler.java

impl/runtime/SAXUnmarshallerHandlerImpl.java

impl/runtime/DefaultJAXBContextImpl.java

impl/runtime/Discarder.java

impl/runtime/ValidationContext.java

impl/runtime/GrammarInfoImpl.java

impl/runtime/NamespaceContext2.java

impl/runtime/AbstractUnmarshallingEventHandlerImpl.java

impl/runtime/XMLSerializer.java

impl/runtime/ErrorHandlerAdaptor.java

impl/runtime/GrammarInfoFacade.java

impl/runtime/MSVValidator.java

impl/runtime/MarshallerImpl.java

impl/runtime/UnmarshallingContext.java

impl/runtime/SAXMarshaller.java

impl/runtime/NamespaceContextImpl.java

AssociationOverride.java

AttributeOverride.java

Attributes.java

Basic.java

CascadeType.java

Column.java

ColumnResult.java

DiscriminatorColumn.java

Embeddable.java

EmbeddableAttributes.java

Embedded.java

EmbeddedId.java

EmptyType.java

Entity.java

EntityListener.java

EntityListeners.java

EntityMappings.java

EntityMappingsType.java

EntityResult.java

FieldResult.java

GeneratedValue.java

Id.java

IdClass.java

Inheritance.java

JoinColumn.java

JoinTable.java

Lob.java

ManyToMany.java

ManyToOne.java

MapKey.java

MappedSuperclass.java

NamedNativeQuery.java

NamedQuery.java

ObjectFactory.java

OneToMany.java

OneToOne.java

PersistenceUnitDefaults.java

PersistenceUnitMetadata.java

PostLoad.java

PostPersist.java

PostRemove.java

PostUpdate.java

PrePersist.java

PreRemove.java

PreUpdate.java

PrimaryKeyJoinColumn.java

QueryHint.java

SecondaryTable.java

SequenceGenerator.java

SqlResultSetMapping.java

Table.java

TableGenerator.java

Transient.java

UniqueConstraint.java

Version.java

bgm.ser

jaxb.properties

解析模式……

编译模式……

Embeddable.java

EmbeddableAttributes.java

Embedded.java

EmbeddedId.java

EmptyType.java

Entity.java

EntityListener.java

EntityListeners.java

EntityMappings.java

EntityResult.java

EnumType.java

FetchType.java

FieldResult.java

GeneratedValue.java

GenerationType.java

Id.java

IdClass.java

Inheritance.java

InheritanceType.java

JoinColumn.java

JoinTable.java

Lob.java

ManyToMany.java

ManyToOne.java

MapKey.java

MappedSuperclass.java

NamedNativeQuery.java

NamedQuery.java

ObjectFactory.java

OneToMany.java

OneToOne.java

PersistenceUnitDefaults.java

PersistenceUnitMetadata.java

PostLoad.java

PostPersist.java

PostRemove.java

PostUpdate.java

PrePersist.java

PreRemove.java

PreUpdate.java

PrimaryKeyJoinColumn.java

QueryHint.java

SecondaryTable.java

SequenceGenerator.java

SqlResultSetMapping.java

Table.java

TableGenerator.java

TemporalType.java

Transient.java

UniqueConstraint.java

Version.java

package-info.java

生成文件总数

142

(57 个位于程序包的根目录中,

55 个位于“impl”子目录,

30 个位于 impl/runtime 子目录)

62

总大小(所有文件)

2.22 MB

279 KB

对于同一个 XML 模式,JAXB 2.0 RI 生成的类要少很多,总文件大小要比 JAXB 1.0 RI 生成的类小一个数量级。请注意,实际上是将所有生成的类写入了与 -p 选项提供的程序包结构 (marx/jpa/persistence/jaxb/) 匹配的适当子目录结构中。我为了节省空间从上表输出所示中删除了这些与程序包对应的子目录结构。

生成的 Java 类使用相应的子目录结构(根据 -p 选项或按 XSD 默认不指定 -p)来对应使用 -d 选项指定的目录下的程序包名称。这时就可以使用自己喜欢的 IDE 或文本编辑器打开这些 Java 类来查看 JAXB RI xjc 编译器所生成的内容了。

JAXB 运行时

xjc 绑定编译器允许我们创建 Java 类,通过操作这些类可以读写符合 orm_1_0.xsd 模式文件的 XML,无需直接处理 XML。由于我们的应用程序只需写(无需读)XML,因此我们只需将基于 JAXB 的对象编组到 XML 中。如果需要读 XML,则还需要将 XML 反编组到由 JAXB 生成的类实例化的 Java 对象。

JAXB 实现提供运行时库,在将 JAXB 对象数据编组到 XML 或将 XML 数据反编组到 JAXB 对象时会用到这些库。由于现在 Java SE 6 本身就包含这些库,因此如果使用该参考实现生成 JAXB 生成的类,则不需要在 classpath 中添加其他 JAR。

将 JAXB Java 对象数据编组到 XML

下一个代码清单(清单 4)演示了如何将 JAXB Java 对象的数据编组到 XML。该代码清单还使用了我们的批注处理器扩展了的 AbstractProcessor 提供的 Filer。Filer 用于将 JAXB 编组的数据实际写入一个称为 orm.xml 的特定 XML 文件中。

清单 4

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;

/**
* Write out XML file that is consistent with JPA in-source annotations found
* in the JPA-based code.
*
* This method only has a few lines of code, but they are all significant.
* The example here demonstrates writing of an XML file using JAXB
* marshalling. It also demonstrates use of the parent class
* (AbstractProcessor class) and access to that parent class' handle to a
* ProcessingEnvironment (processingEnv). Through access to this
* ProcessingEnvironment, the code gains access to a Filer that it uses
* to write out the JAXB-generated XML file to the file system.
*/
private void writeMappingXmlFile()
{
processingEnv.getMessager().printMessage(
Diagnostic.Kind.NOTE,
"Entered writeMappingXmlFile() method..." );

try
{
JavaFileManager.Location location = StandardLocation.CLASS_OUTPUT;

/*
A few interesting observations for Filer.createResource()
1) Files created with this method are not available for further
annotation processing. This is okay in this context because
we are writing out an XML file and would have no reason to
attempt to process that XML for Java annotations.
2) Limitation of using Filer here is that the supported standard
locations may not really be where we wish to write this XML
file. If so, using a different mechanism for writing a file
might be preferred because the main benefit of Filer is using it
in conjunction with the annotation processor and we don't need
that benefit in this case.
3) The second argument to createResource() is for package name.
This XML file we are writing is really not a source or class
file and packaging concepts do not apply. Therefore, as
recommended in the Javadoc for this method, we are passing the
empty string to that argument.
4) The final argument is the name of the actual file to which the
XML will be written. We only need one file to be specified here.
5) Filer.createResource uses "new" Java support for "..." (ellipses
and variable arguments - Java 5)
*/
FileObject fo =
processingEnv.getFiler().createResource(location, "", "orm.xml");
OutputStream os = fo.openOutputStream();

this.marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
Boolean.TRUE);
this.marshaller.marshal(this.em, os /* System.err */);

os.close();
}
catch (PropertyException propEx)
{
processingEnv.getMessager().printMessage(
Diagnostic.Kind.ERROR,
"PropertyException trying to write XML mapping file:"
+ propEx.getMessage() );
}
catch (JAXBException jaxbEx)
{
processingEnv.getMessager().printMessage(
Diagnostic.Kind.ERROR,
"JAXBException trying to write XML mapping file:"
+ jaxbEx.getMessage() );
}
catch (FilerException filerEx) // must appear before parent IOException
{
processingEnv.getMessager().printMessage(
Diagnostic.Kind.ERROR,
"Problem with Processing Environment Filer: "
+ filerEx.getMessage() );
}
catch (IOException ioEx)
{
processingEnv.getMessager().printMessage(
Diagnostic.Kind.ERROR,
"Problem opening file to write ORM XML."
+ ioEx.getMessage() );
}
}

上述代码清单中的注释引出了有关该代码的一些有趣的事情。在有关 JAXB 将 Java 对象编组到 XML 的讨论中,有两行应特别关注:

         this.marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, 
Boolean.TRUE);
this.marshaller.marshal(this.em, os /* System.err */);

JAXB_FORMATTED_OUTPUT 设置指示 JAXB 运行时编组器在写 XML 时使用“适当的”格式和缩进。在紧接着的第二行中,将编组的 XML 写入从该 Filer 获得的输出流中。如该行中的注释所示,可以很方便地将其直接写入 System.err(或 System.out)以便在控制台查看输出 XML,或将其重定向到一个文件中。

清单 4 中使用的编组器先要在这个类中设置,如清单 5 所示:

清单 5

         ClassLoader cl = Class.forName("marx.jpa.persistence.jaxb.ObjectFactory").getClassLoader();
jc = JAXBContext.newInstance("marx.jpa.persistence.jaxb", cl);
this.marshaller = jc.createMarshaller();
this.of = new ObjectFactory();
this.em = of.createEntityMappings();

清单 5 中的代码获得一个 JAXBContext 的新实例,然后使用这个新实例获得清单 4 中使用的编组器。

清单 5 也应引起注意,因为它显示了如何通过调用 ObjectFactory 的 createEntityMappings() 方法创建 orm.xml 文件的根元素。ObjectFactory 由 xjc 绑定编译器和直接映射到 XML 结构的 Java 类生成。如清单 5 所示,ObjectFactory 用于获得编组时将映射到 XML 的对象的访问。

有关 JAXB 的其他信息

Java EE 5 教程”中对 JAXB 2.0 进行了精彩介绍。虽然这个教程主要是介绍 Java EE(企业版)而非 Java SE(标准版),但有关 JAXB 方面的原理对 EE 和 SE 来说都是一样的。“非官方 JAXB 指南”也提供了使用 JAXB 的一些有用技巧。

运行 JPA 批注处理器

由于 Java SE 6 在 javac 命令中内置了批注处理功能,因此运行我们新创建的 JPA 批注处理器非常简单。本文前面列出了 javac 支持批注处理的选项。

要对具有 JPA 批注的 Java 运行批注处理器,使用以下命令:

javac -processor marx.apt.jpa.AnnotationEntityProcessor
-proc:only
-processorpath C:/otnJava6/classes;C:/TopLink_11.1.1.0_070502_preview/lib/java/api/persistence.jar
-cp C:/TopLink_11.1.1.0_070502_preview/lib/java/api/persistence.jar
-AxmlOverrideAnnotations=true
-AuseUpperCaseColumnNames=true
-Xlint:unchecked
*.java

为了更清楚地说明上述命令使用的选项,将 javac 的不同参数放在不同的行上。-process 选项用于指定我们的批注处理类的全名 (marx.apt.jpa.AnnotationEntityProcessor)。-proc:only 选项用于指示 javac 这次只执行批注处理,不实际编译处理其批注的 Java 类。在上面的示例中,javac 命令在包含 Java JPA 批注的类所在的目录中运行。也就是说,只需指定 *.java 以便 javac 知道要用指定的批注处理器处理哪些类的批注。

以上示例中使用 javac 执行批注处理器的设置可以根据需要更改。例如,将 -J"-verbose:class" 添加到 javac 调用中可以让新启动的 Java 解释器报告有关批注处理执行期间所加载的 Java 类的信息。同样,通常与 java(应用程序启动程序)命令关联的许多其他选项也可以由 javac(Java 编译器)命令通过 -J 选项使用。

如本文前面所述,我们还可以将 -XprintProcessorInfo 或 -XprintRounds 选项传递给 javac,这样就可以在处理批注的同时输出有关处理的更多详细信息。打开 javac、java 或批注处理器本身的详细输出选项在编写和调试新批注处理器时特别有用。

最后,可以使用 -A 选项通过 javac 编译器命令传递批注处理器特有的选项。例如,本文讨论的 JPA 批注处理器就允许用户通过在上述 javac 行中添加 -AxmlOverrideAnnotations=true 指定完全忽略源代码中的批注,仅使用外部 XML 映射。虽然这个选项更有可能被批注处理器提供商用于提供特定功能,但它提供了一个向我们定制的处理器类传递参数的简便方法。

这里需要注意的一点是,Java SE 6 支持在 classpath 规范中用通配符批注指定同一目录中的多个 JAR。在上面用到的 classpath 中,JAR 均位于不同地方,因此就像分别调用它们一样简单。但是,如果需要从同一目录调用许多 JAR,则 classpath JAR 通配符可以节省许多脚本编写时间并方便维护。

结果

当批注处理器处理 JPA 批注的类时,会写入一个 orm.xml 文件,该文件描述与原 Java 源代码中相同的元数据。下面是对一组简单的支持 JPA 的实体类运行这个批注处理器生成的 orm.xml 文件(清单 6)。

清单 6

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" version="1.0">
<persistence-unit-metadata>
<xml-mapping-metadata-complete/>
</persistence-unit-metadata>
<named-native-query name="findAllMovies">
<query>SELECT * FROM MOVIE</query>
</named-native-query>
<named-native-query name="oracle-getDbDate">
<query>SELECT sysdate FROM dual</query>
</named-native-query>
<entity name="Director" class="marx.Director">
<discriminator-value>DIRECTOR</discriminator-value>
</entity>
<entity name="Genre" class="marx.Genre">
<attributes>
<id name="label">
<column name="LABEL"/>
</id>
<many-to-many target-entity="marx.Movie"
name="movies" mapped-by="genres"/>
</attributes>
</entity>
<entity name="Movie" class="marx.Movie">
<attributes>
<id name="id">
<column name="ID"/>
</id>
<many-to-many target-entity="marx.Genre" name="genres">
<join-table name="genre_movie">
<join-column referenced-column-name="id" name="movie_id"/>
<inverse-join-column referenced-column-name="label"
name="genre_label"/>
</join-table>
</many-to-many>
<transient name="somethingNotPersisted"/>
</attributes>
</entity>
<entity name="Person" class="marx.Person">
<inheritance strategy="SINGLE_TABLE"/>
<discriminator-column name="ROLE" length="31"
discriminator-type="STRING"/>
<sequence-generator sequence-name="PERSON_SEQ"
name="PERSON_SEQ"
initial-value="1"
allocation-size="1"/>
<attributes>
<id name="id">
<column name="ID"/>
<generated-value strategy="SEQUENCE" generator="PERSON_SEQ"/>
</id>
<basic name="gender">
<enumerated>STRING</enumerated>
</basic>
</attributes>
</entity>
<entity name="Rating" class="marx.Rating">
<attributes>
<id name="label">
<column name="LABEL"/>
</id>
</attributes>
</entity>
<entity name="Studio" class="marx.Studio">
<attributes>
<id name="id">
<column name="ID"/>
</id>
</attributes>
</entity>
</entity-mappings>

对于我们的批注处理器生成的这个 XML,有几点需要特别注意。空标记 <persistence-unit-metadata/> 的存在表明,应忽略所有与 JPA 相关的源代码内批注,并且应使用这个 XML 文件提供元数据。我们的批注处理器之所以输出了这个标记,是因为我们运行该处理器时提供了 -AxmlOverrideAnnotations=true 标志。因为我们使用 -A 选项指定这个标志,所以我们的批注处理器可以使用从 AbstractProcessor 继承的 ProcessingEnvironment 使用它。

这个代码清单还展示了使用批注处理从源代码内批注写入一个 orm.xml(或其他映射文件)的另一个潜在的好处。就本示例而言,批注处理器将所有 <named-native-query..> 项输出为根元素 <entity-mappings..> 的一级子元素。用于 JPA 对象/关系映射的 XML 模式允许将这些项输出到这个级别或特别写入某个指定的实体。但是,因为命名原生查询的范围是持久性单元而非个性化单元,所以把这些查询放在 XML 文件中(这样该范围就显而易见了)更有意义。Java 代码将这些通用的命名原生查询列在单个 Java 实体类中,我们的批注处理器将这些查询移到所生成 XML 文件中一个更有意义的位置。

通过我们 XML 格式的 JPA 应用程序元数据,我们现在可以获得比源代码内批注更多的好处。如果我们的部署人员与开发人员不是同一人,则他们不再需要更改 Java 代码,而是更改外部的 XML 文件。开发人员在开发时仍可使用源代码内批注,而部署人员可以使用我们新的批注处理器将这些源代码内批注转换为 XML。此外,还可以践行 JPA 最佳实践,如我们示例中将原生命名查询放在 ORM 文件中最合适的位置。

批注处理器存在的问题及改进

本文中介绍的批注处理器主要为展示 Java SE 6 如何比以往简化了批注处理和 JAXB 使用提供了一个具体示例。本文的另一个目的是介绍使用批注处理器将源代码内 Java 批注转换到外部 XML 配置文件的概念。

本文中使用的代码存在几方面的限制。首先,没有处理所有与 JPA 相关的批注。其次,即使处理批注的某个部分,也没不一定处理该批注的所有值和属性。但是,按照该批注处理器已重复使用的样式可以轻松添加对其他批注的支持以及对部分支持批注的完整支持。

本文中介绍的批注处理器仅涵盖了与对象/关系映射(如果在外部指定,通常位于 orm.xml 文件中)相关的 JPA 批注。另一个可能的 JPA 外部 XML 配置文件是 persistence.xml 文件(基于 Java SE JPA 的应用程序需要该文件)。本文中建议并部分构建的批注处理器没有涉及到 persistence.xml 文件,Sahoo 有一篇网志介绍了如何使用 J2SE 5 的 apt 工具和 JAXB 2.0 来基于源代码内批注写 persistence.xml 文件。(阅读有关该主题的 Sahoo 网志。)因为 Java SE 6 仍带有 apt,所以这个处理器仍可用于 Java SE 6 安装。

结论

Java SE 6 为 Java 开发人员在进行 JAXB 开发和批注处理开发方面带来了极大的便利。Java 6 对 JAXB 2.0 和复杂的批注处理的现成支持比以往更加简化了将源代码内 JPA 批注转换为 XML 配置文件的工作。本文已经考虑到如何利用 Java SE 6 对 JAXB 可用性和批注处理的改进来提高从源代码批注或外部 XML 配置文件部署基于 JPA 应用程序的能力。


Dustin Marx 是 Raytheon 公司的高级软件工程师和架构师。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics