将访问例程添加到 JAXB 生成的 类 集(源自 XSD)

Adding access routines to a JAXB generated set of classes originating from an XSD

我有一个基于 XSD 的嵌套 XML 结构。我使用 JAXB 进行解组(只读)。

我经常需要在大型结构的某处找到一个或多个元素。为了避免每次需要搜索时都遍历结构,我想添加一个优化的带有内部缓存的搜索功能。

定义它的最佳方式是什么? pro/cons 有哪些不同的方式?

我最初想到的是使用外观或适配器,其中适配器 class 访问生成的 class 并根据需要添加方法;但是我想征求建议。


作为一个(稍微)简化的示例,需要搜索基于此 XSD 的 XML 类型的具有特定 "boq" 元素的 "step" 元素:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
  <xs:element name="test">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="group"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="group">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="step"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="step">
    <xs:complexType>
      <xs:sequence>
        <xs:element minOccurs="0" ref="number"/>
        <xs:element ref="name"/>
        <xs:element ref="type"/>
        <xs:element ref="target"/>
        <xs:sequence minOccurs="0">
          <xs:element ref="boq"/>
          <xs:element ref="remote"/>
        </xs:sequence>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="number" type="xs:integer"/>
  <xs:element name="name" type="xs:NCName"/>
  <xs:element name="type" type="xs:NCName"/>
  <xs:element name="target" type="xs:NCName"/>
  <xs:element name="boq" type="xs:string"/>
  <xs:element name="remote" type="xs:string"/>
</xs:schema>

该模式是使用 JAXB 编译的,所以我得到了几个 classes。 使用解组功能,我在内存中有访问我的 XML 的数据结构。

现在考虑,我需要一个优化的搜索功能,它访问定义了 boq 元素的所有步骤,以及 return boq 和 remote 的值(如果也定义了)。

    HashMap<String,Step> resultMap = new HashMap<>();
    test.getGroup().forEach(group -> 
            group.getStep().forEach(step -> {
                    if ("searchpattern".equals(step.getBoq()))
                        resultMap.put("searchpattern", step);
            }));

封装此类搜索的最佳方法是什么?我可以编写第二个 class 作为包含此方法的适配器,或者有更好的选择吗?遗产?使用 JAXB 本身的选项?使用第三方插件,例如 maven 的 jaxb-delegate 插件?

有多种方法可以解决这个问题。

实用程序Class

最简单的方法是在实用程序中简单地实现访问路由 class。所以基本上你会调用类似 SearchPatterns.of(foo) 的东西,并将模式派生的 class 的实例传递给它。将其与 foo.getSearchPatterns()(其中 getSearchPatterns() 以某种方式添加到模式派生的 class)进行比较,差别不大。好吧,没那么 OOP-incapsulation-whatever,但是,坦率地说,谁在乎。

使用代码注入器插件

您可以使用 XJC 代码注入器插件在生成的 classes 中注入任何代码。请参阅以下问题的示例:

Inserting code with XJC+xsd+jxb using the options " -Xinject-code -extension "

(如果您遇到问题,请提出另一个问题。我知道我们在 中没有 XJC 代码注入器问题。)

这对您来说相对容易,并且允许您注入任何您想要的代码。

缺点是您的 Java 代码的一部分将驻留在一个奇怪的 XML 文件中。

扩展准备好的摘要Class

另一种选择是使用您需要的访问器方法(例如,getSearchpatterns())以及它使用的方法(getGroup())准备一个抽象 class 作为 抽象 方法。然后让你的模式派生 class 扩展这个准备好的摘要 class。生成的方法将实现在准备好的 superclass 中定义的抽象方法。这本质上是一个 "Template method" 模式。

有很多方法可以使模式派生 class 扩展现有的 class。这是其中之一:

JAXB Marshalling - extending an existing class

或者您可以使用 JAXB2 Basics 中的继承插件。 免责声明:我是作者。

而不是扩展 class,您可以定义一个带有模板方法的接口 + 访问器的默认方法,并让您的模式派生 class 实现它。

我不喜欢这个选项,因为它滥用继承来添加实用方法。

编写您自己的 XJC 插件

您要添加的访问器背后可能有一些特定的逻辑。因此,也许您可​​以根据 "specific logic".

实际上生成访问器,而不是简单地注入代码(例如代码注入器插件)

然而,这是一种非常复杂的方法。有关简短概述,请参阅 this answer。我只会在真的有 "specific logic"

的情况下接受它

推荐

我个人更喜欢将我的业务逻辑与模式派生的 classes 分开。我可能是那里最大的 JAXB/XJC 粉丝之一。我肯定会编写一个实用程序 class 来提供您想要的任何访问器。

我不喜欢 Code Injector 选项,因为这样您的部分代码就会出现在一些奇怪的 XML 文件中。因此,如果您要重构 IDE 中的任何内容,则不会触及该代码。

扩展准备好的抽象 class 或实现准备好的接口也不是我的最爱。我认为这只是滥用 OOP 构造来添加一些实用程序代码。

对于没有太多 XJC 插件经验的开发人员来说,编写自己的插件太复杂了。另外,我无法识别可以以某种方式概括的 "specific logic",所以这个选项甚至可能没有意义。