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);
Aegis 为Set对象中的每个类型创建一个 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();
}
正如我们所见,AegisWriter和AegisType对象必须从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属性之外,所有其他属性的值都已恢复,包括值类型为Date的enrolmentDate属性。这正是我们所期望的,因为我们已指示 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 主页。