Советы и приемы программирования Web-служб: Импорт в WSDL-файлах

В данной статье объясняются нюансы двух типов операторов import, используемых в Web Services Description Language (WSDL, язык описания Web-служб)

Russell Butek (butek@us.ibm.com)
Инженер-программист, IBM

Содержание

1 Import против include
2 XSD-импорт
3 WSDL-импорт
4 Резюме
5 Ресурсы
6 Об авторах

Операторы import являются простыми, не так ли? Они есть почти в каждом языке программирования или языке описания интерфейсов; если вы читаете эту статью, то, возможно, знаете все, что необходимо знать об import. Итак, почему вы должны прочитать совет по оператору import в файлах Web Services Description Language (WSDL)? Во-первых, существуют два типа операторов import: XSD import и WDSL import. Во-вторых, их соответствующее поведение не совсем идентично. И, в-третьих, хорошо бы знать взаимосвязь между ними.

Import против include

Перед погружением в описание операторов import, разрешите мне сказать о различии import и include. Оператор import работает в другом пространстве имен. Оператор include включает другие описания в текущее пространство имен.

XSD-импорт

Рассмотрим основной XSD import, например, показанный красным цветом в листинге 1. Все что этот оператор делает - это импорт пространства имен из одной схемы в другую. Схема, определяющая пространство имен urn:listing2, импортирует схему urn:listing3. Это все. Никакой файл не импортируется. Обе схемы находятся в одном том же самом файле в листинге 1.

Листинг 1. Адресная книга WSDL, использующая два пространства имен

<?xml version="1.0" ?>
<wsdl:definitions targetNamespace="urn:listing2"
                  xmlns:tns="urn:listing2"
                  xmlns:listing3="urn:listing3"
                  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
  <wsdl:types>
    <xsd:schema targetNamespace="urn:listing3"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      
          <xsd:import namespace="http://www.w3.org/2001/XMLSchema"/>
        
      <xsd:complexType name="Phone">
        <xsd:sequence>
          <xsd:element name="areaCode" type="xsd:int"/>
          <xsd:element name="exchange" type="xsd:int"/>
          <xsd:element name="number" type="xsd:int"/>
        </xsd:sequence>
      </xsd:complexType>
    </xsd:schema>
    <xsd:schema targetNamespace="urn:listing2"
                xmlns:listing3="urn:listing3"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      
          <xsd:import namespace="urn:listing3"/>






<xsd:import namespace="http://www.w3.org/2001/XMLSchema"/>
        
      <xsd:complexType name="Address">
        <xsd:sequence>
          <xsd:element name="streetNum" type="xsd:int"/>
          <xsd:element name="streetName" type="xsd:string"/>
          <xsd:element name="city" type="xsd:string"/>
          <xsd:element name="state" type="xsd:string"/>
          <xsd:element name="phone" type="listing3:Phone"/>
        </xsd:sequence>
      </xsd:complexType>
    </xsd:schema>
  </wsdl:types>

  <wsdl:message name="GetAddressRequest">
    <wsdl:part name="name" type="xsd:string"/>
  </wsdl:message>
  <wsdl:message name="GetAddressResponse">
    <wsdl:part name="address" type="tns:Address"/>
  </wsdl:message>
  <wsdl:message name="GetPhoneRequest">
    <wsdl:part name="name" type="xsd:string"/>
  </wsdl:message>
  <wsdl:message name="GetPhoneResponse">
    <wsdl:part name="phone" type="listing3:Phone"/>
  </wsdl:message>

  <wsdl:portType name="AddressBook">
    <wsdl:operation name="getAddress">
      <wsdl:input message="tns:GetAddressRequest"/>
      <wsdl:output message="tns:GetAddressResponse"/>
    </wsdl:operation>
    <wsdl:operation name="getPhone">
      <wsdl:input message="tns:GetPhoneRequest"/>
      <wsdl:output message="tns:GetPhoneResponse"/>
    </wsdl:operation>
  </wsdl:portType>
