Contents

Apache CXF AEGIS数据绑定简介

1. 概述

本教程介绍了Aegis 数据绑定,这是一个可以在 Java 对象和 XML 模式描述的 XML 文档之间映射的子系统。Aegis 允许对映射过程进行详细控制,同时将编程工作量降至最低。

Aegis 是Apache CXF 的一部分,但不限于仅在此框架内使用。相反,这种数据绑定机制可以在任何地方使用,因此在本教程中,我们将重点关注它作为独立子系统的使用。

2. Maven依赖

激活 Aegis 数据绑定所需的唯一依赖项是:

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-databinding-aegis</artifactId>
    <version>3.1.8</version>
</dependency>

可以在此处 找到此工件的最新版本。

3. 类型定义

本节介绍用于说明 Aegis 的三种类型的定义。

3.1. Course

这是我们示例中最简单的类,定义为:

public class Course {
    private int id;
    private String name;
    private String instructor;
    private Date enrolmentDate;
    // standard getters and setters
}

3.2. CourseRepo

CourseRepo是我们模型中的顶级类型。我们将它定义为一个接口而不是一个类,以演示编组 Java 接口是多么容易,这在没有自定义适配器的 JAXB 中是不可能的:

public interface CourseRepo {
    String getGreeting();
    void setGreeting(String greeting);
    Map<Integer, Course> getCourses();
    void setCourses(Map<Integer, Course> courses);
    void addCourse(Course course);  
}

请注意,我们使用返回类型Map声明getCourses方法。这是为了表达 Aegis 相对于 JAXB 的另一个优势。后者无法在没有自定义适配器的情况下编组地图,而前者可以。

3.3. CourseRepoImpl

此类提供CourseRepo接口的实现:

public class CourseRepoImpl implements CourseRepo {
    private String greeting;
    private Map<Integer, Course> courses = new HashMap<>();
    // standard getters and setters
    @Override
    public void addCourse(Course course) {
        courses.put(course.getId(), course);
    }
}

4.自定义数据绑定

为了使自定义生效,XML 映射文件必须存在于类路径中。要求将这些文件放在一个目录中,该目录的结构对应于相关 Java 类型的包层次结构。

例如,如果一个完全限定的名称类名为package.ClassName,则其关联的映射文件必须位于类路径上的package/ClassName子目录中。映射文件的名称必须与附加了*.aegis.xml*后缀的关联 Java 类型相同。

4.1. CourseRepo映射

CourseRepo接口属于com.blogdemo.cxf.aegis包,所以其对应的映射文件命名为CourseRepo.aegis.xml,放到classpath的com/blogdemo/cxf/aegis目录下。

CourseRepo映射文件中,我们更改与CourseRepo接口关联的 XML 元素的名称和命名空间,以及它的greeting属性的样式:

<mappings xmlns:ns="http://courserepo.blogdemo.com">
    <mapping name="ns:Blogdemo">
        <property name="greeting" style="attribute"/>
    </mapping>
</mappings>

4.2. Course映射

CourseRepo类型类似,Course类的映射文件名为Course.aegis.xml,也位于com/blogdemo/cxf/aegis目录下。

在这个映射文件中,我们指示 Aegis 在编组时忽略Course类的instructor属性,以便其值在从输出 XML 文档重新创建的对象中不可用:

<mappings>
    <mapping>
        <property name="instructor" ignore="true"/>
    </mapping>
</mappings>

Aegis 的主页 是我们可以找到更多自定义选项的地方。

5. 测试

本节是设置和执行测试用例的分步指南,说明 Aegis 数据绑定的使用。

为了方便测试过程,我们在测试类中声明了两个字段:

public class BlogdemoTest {
    private AegisContext context;
    private String fileName = "blogdemo.xml";
    // other methods
}

这些字段已在此处定义以供此类的其他方法使用。

5.1. AegisContext初始化

首先,必须创建一个AegisContext对象:

context = new AegisContext();

然后配置并初始化该AegisContext实例。以下是我们为上下文设置根类的方法:

Set<Type> rootClasses = new HashSet<Type>();
rootClasses.add(CourseRepo.class);
context.setRootClasses(rootClasses);

