1. 概述

本文介绍 Aegis 数据绑定机制,这是一个能在 Java 对象与 XML Schema 描述的 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>

最新版本可在 Maven 仓库 查询。

3. 类型定义

本节通过三个示例类型说明 Aegis 的用法。

3.1. Course 类

这是最简单的示例类:

public class Course {
    private int id;
    private String name;
    private String instructor;
    private Date enrolmentDate;

    // 标准 getter/setter
}

3.2. CourseRepo 接口

作为顶层类型,我们将其定义为接口(而非类)以展示 Aegis 处理接口的能力——这在 JAXB 中需要自定义适配器才能实现:

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

注意 getCourses 方法返回 Map 类型。这体现了 Aegis 相对 JAXB 的优势:后者需要自定义适配器才能处理 Map,而前者原生支持。

3.3. CourseRepoImpl 实现类

public class CourseRepoImpl implements CourseRepo {
    private String greeting;
    private Map<Integer, Course> courses = new HashMap<>();

    // 标准 getter/setter

    @Override
    public void addCourse(Course course) {
        courses.put(course.getId(), course);
    }
}

4. 自定义数据绑定

自定义配置需在类路径下放置 XML 映射文件。文件目录结构必须与对应 Java 类的包结构一致,例如:

  • 类全限定名:com.example.Course
  • 映射文件路径:com/example/Course.aegis.xml

映射文件命名规则:类名.aegis.xml

4.1. CourseRepo 映射配置

CourseRepo 接口位于 com.baeldung.cxf.aegis 包,因此映射文件为: com/baeldung/cxf/aegis/CourseRepo.aegis.xml

配置内容如下:

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

此配置实现: ✅ 修改接口对应 XML 元素的名称和命名空间 ✅ 将 greeting 属性转为 XML 属性(而非子元素)

4.2. Course 映射配置

Course 类的映射文件位于相同目录:com/baeldung/cxf/aegis/Course.aegis.xml

配置内容:

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

此配置实现: ❌ 序列化时忽略 instructor 属性 ⚠️ 从 XML 反序列化时该属性将丢失

更多自定义选项请参考 Aegis 官方文档

5. 测试验证

本节通过测试用例演示 Aegis 数据绑定的完整流程。

测试类定义核心字段:

public class BaeldungTest {
    private AegisContext context;
    private String fileName = "baeldung.xml";

    // 其他方法
}

5.1. 初始化 AegisContext

private void initializeContext() {
    context = new AegisContext();
    
    // 设置根类型
    Set<Type> rootClasses = new HashSet<>();
    rootClasses.add(CourseRepo.class);
    context.setRootClasses(rootClasses);
    
    // 指定接口实现类
    Map<Class<?>, String> implMap = new HashMap<>();
    implMap.put(CourseRepoImpl.class, "CourseRepo");
    context.setBeanImplementationMap(implMap);
    
    // 启用 xsi:type 属性
    context.setWriteXsiTypes(true);
    
    context.initialize();
}

关键配置说明:

  1. 根类型:Aegis 会为每个根类型生成 XML 映射元素
  2. 实现映射:为接口指定代理实现类
  3. 类型标记:在 XML 中保留 xsi:type 属性(除非被映射文件覆盖)

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 Baeldung!");
    courseRepo.addCourse(restCourse);
    courseRepo.addCourse(securityCourse);
    return courseRepo;
}

5.3. 对象与 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.baeldung.com", "baeldung"), 
      false, xmlWriter, aegisType);
    
    xmlWriter.close();
}

反序列化方法

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;
}

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);

    // 验证断言
    assertEquals("Welcome to Baeldung!", 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()); // 被映射文件忽略
}

验证要点: ✅ greeting 属性正确恢复 ✅ Date 类型自动转换为 xsd:dateTimeinstructor 属性按预期被忽略

5.5. XML 输出对比

未自定义映射的 XML

<ns1:baeldung xmlns:ns1="http://aegis.cxf.baeldung.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>
        <!-- 其他课程数据 -->
    </ns1:courses>
    <ns1:greeting>Welcome to Baeldung!</ns1:greeting>  <!-- 子元素形式 -->
</ns1:baeldung>

应用自定义映射后的 XML

<ns1:baeldung xmlns:ns1="http://aegis.cxf.baeldung.com"
    xmlns:ns="http://courserepo.baeldung.com"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:type="ns:Baeldung" greeting="Welcome to Baeldung!">  <!-- 属性形式 -->
    <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>
                <!-- instructor 属性被忽略 -->
                <ns1:name>REST with Spring</ns1:name>
            </ns2:value>
        </ns2:entry>
        <!-- 其他课程数据 -->
    </ns:courses>
</ns1:baeldung>

关键变化对比: | 特性 | 默认行为 | 自定义映射后 | |---------------------|------------------------|---------------------------| | 根元素类型 | ns1:CourseRepo | ns:Baeldung | | greeting 属性 | XML 子元素 | XML 属性 | | instructor 属性 | 包含在输出中 | 被忽略 |

💡 提示:测试运行后可在项目根目录找到生成的 baeldung.xml 文件。

6. 总结

本文演示了 Apache CXF Aegis 数据绑定作为独立子系统的使用方法,包括:

  1. Java 对象与 XML 的双向转换
  2. 通过映射文件自定义序列化行为
  3. 处理复杂类型(Map、接口、Date)的简单方案

完整示例代码请参考 GitHub 项目


原始标题:Introduction to Apache CXF Aegis Data Binding