</wsdl:definitions>

Надеюсь, из листинга 1 ясно, что главной целью оператора import является импорт пространства имен. Более распространенным использованием оператора XSD import является импорт пространства имен, которое появляется в другом файле. Вы можете собрать информацию по пространству имен из файла, но не забудьте, что импортируете пространство имен, а не файл (не путайте оператор import с include).

При импортировании пространства имен из файла вы можете обнаружить атрибут schemaLocation в операторе XSD import, но это необязательный атрибут. Как показано в листинге 1, schemaLocation не обязателен, поскольку пространство имен оператора import находится там же (в том же файле), где и сам оператор import. Фактически, если вы укажете место расположение файла (как в листинге 2), XML-парсер может проигнорировать это по своему усмотрению. Атрибут schemaLocation - это попросту подсказка. Если парсер уже знает о типах схемы в этом пространстве имен или имеет какие-либо другие средства их обнаружения, он не обязан переходить в указанное вами место. Такое поведение должно быть для вас еще одним напоминанием о том, что основным назначением оператора XSD import является импортирование пространства имен, а не указание местонахождения объявлений в нем. Естественно, что чаще всего вы будете импортировать пространство имен, о котором XML-парсер ничего не знает, поэтому атрибут schemaLocation становится необходимым и легко забыть, что он является только подсказкой.

Теперь рассмотрим операторы import, выделенные в листинге 1 синим цветом. Поскольку я использую XSD-пространство имен, я должен явно импортировать его. Но это пространство имен является общим. Практически каждый XML-парсер знает о нем. Большинство парсеров часто не требуют указания для него оператора import. Множество инструментальных средств даже не требуют от вас включения оператора import, выделенного красным цветом (в конце концов, импортируемое пространство имен находится в этом же файле), но у вас должно войти в привычку включение оператора import для всех используемых вами пространств имен. Вы не можете знать, что вы или кто-нибудь другой, использующий ваш WSDL-файл, не будет работать с более взыскательными программами.

Еще раз убедитесь, что используемое вами в операторе import пространство имен является таким же, что и targetNamespace импортируемой схемы. Совершенно очевидно, что в показанном в листинге 1 примере вы должны сделать это. Но если вы переместите схему urn:listing3 в файл с именем listing3.xml и импортируете этот файл (как в листинге 2), то это может стать не очевидным. Фактически, это может выглядеть так, как будто вы изменяете пространство имен схемы в файле, используя отличный от указанного в targetNamespace атрибут оператора import. Это является ошибкой. Вы не можете изменять пространства имен. Атрибут пространства имен оператора import должен соответствовать targetNamespace схемы.

Листинги 2 и 3 получены из листинга 1. Листинг 2 - это листинг 1 с перемещенной схемой Phone в другой файл - листинг 3. Оператор import листинга 2 теперь включает атрибут schemaLocation (выделенный синим цветом). Это рекомендуемый способ импорта схемы из файла.

Листинг 2. Адресная книга WSDL, импортирующая XSD-файл для схемы Phone