AegisSet对象中的每个类型创建一个 XML 映射元素。在本教程中,我们仅将CourseRepo设置为根类型。 现在,让我们为上下文设置实现映射,以指定CourseRepo接口的代理类:

Map<Class<?>, String> beanImplementationMap = new HashMap<>();
beanImplementationMap.put(CourseRepoImpl.class, "CourseRepo");
context.setBeanImplementationMap(beanImplementationMap);

Aegis 上下文的最后一个配置是告诉它在相应的 XML 文档中设置 xsi:type 属性。此属性携带相关 Java 对象的实际类型名称,除非被映射文件覆盖:

context.setWriteXsiTypes(true);

我们的AegisContext实例现在可以初始化了:

context.initialize();

为了保持代码干净,我们将本小节中的所有代码片段收集到一个辅助方法中:

private void initializeContext() {
    // ...
}

5.2. 简单的数据设置

由于本教程的简单性,我们直接在内存中生成示例数据,而不是依赖于持久解决方案。让我们使用以下设置逻辑填充课程存储库:

private CourseRepoImpl initCourseRepo() {
    Course restCourse = new Course();
    restCourse.setId(1);
    restCourse.setName("REST with Spring");
    restCourse.setInstructor("Eugen");
    restCourse.setEnrolmentDate(new Date(1234567890000L));
    
    Course securityCourse = new Course();
    securityCourse.setId(2);
    securityCourse.setName("Learn Spring Security");
    securityCourse.setInstructor("Eugen");
    securityCourse.setEnrolmentDate(new Date(1456789000000L));
    
    CourseRepoImpl courseRepo = new CourseRepoImpl();
    courseRepo.setGreeting("Welcome to Beldung!");
    courseRepo.addCourse(restCourse);
    courseRepo.addCourse(securityCourse);
    return courseRepo;
}

5.3. 绑定 Java 对象和 XML 元素

将 Java 对象编组为 XML 元素需要采取的步骤通过以下辅助方法进行说明:

private void marshalCourseRepo(CourseRepo courseRepo) throws Exception {
    AegisWriter<XMLStreamWriter> writer = context.createXMLStreamWriter();
    AegisType aegisType = context.getTypeMapping().getType(CourseRepo.class);
    XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance()
      .createXMLStreamWriter(new FileOutputStream(fileName));
    
    writer.write(courseRepo, 
      new QName("http://aegis.cxf.blogdemo.com", "blogdemo"), false, xmlWriter, aegisType);
    
    xmlWriter.close();
}

正如我们所见,AegisWriterAegisType对象必须从AegisContext实例中创建。然后AegisWriter对象将给定的 Java 实例编组到指定的输出。

在这种情况下,这是一个XMLStreamWriter对象,它与以文件系统中fileName类级字段的值命名的文件相关联。

以下方法将 XML 文档解组为给定类型的 Java 对象:

private CourseRepo unmarshalCourseRepo() throws Exception {       
    AegisReader<XMLStreamReader> reader = context.createXMLStreamReader();
    XMLStreamReader xmlReader = XMLInputFactory.newInstance()
      .createXMLStreamReader(new FileInputStream(fileName));
    
    CourseRepo courseRepo = (CourseRepo) reader.read(
      xmlReader, context.getTypeMapping().getType(CourseRepo.class));
    
    xmlReader.close();
    return courseRepo;
}

在这里,从AegisContext实例生成一个AegisReader对象。然后,AegisReader对象根据提供的输入创建一个 Java 对象。在此示例中,该输入是一个XMLStreamReader对象,该对象由我们在上面描述的marshalCourseRepo方法中生成的文件支持。

5.4. 测试

现在,是时候将前面小节中定义的所有辅助方法组合成一个测试方法了:

@Test
public void whenMarshalingAndUnmarshalingCourseRepo_thenCorrect()
  throws Exception {
    initializeContext();
    CourseRepo inputRepo = initCourseRepo();
    marshalCourseRepo(inputRepo);
    CourseRepo outputRepo = unmarshalCourseRepo();
    Course restCourse = outputRepo.getCourses().get(1);
    Course securityCourse = outputRepo.getCourses().get(2);
    // JUnit assertions
}

