Статьи

Подходы к XML — Часть 3 — JAXB

Если вы помните из части 1 и части 2 , я освещаю различные подходы к синтаксическому анализу XML-сообщений с использованием невероятно банального сценария Pete’s Perfect Pizza , компании по производству пиццы с большими идеями. В этой истории вы являетесь сотрудником Pete’s, и вас попросили внедрить систему отправки заказов со стойки регистрации на кухню, и у вас возникла идея использовать XML. У вас только что работает ваш SAX Parser, но Пит становится глобальным, открывая кухни по всему миру, принимая заказы через Интернет. Он нанял несколько консультантов, которые придумали план расширения вашего удобного XML-сообщения, и они определили его с помощью схемы. Они также улучшили ваше сообщение, добавив одну из своих собственных схем клиентов. В результате следующие XSD-файлы попадают в ваш почтовый ящик, и вам нужно заняться …

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
<?xml version='1.0' encoding='UTF-8'?>
<!-- edited with XMLSpy v2011 sp1 (http://www.altova.com) by Roger Hughes (Marin Solutions Ltd) -->
<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema' xmlns:ppp='http://www.petesperfectpizza.com' xmlns:cust='http://customer.dets' targetNamespace='http://www.petesperfectpizza.com' elementFormDefault='qualified' attributeFormDefault='unqualified' version='1.00'>
 <!-- Import the Namespaces required -->
 <xs:import namespace='http://customer.dets' schemaLocation='customer.xsd'/>
 <!-- The Root Node -->
 <xs:element name='PizzaOrder'>
  <xs:annotation>
   <xs:documentation>A wrapper around the customer and the pizza order</xs:documentation>
  </xs:annotation>
  <xs:complexType>
   <xs:sequence>
    <xs:element name='orderID' type='ppp:CorrelationIdentifierType'/>
    <xs:element name='date' type='ppp:DateType'/>
    <xs:element name='time' type='ppp:TimeType'/>
    <xs:element name='Customer' type='cust:CustomerType'/>
    <xs:element ref='ppp:pizzas'/>
   </xs:sequence>
  </xs:complexType>
 </xs:element>
 <!-- The Pizza Order-->
 <xs:element name='pizzas'>
  <xs:annotation>
   <xs:documentation>This is a list of pizzas ordered by the customer</xs:documentation>
  </xs:annotation>
  <xs:complexType>
   <xs:sequence>
    <xs:element name='pizza' type='ppp:PizzaType'  minOccurs='1' maxOccurs='unbounded'/>
   </xs:sequence>
  </xs:complexType>
 </xs:element>
 <xs:complexType name='PizzaType'>
  <xs:sequence>
   <xs:element name='name' type='ppp:PizzaNameType'>
    <xs:annotation>
     <xs:documentation>The type of pizza on the menu</xs:documentation>
    </xs:annotation>
   </xs:element>
   <xs:element name='base' type='ppp:BaseType'>
    <xs:annotation>
     <xs:documentation>type of base</xs:documentation>
    </xs:annotation>
   </xs:element>
   <xs:element name='quantity' type='ppp:QuantityType'>
    <xs:annotation>
     <xs:documentation>quantity of pizzas</xs:documentation>
    </xs:annotation>
   </xs:element>
  </xs:sequence>
 </xs:complexType>
 <xs:simpleType name='PizzaNameType'>
  <xs:restriction base='xs:token'>
   <xs:enumeration value='Margherita'>
    <xs:annotation>
     <xs:documentation>Plain and Simple</xs:documentation>
    </xs:annotation>
   </xs:enumeration>
   <xs:enumeration value='Marinara'>
    <xs:annotation>
     <xs:documentation>Garlic Pizza...</xs:documentation>
    </xs:annotation>
   </xs:enumeration>
   <xs:enumeration value='Prosciutto e Funghi'>
    <xs:annotation>
     <xs:documentation>Ham and Musheroom</xs:documentation>
    </xs:annotation>
   </xs:enumeration>
   <xs:enumeration value='Capricciosa'>
    <xs:annotation>
     <xs:documentation>with an egg</xs:documentation>
    </xs:annotation>
   </xs:enumeration>
  </xs:restriction>
 </xs:simpleType>
 <xs:simpleType name='BaseType'>
  <xs:restriction base='xs:token'>
   <xs:enumeration value='thin'>
    <xs:annotation>
     <xs:documentation>thin base traditional</xs:documentation>
    </xs:annotation>
   </xs:enumeration>
   <xs:enumeration value='thick'>
    <xs:annotation>
     <xs:documentation>Thick base</xs:documentation>
    </xs:annotation>
   </xs:enumeration>
  </xs:restriction>
 </xs:simpleType>
 <xs:simpleType name='QuantityType'>
  <xs:restriction base='xs:nonNegativeInteger'/>
 </xs:simpleType>
 <xs:simpleType name='CorrelationIdentifierType'>
  <xs:restriction base='xs:token'>
   <xs:maxLength value='44'/>
  </xs:restriction>
 </xs:simpleType>
 <xs:simpleType name='DateType'>
  <xs:annotation>
   <xs:documentation>The date is in the Common Era (minus sign in years is not permitted)</xs:documentation>
  </xs:annotation>
  <xs:restriction base='xs:date'>
   <xs:pattern value='\d{4}-\d{2}-\d{2}'/>
  </xs:restriction>
 </xs:simpleType>
 <xs:simpleType name='TimeType'>
  <xs:annotation>
   <xs:documentation>The time zone although not included UTC is implied</xs:documentation>
  </xs:annotation>
  <xs:restriction base='xs:time'>
   <xs:pattern value='\d{2}:\d{2}:\d{2}(\.\d+)?'/>
  </xs:restriction>
 </xs:simpleType>
</xs:schema>
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?xml version='1.0' encoding='UTF-8'?>
<!-- edited with XMLSpy v2011 sp1 (http://www.altova.com) by Roger Hughes (Marin Solutions Ltd) -->
<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema' xmlns:cust='http://customer.dets' targetNamespace='http://customer.dets' elementFormDefault='qualified' attributeFormDefault='unqualified'>
 <xs:element name='Customer' type='cust:CustomerType'>
  <xs:annotation>
   <xs:documentation>Generic Customer Definition</xs:documentation>
  </xs:annotation>
 </xs:element>
 <xs:complexType name='CustomerType'>
  <xs:sequence>
   <xs:element name='name' type='cust:NameType'/>
   <xs:element name='phone' type='cust:PhoneNumberType'/>
   <xs:element name='address' type='cust:AddressType'/>
  </xs:sequence>
 </xs:complexType>
 <xs:complexType name='NameType'>
  <xs:sequence>
   <xs:element name='firstName' type='cust:FirstNameType'/>
   <xs:element name='lastName' type='cust:LastNameType'/>
  </xs:sequence>
 </xs:complexType>
 <xs:simpleType name='FirstNameType'>
  <xs:annotation>
   <xs:documentation>The Customer's first name</xs:documentation>
  </xs:annotation>
  <xs:restriction base='xs:token'>
   <xs:maxLength value='16'/>
   <xs:pattern value='.{1,16}'/>
  </xs:restriction>
 </xs:simpleType>
 <xs:simpleType name='LastNameType'>
  <xs:annotation>
   <xs:documentation>The Customer's surname</xs:documentation>
  </xs:annotation>
  <xs:restriction base='xs:token'>
   <xs:pattern value='.{1,48}'/>
  </xs:restriction>
 </xs:simpleType>
 <xs:complexType name='AddressType'>
  <xs:sequence>
   <xs:element name='houseNumber' type='cust:HouseNumberType'/>
   <xs:element name='street' type='cust:AddressLineType'/>
   <xs:element name='town' type='cust:AddressLineType' minOccurs='0'/>
   <xs:element name='area' type='cust:AddressLineType' minOccurs='0'/>
   <xs:element name='postCode' type='cust:PostCodeType'/>
  </xs:sequence>
 </xs:complexType>
 <xs:simpleType name='HouseNumberType'>
  <xs:annotation>
   <xs:documentation>The house number</xs:documentation>
  </xs:annotation>
  <xs:restriction base='xs:nonNegativeInteger'/>
 </xs:simpleType>
 <xs:simpleType name='AddressLineType'>
  <xs:annotation>
   <xs:documentation>A line of an address</xs:documentation>
  </xs:annotation>
  <xs:restriction base='xs:token'>
   <xs:pattern value='.{1,100}'/>
  </xs:restriction>
 </xs:simpleType>
 <xs:simpleType name='PhoneNumberType'>
  <xs:restriction base='xs:token'>
   <xs:maxLength value='18'/>
   <xs:pattern value='.{1,18}'/>
  </xs:restriction>
 </xs:simpleType>
 <xs:simpleType name='PostCodeType'>
  <xs:restriction base='xs:token'>
   <xs:maxLength value='10'/>
  </xs:restriction>
 </xs:simpleType>
</xs:schema>

Вы понимаете, что с таким уровнем сложности вы будете долго возиться с SAX, и вы также можете сделать несколько ошибок. Должен быть лучший способ, верно? В конце концов, XML существует уже какое-то время, поэтому должно быть несколько структур, которые могли бы быть полезными. После еще немного Googling вы сталкиваетесь с JAXB и понимаете, что есть …

JAXB или Java-архитектура для связывания XML использует компилятор связывания JAXB xjc для преобразования схемы XML в набор связанных классов Java. Они определяют типы, необходимые для доступа к элементам XML, атрибутам и другому контенту безопасным для типов способом. Этот блог не является учебником, охватывающим все тонкости JAXB, который можно найти здесь, в Oracle и здесь, на страницах Glassfish, Project Metro, JAXB , который также включает в себя учебное пособие ; кроме того, чтобы сказать, что ключевая идея для синтаксического анализа или демаршаллинга XML заключается в том, что вы компилируете свои классы Java с помощью компилятора xjc, а затем используете эти классы, не связанные с API JAXB, для захвата элементов и атрибутов XML.

При использовании любой XML-схемы для компилятора классов Java, самый лучший подход — поместить все ваши схемы и их скомпилированные классы в отдельный файл JAR. Вы можете смешивать их с исходным кодом вашего приложения, но это обычно затуманивает основание кода, усложняя обслуживание. При создании JAR-файла JAXB вы можете создать файл POM, который выглядит примерно так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.captaindebug</groupId>
 <artifactId>xml-tips-jaxb</artifactId>
 <packaging>jar</packaging>
 <version>1.0-SNAPSHOT</version>
 <name>Jaxb for Pete's Perfect Pizza</name>
 <dependencies>
  <dependency>
   <groupId>javax.xml.bind</groupId>
   <artifactId>jaxb-api</artifactId>
   <version>2.0</version>
  </dependency>
 </dependencies>
 <build>
  <plugins>
   <plugin>
    <groupId>com.sun.tools.xjc.maven2</groupId>
    <artifactId>maven-jaxb-plugin</artifactId>
    <executions>
     <execution>
      <goals>
       <goal>generate</goal>
      </goals>
     </execution>
    </executions>
    <configuration>
     <generatePackage>com.captaindebug.jaxb</generatePackage>
     <includeSchemas>
      <includeSchema>**/*.xsd</includeSchema>
     </includeSchemas>
     <strict>true</strict>
     <verbose>true</verbose>
    </configuration>
   </plugin>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>2.3.2</version>
    <configuration>
     <source>1.6</source>
     <target>1.6</target>
    </configuration>
   </plugin>
  </plugins>
 </build>
</project>

… что очень прямо вперед. Итак, возвращаясь к Pete’s Perfect Pizza , вы создали JAR-файл JAXB, и все, что осталось сделать, — это изучить, как он работает, как показано в тестах JUnit ниже:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
@Test
 
public void testLoadPizzaOrderXml() throws JAXBException, IOException {
 
 
 
  InputStream is = loadResource('/pizza-order1.xml');
 
 
 
  // Load the file
 
  JAXBContext context = JAXBContext.newInstance(PizzaOrder.class);
 
  Unmarshaller um = context.createUnmarshaller();
 
  PizzaOrder pizzaOrder = (PizzaOrder) um.unmarshal(is);
 
 
 
  String orderId = pizzaOrder.getOrderID();
 
  assertEquals('123w3454r5', orderId);
 
 
 
  // Check the customer details...
 
  CustomerType customerType = pizzaOrder.getCustomer();
 
 
 
  NameType nameType = customerType.getName();
 
  String firstName = nameType.getFirstName();
 
  assertEquals('John', firstName);
 
  String lastName = nameType.getLastName();
 
  assertEquals('Miggins', lastName);
 
 
 
  AddressType address = customerType.getAddress();
 
  assertEquals(new BigInteger('15'), address.getHouseNumber());
 
  assertEquals('Credability Street', address.getStreet());
 
  assertEquals('Any Town', address.getTown());
 
  assertEquals('Any Where', address.getArea());
 
  assertEquals('AW12 3WS', address.getPostCode());
 
 
 
  Pizzas pizzas = pizzaOrder.getPizzas();
 
  List<PizzaType> pizzasOrdered = pizzas.getPizza();
 
 
 
  assertEquals(3, pizzasOrdered.size());
 
 
 
  // Check the pizza order...
 
  for (PizzaType pizza : pizzasOrdered) {
 
 
 
    PizzaNameType pizzaName = pizza.getName();
 
    if ((PizzaNameType.CAPRICCIOSA == pizzaName) || (PizzaNameType.MARINARA == pizzaName)) {
 
      assertEquals(BaseType.THICK, pizza.getBase());
 
      assertEquals(new BigInteger('1'), pizza.getQuantity());
 
    } else if (PizzaNameType.PROSCIUTTO_E_FUNGHI == pizzaName) {
 
      assertEquals(BaseType.THIN, pizza.getBase());
 
      assertEquals(new BigInteger('2'), pizza.getQuantity());
 
    } else {
 
      fail('Whoops, can't find pizza type');
 
    }
 
  }
 
 
 
}
 
 
 
private InputStream loadResource(String filename) throws IOException {
 
 
 
  InputStream is = getClass().getResourceAsStream(filename);
 
  if (is == null) {
 
    throw new IOException('Can't find the file: ' + filename);
 
  }
 
 
 
  return is;
 
}

Код выше может выглядеть длинным и сложным, но на самом деле он состоит только из трех шагов:

  1. превратить тестовый файл во входной поток. Затем это может быть обработано API JAXB.
  2. создать JAXB-контекст и связанный с ним демаршаллер. Затем он используется для чтения XML и преобразования его в элемент и, если возможно, атрибутные объекты
  3. Используйте возвращенный список классов, чтобы проверить содержимое, как мы и ожидали (это самый большой шаг).

Когда вы довольны стандартным использованием JAXB, вы добавляете его в свой код XML-анализатора своей кухни и распространяете по всему миру среди многочисленных пиццерий Питера.

Одним из сильных сторон использования такой инфраструктуры, как JAXB, является то, что, если когда-либо произойдут изменения в схеме, все, что требуется для включения этих изменений, — это перекомпилировать с использованием XJC, а затем соответствующим образом исправить свой клиентский код. Это может показаться головной болью, но это гораздо меньше головной боли, чем попытка переделать SAX-парсер. С другой стороны, JAXB критиковался за медлительность, но у меня никогда не было слишком много проблем. Теоретически он будет использовать больше памяти, чем SAX — это может или не может быть правдой. Он создает наборы классов, но, опять же, делает некоторые производные классы SAX ContentHandler .

Следует помнить, что JAXB — не единственный инструмент, использующий этот подход, и чтобы продемонстрировать это, мой следующий блог рассказывает ту же историю, но с использованием XMLBeans…

Исходный код доступен на GitHub по адресу:

мерзавец: //github.com/roghughe/captaindebug.git

Перейдите к части 4 серии.

Ссылка: Подходы к XML — Часть 3 — JAXB от нашего партнера по JCG Роджера Хьюза в блоге Captain Debug’s Blog .