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. Курс
Это самый простой класс в нашем примере, который определяется как:
public class Course {
private int id;
private String name;
private String instructor;
private Date enrolmentDate;
// standard getters and setters
}
3.2. КурсРепо
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);
}
Обратите внимание, что мы объявляем метод getCourses
с возвращаемым типом Map
. Это намеренно, чтобы выразить еще одно преимущество 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
в пути к классам. Имя файла сопоставления должно совпадать с соответствующим типом Java с добавленным к нему суффиксом .aegis.xml .
4.1. Сопоставление репозитория
курса
Интерфейс CourseRepo
принадлежит пакету com.foreach.cxf.aegis
, поэтому соответствующий файл сопоставления называется CourseRepo.aegis.xml
и помещается в каталог com/foreach/cxf/aegis
в пути к классам.
В файле сопоставления CourseRepo
мы меняем имя и пространство имен элемента XML, связанного с интерфейсом CourseRepo
, а также стиль его свойства приветствия :
<mappings xmlns:ns="http://courserepo.foreach.com">
<mapping name="ns:ForEach">
<property name="greeting" style="attribute"/>
</mapping>
</mappings>
4.2. Картографирование курса
Подобно типу CourseRepo
, файл сопоставления класса Course
называется Course.aegis.xml
и также находится в каталоге com/foreach/cxf/aegis
.
В этом файле сопоставления мы указываем Aegis игнорировать свойство инструктора класса
Course
при маршалинге, чтобы его значение было недоступно в объекте, воссозданном из выходного XML-документа:
<mappings>
<mapping>
<property name="instructor" ignore="true"/>
</mapping>
</mappings>
На домашней странице Aegis мы можем найти дополнительные параметры настройки.
5. Тестирование
Этот раздел представляет собой пошаговое руководство по настройке и выполнению тестового примера, иллюстрирующего использование привязок данных Aegis.
Чтобы облегчить процесс тестирования, мы объявляем два поля внутри тестового класса:
public class ForEachTest {
private AegisContext context;
private String fileName = "foreach.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 создает элемент сопоставления XML для каждого типа
в объекте Set<Type> .
В этом руководстве мы устанавливаем только CourseRepo
в качестве корневого типа.
Теперь давайте установим карту реализации для контекста, чтобы указать прокси-класс для интерфейса CourseRepo
:
Map<Class<?>, String> beanImplementationMap = new HashMap<>();
beanImplementationMap.put(CourseRepoImpl.class, "CourseRepo");
context.setBeanImplementationMap(beanImplementationMap);
Последняя конфигурация контекста Aegis сообщает ему установить атрибут xsi:type
в соответствующем XML-документе. Этот атрибут содержит фактическое имя типа связанного объекта 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("ForEach");
restCourse.setEnrolmentDate(new Date(1234567890000L));
Course securityCourse = new Course();
securityCourse.setId(2);
securityCourse.setName("Learn Spring Security");
securityCourse.setInstructor("ForEach");
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.foreach.com", "foreach"), 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;
}
Здесь объект AegisReader
создается из экземпляра AegisContext
. Затем объект 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());
Понятно, что, кроме свойства инструктора
, у всех остальных восстанавливаются значения, в том числе у свойства enrolmentDate
со значениями типа Date
. Это именно то, чего мы ожидаем, поскольку мы проинструктировали Aegis игнорировать свойство инструктора
при маршалинге объектов курса .
5.5. Выходной XML-документ
Чтобы сделать эффект файлов сопоставления Aegis явным, мы показываем XML-документ без настройки ниже:
<ns1:foreach xmlns:ns1="http://aegis.cxf.foreach.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>ForEach</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>ForEach</ns1:instructor>
<ns1:name>Learn Spring Security</ns1:name>
</ns2:value>
</ns2:entry>
</ns1:courses>
<ns1:greeting>Welcome to Beldung!</ns1:greeting>
</ns1:foreach>
Сравните это со случаем, когда пользовательское сопоставление Aegis находится в действии:
<ns1:foreach xmlns:ns1="http://aegis.cxf.foreach.com"
xmlns:ns="http://courserepo.foreach.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="ns:ForEach" 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:foreach>
Вы можете найти эту XML-структуру в файле foreach.xml
прямо в главном каталоге проекта после выполнения теста, определенного в этом разделе.
Вы увидите, что атрибут type
и пространство имен элемента XML, соответствующего объекту CourseRepo,
изменяются в соответствии с тем, что мы установили в файле CourseRepo.aegis.xml
. Свойство приветствия
также преобразуется в атрибут, а свойство инструктора объектов
курса
исчезает, как и ожидалось.
Стоит отметить, что по умолчанию Aegis преобразует базовый тип Java в наиболее подходящий тип схемы, например, из объектов Date в элементы
xsd:dateTime
, как показано в этом руководстве. Однако мы можем изменить эту конкретную привязку, установив конфигурацию в соответствующем файле сопоставления.
Пожалуйста, перейдите на домашнюю страницу Aegis, если вы хотите получить больше информации.
6. Заключение
В этом руководстве показано использование привязки данных Apache CXF Aegis в качестве отдельной подсистемы. Он демонстрирует, как можно использовать Aegis для преобразования объектов Java в элементы XML и наоборот.
В этом руководстве также рассматривается, как настроить поведение привязки данных.
И, как всегда, реализацию всех этих примеров и фрагментов кода можно найти в проекте GitHub .