我们首先创建一个CourseRepo实例,然后将其编组为 XML 文档,最后解组该文档以重新创建原始对象。让我们验证重新创建的对象是否符合我们的预期:

assertEquals("Welcome to Beldung!", outputRepo.getGreeting());
assertEquals("REST with Spring", restCourse.getName());
assertEquals(new Date(1234567890000L), restCourse.getEnrolmentDate());
assertNull(restCourse.getInstructor());
assertEquals("Learn Spring Security", securityCourse.getName());
assertEquals(new Date(1456789000000L), securityCourse.getEnrolmentDate());
assertNull(securityCourse.getInstructor());

很明显,除了Instructor属性之外,所有其他属性的值都已恢复,包括值类型为DateenrolmentDate属性。这正是我们所期望的,因为我们已指示 Aegis在编组Course对象时忽略Instructor属性。

5.5. 输出 XML 文档

为了使 Aegis 映射文件的效果更加明确,我们在下面展示了没有自定义的 XML 文档:

<ns1:blogdemo xmlns:ns1="http://aegis.cxf.blogdemo.com"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:type="ns1:CourseRepo">
    <ns1:courses>
        <ns2:entry xmlns:ns2="urn:org.apache.cxf.aegis.types">
            <ns2:key>1</ns2:key>
            <ns2:value xsi:type="ns1:Course">
                <ns1:enrolmentDate>2009-02-14T06:31:30+07:00
                </ns1:enrolmentDate>
                <ns1:id>1</ns1:id>
                <ns1:instructor>Eugen</ns1:instructor>
                <ns1:name>REST with Spring</ns1:name>
            </ns2:value>
        </ns2:entry>
        <ns2:entry xmlns:ns2="urn:org.apache.cxf.aegis.types">
            <ns2:key>2</ns2:key>
            <ns2:value xsi:type="ns1:Course">
                <ns1:enrolmentDate>2016-03-01T06:36:40+07:00
                </ns1:enrolmentDate>
                <ns1:id>2</ns1:id>
                <ns1:instructor>Eugen</ns1:instructor>
                <ns1:name>Learn Spring Security</ns1:name>
            </ns2:value>
        </ns2:entry>
    </ns1:courses>
    <ns1:greeting>Welcome to Beldung!</ns1:greeting>
</ns1:blogdemo>

将此与 Aegis 自定义映射运行时的情况进行比较:

<ns1:blogdemo xmlns:ns1="http://aegis.cxf.blogdemo.com"
    xmlns:ns="http://courserepo.blogdemo.com"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:type="ns:Blogdemo" greeting="Welcome to Beldung!">
    <ns:courses>
        <ns2:entry xmlns:ns2="urn:org.apache.cxf.aegis.types">
            <ns2:key>1</ns2:key>
            <ns2:value xsi:type="ns1:Course">
                <ns1:enrolmentDate>2009-02-14T06:31:30+07:00
                </ns1:enrolmentDate>
                <ns1:id>1</ns1:id>
                <ns1:name>REST with Spring</ns1:name>
            </ns2:value>
        </ns2:entry>
        <ns2:entry xmlns:ns2="urn:org.apache.cxf.aegis.types">
            <ns2:key>2</ns2:key>
            <ns2:value xsi:type="ns1:Course">
                <ns1:enrolmentDate>2016-03-01T06:36:40+07:00
                </ns1:enrolmentDate>
                <ns1:id>2</ns1:id>
                <ns1:name>Learn Spring Security</ns1:name>
            </ns2:value>
        </ns2:entry>
    </ns:courses>
</ns1:blogdemo>

运行本节中定义的测试后,您可以在项目主目录中的blogdemo.xml中找到这个 XML 结构。

您会看到CourseRepo对象对应的 XML 元素的type属性和命名空间会根据我们在CourseRepo.aegis.xml文件中设置的内容发生变化。greeting属性也转化为一个属性,Course对象的instructor属性如预期般消失了。

值得注意的是,默认情况下,Aegis 将基本 Java 类型转换为最匹配的模式类型,例如从Date对象转换为xsd:dateTime 元素,如本教程所示。但是,我们可以通过在相应的映射文件中设置配置来更改该特定绑定。

如果您想了解更多信息,请导航至Aegis 主页。