<?xml version="1.0" ?>
<wsdl:definitions targetNamespace="urn:listing2"
                  xmlns:tns="urn:listing2"
                  xmlns:listing3="urn:listing3"
                  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
  <wsdl:types>
    <xsd:schema targetNamespace="urn:listing2"
                xmlns:listing3="urn:listing3"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <xsd:import namespace="urn:listing3" 
          schemaLocation="listing3.xsd"
        />
      <xsd:import namespace="http://www.w3.org/2001/XMLSchema"/>
      <xsd:complexType name="Address">
        <xsd:sequence>
          <xsd:element name="streetNum" type="xsd:int"/>
          <xsd:element name="streetName" type="xsd:string"/>
          <xsd:element name="city" type="xsd:string"/>
          <xsd:element name="state" type="xsd:string"/>
          <xsd:element name="phone" type="listing3:Phone"/>
        </xsd:sequence>
      </xsd:complexType>
    </xsd:schema>
  </wsdl:types>

  <wsdl:message name="GetAddressRequest">
    <wsdl:part name="name" type="xsd:string"/>
  </wsdl:message>
  <wsdl:message name="GetAddressResponse">
    <wsdl:part name="address" type="tns:Address"/>
  </wsdl:message>
  <wsdl:message name="GetPhoneRequest">
    <wsdl:part name="name" type="xsd:string"/>
  </wsdl:message>
  <wsdl:message name="GetPhoneResponse">
    <wsdl:part name="phone" type="listing3:Phone"/>
  </wsdl:message>

  <wsdl:portType name="AddressBook">
    <wsdl:operation name="getAddress">
      <wsdl:input message="tns:GetAddressRequest"/>
      <wsdl:output message="tns:GetAddressResponse"/>
    </wsdl:operation>
    <wsdl:operation name="getPhone">
      <wsdl:input message="tns:GetPhoneRequest"/>
      <wsdl:output message="tns:GetPhoneResponse"/>
    </wsdl:operation>
  </wsdl:portType>
</wsdl:definitions>

Листинг 3. XSD-файл для схемы Phone

<?xml version="1.0" ?>
<xsd:schema targetNamespace="urn:listing3"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <xsd:import namespace="http://www.w3.org/2001/XMLSchema"/>
  <xsd:complexType name="Phone">
    <xsd:sequence>
      <xsd:element name="areaCode" type="xsd:int"/>
      <xsd:element name="exchange" type="xsd:int"/>
      <xsd:element name="number" type="xsd:int"/>
    </xsd:sequence>
  </xsd:complexType>
</xsd:schema>

WSDL-импорт

Взгляните на листинги 4 и 5. Они почти такие же, как и листинги 2 и 3. Листинг 4 импортирует листинг 5 как листинг 2 импортирует листинг 3. Но сейчас я использую WSDL-импорт вместо XSD-импорта. Отличия между листингом 2 и листингом 4 выделены синим цветом в листинге 4. Аналогично, отличия между листингом 3 и листингом 5 выделены синим цветом в листинге 5.

Листинг 4. Адресная книга WSDL, импортирующая WSDL-файл для схемы Phone

<?xml version="1.0" ?>
<wsdl:definitions targetNamespace="urn:listing4"
                  xmlns:tns="urn:listing4"
                  xmlns:listing5="urn:listing5"
                  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
  
          <wsdl:import namespace="urn:listing5" location="listing5.wsdl"/>
        
  <wsdl:types>
    <xsd:schema targetNamespace="urn:listing4"
                xmlns:listing5="urn:listing5"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <xsd:import namespace="http://www.w3.org/2001/XMLSchema"/>
      <xsd:complexType name="Address">
        <xsd:sequence>
          <xsd:element name="streetNum" type="xsd:int"/>
          <xsd:element name="streetName" type="xsd:string"/>
          <xsd:element name="city" type="xsd:string"/>
          <xsd:element name="state" type="xsd:string"/>
          <xsd:element name="phone" 
          type="listing5:Phone"
        />
        </xsd:sequence>
      </xsd:complexType>
    </xsd:schema>
  </wsdl:types>

  <wsdl:message name="GetAddressRequest">
    <wsdl:part name="name" type="xsd:string"/>
  </wsdl:message>
  <wsdl:message name="GetAddressResponse">
    <wsdl:part name="address" type="tns:Address"/>
  </wsdl:message>
  <wsdl:message name="GetPhoneRequest">
    <wsdl:part name="name" type="xsd:string"/>
  </wsdl:message>
  <wsdl:message name="GetPhoneResponse">
    <wsdl:part name="phone" 
          type="listing5:Phone"
        />
  </wsdl:message>

  <wsdl:portType name="AddressBook">
    <wsdl:operation name="getAddress">
      <wsdl:input message="tns:GetAddressRequest"/>
      <wsdl:output message="tns:GetAddressResponse"/>
    </wsdl:operation>
    <wsdl:operation name="getPhone">
      <wsdl:input message="tns:GetPhoneRequest"/>
      <wsdl:output message="tns:GetPhoneResponse"/>
    </wsdl:operation>
  </wsdl:portType>
</wsdl:definitions>

Листинг 5. WSDL-файл для схемы Phone

<?xml version="1.0" ?>

          <wsdl:definitions targetNamespace="urn:listing5"
                  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <wsdl:types>
        
    <xsd:schema targetNamespace="urn:listing5"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <xsd:import namespace="http://www.w3.org/2001/XMLSchema"/>
      <xsd:complexType name="Phone">
        <xsd:sequence>
          <xsd:element name="areaCode" type="xsd:int"/>
          <xsd:element name="exchange" type="xsd:int"/>
          <xsd:element name="number" type="xsd:int"/>
        </xsd:sequence>
      </xsd:complexType>
    </xsd:schema>
  
          </wsdl:types>
</wsdl:definitions>

Нужно ли это делать? Если вы запустите любимую программу преобразования WSDL в Java с listing4.wsdl, то получите сообщение об ошибке. В листинге 4 я выделил две ссылки на тип Phone: одну зеленым цветом, другую красным. Зеленая ссылка находится в операторе WSDL message. Этот оператор находит Phone, поскольку является WSDL-оператором, а WSDL-файл импортирует Phone через WSDL-оператор import. Красная ссылка находится в схеме. Эта ссылка не находит схему Phone, поскольку она не импортируется через XSD-оператор import. Вы не можете выйти из схемы для поиска других схем. Вы должны импортировать схемы внутри схем.

Если тип Address не содержит элемент phone и, таким образом, не ссылается на пространство имен urn:listing5, то эта пара (листинг 4 и листинг 5) будет допустимой. Но это не является хорошей практикой импорта информации о схеме WSDL-оператором import. Листинги 2 и 3 предпочтительней листингов 4 и 5. Используйте XSD-импорт для импорта схем. Используйте WSDL-импорт для импорта WSDL.

В качестве примера правильного WSDL-импорта обратите внимание на листинг 4, который не содержит ни операторы binding, ни операторы service. Возможно какой-нибудь другой файл, содержащий binding и service, будет импортировать listing4.wsdl через WSDL-оператор import.

Пара последних комментариев по WSDL-импорту. Аналогично XSD-импорту, атрибут namespace WSDL-оператора import должен быть таким же, как targetNamespace импортируемого WSDL. Атрибут location WSDL-оператора import, аналогично атрибуту schemaLocation XSD-оператора import, является просто подсказкой. Однако, в отличие от schemaLocation, присутствие атрибута location WSDL-оператора import необходимо. (Это не ясно из спецификации WSDL 1.1, но Basic Profile на Web-сайте WS-I указывает на это. (См. раздел Ресурсы.)

Резюме

В данной статье сделана попытка рассказать о следующем:

  • Хорошей практикой является использование XSD-импорта для импорта схем и WSDL-импорта для импорта WSDL.
  • Хорошей практикой является импортирование всех используемых вами пространств имен.
  • Значение атрибута импортируемого пространства имен должно соответствовать импортируемому значению targetNamespace.
  • Основным назначением оператора import является импорт пространств имен. Атрибуты schemaLocation и location фактически являются только подсказками, хотя иногда необходимы.

Ресурсы

Об авторах

Russell Butek является консультантом IBM по Web-службам. Он является одним из разработчиков системы IBM WebSphere Web services, а также членом экспертной группы JAX-RPC Java Specification Request (JSR). Russell Butek участвует в реализации системы AXIS SOAP в Apache, приводя AXIS 1.0 в соответствие с JAX-RPC 1.0. Ранее он был разработчиком IBM CORBA ORB и представителем IBM во многих специальных комиссиях OMG, включая председательство в комиссии portable interceptor. Связаться с Russell можно на butek us.ibm.com.