Программные интерфейсы JAX-WS Сlient API в Web Services Feature Pack for WebSphere Application Server V6.1: Часть 2. Создание прокси-клиента

Эта серия статей знакомит разработчиков с JAX-WS 2.0, новой моделью программирования, поддерживаемой в Web Services Feature Pack for WebSphere Application Server V6.1. Часть 2 является руководством по созданию динамического прокси-клиента.

Нихил Такер, старший инженер-программист, IBM

Нихил Такер (Nikhil Thaker) - фотографияНихил Такер (Nikhil Thaker) работает старшим инженером-программистом IBM Software Group в группе разработки WebSphere, занимающейся созданием Web Services Feature Pack. Он имеет более чем 9-летний опыт работы в области Enterprise Application Integration (интеграция корпоративных приложений), а в последние 2 года основное внимание уделяет Web-сервисам. Нихил работал с различными клиентами IBM в качестве ИТ-специалиста IBM Global Services по Enterprise Application Integration. Имеет опыт работы в автомобильной отрасли, здравоохранении, телекоммуникациях и сфере коммунальных услуг. С ним можно связаться по адресу nikhil.v.thaker@us.ibm.com.



Дэн Седов, старший инженер-программист, IBM

Дэн Седов (Dan Sedov) - фотографияДэн Седов (Dan Sedov) работает старшим инженером-программистом в IBM Software Group; занимается тестированием функций компонента Web Services Feature Pack сервера WebSphere Application Server. Последние 2 года Дэн является членом группы разработки WebSphere, занимающейся созданием и тестированием первого Web Services Feature Pack. Он работает над созданием и автоматизацией тестов механизма Web-сервисов JAX-WS. С ним можно связаться по адресу sedov@us.ibm.com.



27.12.2011

Введение

В части 1 данной серии статей описываются клиентские программные интерфейсы Java API for XML Web Services (далее называемые JAX-WS) и объясняется использование модели программирования JAX-WS для создания клиента Dispatch. В настоящей статье вы познакомитесь с созданием динамического прокси-клиента. Вы научитесь:

  1. Создавать артефакты JAX-WS, используя имеющиеся инструменты.
  2. Создавать динамический прокси-клиент.
  3. Настраивать контекст запроса.
  4. Вызывать операции интерфейса конечной точки сервиса для создания счета, снятия денег со счета и проверки баланса счета.

Динамические прокси-клиенты JAX-WS 2.0

Динамические прокси обеспечивают во время исполнения доступ к интерфейсам конечной точки сервиса (Service Endpoint Interfaces - SEI) без статического создания класса-заглушки. Реализация JAX-WS обрабатывает прокси при помощи средства динамического прокси платформы Java™ 2 Platform, Standard Edition (J2SE) 5.0. Динамический прокси - это реализующий перечень интерфейсов класс, который создается средой Java Runtime Environment (JRE) во время исполнения. Таким образом, отсутствие в клиентах заглушек - это основное преимущество JAX-WS перед JAX-RPC. Динамические прокси JAX-WS всегда реализуют javax.xml.ws.BindingProvider, поэтому обращение к прокси, подобно клиенту Dispatch, также осуществляется как к BindingProvider.

Пример

На примере простого банковского приложения мы продемонстрируем различные способы использования динамических прокси-клиентов. Мы создадим новый счет, снимем с него деньги и просмотрим данные счета.

В приведенном ниже WSDL-документе определен сервис BankingService с единственным портом AccountsPort. Порт portType BankingSEI определяет три операции: createAccount, withdraw и getAccountInfo.

Листинг 1. WSDL-определение
<?xml version="1.0" encoding="UTF-8"?>
<definitions name="BankingService"
   xmlns="http://schemas.xmlsoap.org/wsdl/"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
   xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
   xmlns:tns="http://www.example.com/services/Banking"
   xmlns:types="http://www.example.com/schemas/Banking"
   targetNamespace="http://www.example.com/services/Banking">

   <types>
      <schema
		xmlns="http://www.w3.org/2001/XMLSchema"
	 	targetNamespace=”http://www.example.com/schemas/Banking”>

		<!--
		определение bean-компонентов запроса и ответа операции createAccount
		-->
		<element name=”createAccount”>
			<complexType>
				<sequence>
					<element name=”owner” type=”string” />
				<element name=”initialBalance” type=”double” />
				</sequence>
			</complexType>
		</element>

		<element name=”createAccountResponse”>
			<complexType>
				<sequence>
					<element name=”accountNumber” type=”long” />
				</sequence>
			</complexType>
		</element>


		<!--
		определение bean-компонентов запроса, ответа и ошибки операции withdraw 
		-->
		<element name=”withdraw”>
			<complexType>
				<sequence>
					<element name=”accountNumber” type=”long” />
					<element name=”amount” type=”double” />
				</sequence>
			</complexType>
		</element>

		<element name=”withdrawResponse”>
			<complexType>
				<sequence>
					<element name=”amount” type=”double” />
				</sequence>
			</complexType>
		</element>

		<element name=”InsufficientFunds”>
			<complexType>
				<sequence>
					<element name=”errorMessage” type=”string” />
					<element name=”errorCode” type=”int” />
				</sequence>
			</complexType>
		</element>

		<!--
		определение bean-компонентов запроса и ответа операции getAccountInfo  
		-->
		<element name=”getAccountInfo”>
			<complexType>
				<sequence>
					<element name=”accountNumber” type=”long” />
				</sequence>
			</complexType>
		</element>

		<element name=”getAccountInfoResponse”>
			<complexType>
				<sequence>
					<element name=”balance” type=”double” />
					<element name=”owner” type=”string” />
				</sequence>
			</complexType>
		</element>
	</schema>
   </types>

   <message name="createAccountRequest">
      <part name="request" element="types:createAccount"/>
   </message>

   <message name="createAccountResponse">
      <part name="response" element=”types:createAccountResponse"/>
   </message>

   <message name="withdrawRequest">
      <part name="request" element=”types:withdraw "/>
   </message>

   <message name="withdrawResponse">
      <part name="response" element=”types:withdrawResponse"/>
   </message>

   <message name="InsufficientFunds">
      <part name="error" element=”types:InsufficientFunds"/>
   </message>

   <message name="getAccountInfoRequest">
      <part name="request" element=”types:getAccountInfo"/>
   </message>

   <message name="getAccountInfoResponse">
      <part name="response" element=”types:getAccountInfoResponse"/>
   </message>

   <portType name="BankingSEI">
      <operation name="createAccount">
         <input message="tns:createAccountRequest"/>
         <output message="tns:createAccountResponse"/>
      </operation>

      <operation name="withdraw">
         <input message="tns:withdrawRequest"/>
         <output message="tns:withdrawResponse"/>
	   <fault message="tns:InsufficientFunds"/>
      </operation>

      <operation name="getAccountInfo">
         <input message="tns:getAccountInfoRequest"/>
         <output message="tns:getAccountInfoResponse"/>
      </operation>
   </portType>
   
   <binding name="BankingSoap11Binding" type="tns:BankingSEI">
      <soap:binding
		style="document" 
		transport="http://schemas.xmlsoap.org/soap/http"/>

      <operation name="createAccount">
         <soap:operation soapAction="createAccount"/>
         <input>	
            <soap:body use="literal"/>
         </input>
         <output>
            <soap:body use="literal"/>
         </output>
      </operation>

      <operation name=" withdraw ">
         <soap:operation soapAction="withdraw"/>
         <input>
            <soap:body use="literal"/>
         </input>
         <output>
            <soap:body use="literal"/>
         </output>
         <fault>
            <soap:fault name=”InsufficientFunds” use="literal"/>
         </fault>
      </operation>

      <operation name="getAccountInfo">
         <soap:operation soapAction="getAccountInfo"/>
         <input>	
            <soap:body use="literal"/>
         </input>
         <output>
            <soap:body use="literal"/>
         </output>
      </operation>

   </binding>

   <service name="BankingService">
      <port binding="tns: BankingSoap11Binding" name="AccountsPort">
         <soap:address
             location="http://localhost:8080/banking/services/BankingService"/>
      </port>
   </service>
</definitions>

Рассмотрим простой динамический прокси-клиент, который создает счет, снимает деньги и проверяет баланс счета:

Листинг 2. Клиент WJAX-WS – пример динамического прокси
// Создание динамического прокси-клиента
BankingService service = new BankingService();
BankingSEI port = service.getAccountsPort();

// Использование экземпляра прокси как BindingProvider
BindingProvider bp = (BindingProvider) port;

// (Необязательно) Настройка RequestContext с использование URL конечной точки
Map<String, Object> rc = bp.getRequestContext();
rc.put (BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
"http://localhost:9080/hello/services/BankingService”);

// Создание счета с 10 долларами на нем
int accountNumber = port.createAccount("Joe Customer”, 10.00);

// Снятие денег
try{
port.withdraw(accountNumber, 1000.00);
} catch (InsufficientFundsException ife){
	InsufficientFunds if = ife.getFaultInfo();
	System.out.println("Error message: ” + if.getMessage());
}

// получение данных счета
Holder<String> owner = new Holder<String>();
Holder<Double> balance = new Holder<Double>();

port.getAccountInfo(accountNumber, owner, balance);
System.out.println("Account number " + accountNumber);
System.out.println("Account belongs to" + owner.getValue());
System.out.println("Account balance is " + balance.getValue());

В приведенном выше примере можно выделить следующие этапы вызова операции в конечной точке с использованием программного интерфейса динамического прокси:

  1. Создание динамического прокси-клиента.
  2. Настройка контекста запроса.
  3. Создание нового счета.
  4. Снятие некоторой суммы.
  5. Получение баланса счета.

Этап 1. Создание артефактов JAX-WS с использованием инструментальных средств

В части 1 говорилось, что динамические прокси-клиенты скрывают сложность работы с программными интерфейсами XML, представляя XML с помощью технологии связывания данных JAXB 2.0. Это означает, что до создания клиента нужно передать через инструменты JAX-WS WSDL-документ для формирования необходимых артефактов. В Web Services Feature Pack for WebSphere Application Server V6.1 можно использовать либо команду wsImport, расположенную по адресу WAS_HOME/bin/wsimport, либо задание Ant wsImport, либо Application Server Toolkit. Команда wsImport создает приведенные ниже артефакты в соответствии с WSDL-файлом из листинга 1.

Вот простой пример использования wsImport с WSDL-файлом:

<cmd>wsimport bankingservice.wsdl –d c:\app\classfiles –keep –s c:\app\javafiles

Создаваемые инструментальными средствами артефакты приведены в следующих таблицах.

Артефакты схемы JAX-B Назначение
CreateAccount
ResponseWithdraw
WithdrawResponse
InsufficientFunds
GetAccountInfo
GetAccountInfo
Response
Bean-компоненты XML, каждый из которых представляет XML-элемент.
ObjectFactory

Фабрика для XML-элементов и complexTypes, определяемых этой схемой. Если WSDL ссылается на несколько схем, каждый сгенерированный пакет имеет свой собственный ObjectFactory.

WSDL-артефакты JAX-WS Назначение
InsufficientFundsException Throwable-исключение, содержащее bean-компонент InsufficientFunds.
BankingSEI Java-интерфейс, представляющий portType.
BankingService Java-эквивалент WSDL-сервиса. Этот класс содержит методы getPort для каждого порта, отображаемого в этот WSDL-сервис.

Этап 2. Создание динамического прокси-клиента

В предыдущем разделе говорилось, что инструментальные средства JAX-WS создают конкретный класс реализации Service, также называемый статическим сервисом. В созданном классе есть два общедоступных конструктора: один без аргументов и второй с двумя аргументами, указывающими расположение WSDL (java.net.URL) и имя сервиса (javax.xml.namespace.QName) соответственно.

Service - это абстракция, представляющая WSDL-сервис. Экземпляр Service может быть динамическим сервисом, как описано в части 1, или статическим сервисом. В данной статье основное внимание уделено использованию статического сервиса.

Для создания экземпляра Service J2SE-клиент может использовать класс BankingService посредством двух его конструкторов:

  • BankingService(): это способ по умолчанию для создания сервиса в предположении, что WSDL находится там же, где и созданные артефакты (либо в локальном файле, либо по URL-адресу).
  • BankingService(javax.xml.namespace.QName, java.net.URL): этот способ используется для указания нового местоположения WSDL. Прежде чем вызвать конечную точку, необходимо проанализировать WSDL, чтобы прочитать информацию о связывании и адресе конечной точки.
Листинг 3. Создание статического экземпляра Service
// Создание статического экземпляра Service
URL wsdlLocation =
    new 
URL("http://localhost:9080/banking/services/BankingService?wsdl");
QName serviceName =
  new Qname("http://www.example.com/services/BankingService",
"BankingService");

BankingService service = new BankingService(serviceName, wsdlLocation);

Во время выполнения методом getAccountsPort() класса BankingService создается заглушка. Выше уже говорилось, что созданный класс Service должен иметь метод getPort для каждого порта, определенного в WSDL-сервисе.

Листинг 4. Использование статического сервиса для создания прокси
// Создание динамического прокси-клиента
BankingSEI port = service.getAccountsPort();

Этап 3. Настройка контекста запроса

В программных интерфейсах JAX-WS Сlient API есть интерфейс BindingProvider для работы с отдельными контекстами на этапах запроса и ответа при обмене сообщениями с конечной точкой. Контексты запроса и ответа имеют тип Map<String, Object> и извлекаются при помощи методов getRequestContext и getResponseContext класса BindingProvider.

Ниже приведены стандартные свойства, которые можно настроить для контекста запроса после создания экземпляра сервиса:

СвойствоНазначение
javax.xml.ws.service.endpoint.address Адрес конечной точки сервиса в виде зависящего от протокола URI. Схема URI должна соответствовать используемому связыванию протокола.
javax.xml.ws.security.auth.usernamejavax.xml.ws.security.auth.password Имя пользователя и пароль для HTTP-аутентификации.
javax.xml.ws.session.maintain Показывает, что клиент будет участвовать в HTTP-сеансе.
javax.xml.ws.soap.http.soapaction.usejavax.xml.ws.soap.http.soapaction.uri Определяет, используется ли HTTP-заголовок SOAPAction в SOAP-пакете поверх HTTP-запросов.

Ниже приведены свойства, специфичные для WebSphere:

СвойствоНазначение
com.ibm.wsspi.websvcs.Constants.READ_TIMEOUT_PROPERTY
com.ibm.wsspi.websvcs.Constants.WRITE_TIMEOUT_PROPERTY
Определят таймауты отправки и получения данных в миллисекундах. Изменяйте эти свойства только при медленном соединении с сервером или при передаче больших объемов данных.

В следующем примере указанный в WSDL адрес конечной точки заменяется нашим собственным адресом:

Листинг 5. Настройка контекста запроса
// (Необязательно) Настройка RequestContext с использование URL конечной точки
Map<String, Object> rc = bp.getRequestContext();
rc.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, 
"http://www.example.com:9080/banking/services/BankingService");

Совет по повышению производительности. Желание изменить адрес может вызвать недоумение, поскольку Web-сервисы обычно являются общеизвестными и общедоступными. Однако с обращением к WSDL посредством "?wsdl" связано несколько проблем:

  1. При создании экземпляра сервиса WSDL-документ необходимо анализировать.
  2. Сетевые задержки снижают производительность, особенно если WSDL и его схемы имеют большие размеры.
  3. В процессе работы общедоступные WSDL (такие как eBay или Amazon) могут быть изменены новыми операциями, что приведет к несоответствию между сгенерированным интерфейсом конечной точки сервиса (SEI) и WSDL и заставит повторно создавать артефакты.

По этим причинам рекомендуется хранить WSDL локально и всегда использовать приведенный выше пример для указания актуальной конечной точки.


Этап 4. Вызов SEI-операций

В следующих разделах описываются создание нового счета, снятие денег и проверка баланса счета.

Создание нового счета

Первый пример, представляющий типичную операцию, очень прост. Мы создадим новый счет с начальным балансом 10 долларов. Сервис возвращает номер только что созданного счета:

Листинг 6. Вызов SEI-операции с использованием прокси
// Создание счета с 10 долларами на нем
int accountNumber = port.createAccount("Joe Customer", 10.00);

Может возникнуть вопрос, почему используются параметры owner и initialBalance, а не элемент createAccount. Причина заключается в том, что наш WSDL следует упакованному документально-литеральному (document/literal-wrapped) шаблону. Упакованный документально-литеральный шаблон имеет следующие характеристики:

  • Входное сообщение состоит из одной части, которая должна быть элементом.
  • Операция имеет то же имя, что и элемент.
  • Сложный тип элемента не имеет атрибутов.

Невыполнение любого из вышеуказанных условий просто означало бы использование обычного документально-литерального шаблона, а сигнатура операции выглядела бы следующим образом:

Листинг 7. Пример документально-литеральной операции (неупакованной)
CreateAccountResponse createAccount(CreateAccount request);

Снятие денег

В этом примере мы продемонстрируем ошибки Web-сервисов и соответствующие им исключительные ситуации Java при вызове WSDL-операции с использованием прокси. Чтобы показать ошибки Web-сервиса, давайте снимем больше денег, чем есть на счете. Большинство банков идентифицируют эту ситуацию как ошибку пользователя и блокируют снятие денег.

В листинге 1 представлена WSDL-ошибка InsufficientFunds. Эта ошибка является альтернативным результатом операции снятия денег. В соответствии с правилами отображения WSDL в Java, определенными спецификацией JAX-WS, к имени ошибки, указанному в разделе binding WSDL-документа, добавляется Exception. Поэтому ошибка InsufficientFunds превращается в InsufficientFundsException, которая становится Java Exception.

Листинг 8. Фрагменты WSDL, демонстрирующие операцию снятия денег
<schema>
	<element name=”InsufficientFunds”>
		<complexType>
			<sequence>
				<element name=”errorMessage” type=”string” />
				<element name=”errorCode” type=”int” />
			</sequence>
		</complexType>
	</element>
</schema>

<message name="InsufficientFunds">
      <part name="error" element=”types:InsufficientFunds"/>
</message>

<portType name="BankingSEI">
	<operation name="withdraw">
         <input message="tns:withdrawRequest"/>
         <output message="tns:withdrawResponse"/>
	    <fault message="tns:InsufficientFunds"/>
      </operation>
</portType>

<binding name="BankingSoap11Binding" type="tns:BankingSEI">

      <operation name=" withdraw ">
         <soap:operation soapAction="withdraw"/>
         <input>
            <soap:body use="literal"/>
         </input>
         <output>
            <soap:body use="literal"/>
         </output>
         <fault>
            <soap:fault name=”InsufficientFunds” use="literal"/>
         </fault>
      </operation>
</binding>

Как можно увидеть в листинге 9, вызов такой операции снятия денег со счета, на котором меньше 1000 долларов, приводит к возникновению InsufficientFundsException. Эту исключительную ситуацию можно перехватить и выделить bean-компонент ошибки InsufficientFunds при помощи метода getFaultInfo().

Листинг 9. Операция снятия денег генерирует исключительную ситуацию
// Снятие денег
try{
port.withdraw(accountNumber, 1000.00);
} catch (InsufficientFundsException ife){

//getFaultInfo возвращает bean-компонент InsufficientFunds.
	InsufficientFunds if = ife.getFaultInfo();
	System.out.println("Error message: " + if.getMessage());
}

Обратите внимание, что хотя это не указывается явно, при возникновении каких-либо ошибок при передаче запроса на сервер операция снятия денег сгенерирует исключительную ситуацию javax.xml.ws.WebServiceException.

Получение баланса счета

Теперь проверим баланс счета и посмотрим, взимал ли банк какие-либо комиссионные. Ожидается, что операция возвратит несколько значений, а именно баланс счета и имя владельца счета. Поскольку Java не поддерживает многозначные функции, мы введем в эту операцию классы holder.

Рассмотрим элементы схемы, используемые в операции getAccountInfo. Эти элементы очень похожи на те, которые использовались в предыдущей операции, но элемент вывода состоит из двух частей, а не из одной:

Листинг 10. Определения элементов для getAccountInfo и getAccountInforResponse
<element name="getAccountInfo">
	<complexType>
		<sequence>
			<element name="accountNumber" type="long" />
		</sequence>
	</complexType>
</element>

<element name="getAccountInfoResponse">
	<complexType>
		<sequence>
			<element name="balance" type="double" />
			<element name="owner" type="string" />
		</sequence>
	</complexType>
</element>

Рассмотрим различные типы параметров этой операции:

Параметр Тип Пример getAccountInfo
wsdl:inputINТолько accountNumber представлен в getAccountInfo.
wsdl:outputOUTТолько owner и balanceare представлены в getAccountInfoResponse.
wsdl:input и wsdl:outputINOUT

Классы holder используются для поддержки параметров OUT и INOUT в отображенных сигнатурах методов. Они предоставляют изменяемую оболочку для других неизменяемых объектных ссылок. JAX-WS определяет общий holder-класс javax.xml.ws.Holder<T>, который можно использовать для любого Java-класса. Ниже приведено соответствующее описание операции getAccountInfo. Поскольку используются классы holder, метод объявляется как void.

Листинг 11. Операции getAccountInfo
void getAccountInfo(int accountNumber, Holder<String> owner,
                                             Holder<Double> balance);

Ниже приведен код создания holder, вызова операции и чтения данных из holder:

Листинг 12. Извлечение информации из holder
// Создание параметров Holder
Holder<String> owner = new Holder<String>();
Holder<Double> balance = new Holder<Double>();

// Получение информации о счете
port.getAccountInfo(accountNumber, owner, balance);

System.out.println("Account number " + accountNumber);
System.out.println("Account belongs to" + owner.getValue());
System.out.println("Account balance is " + balance.getValue());

Обратите внимание, что при вызове операции параметры owner и balance не инициализируются. Причина в том, что оба они являются параметрами типа OUT и используются только для предоставления возвращаемых значений. Конечная точка предоставит эти значения в ответе на запрос.


Заключение

Динамические прокси обеспечивают простой, не требующий глубоких знаний базового XML механизм вызова Web-сервиса. Использование прокси не требует от разработчика понимания связывания данных между Java и XML, поскольку базовая реализация JAX-WS обрабатывает все преобразования. Таким образом, разработчикам приложений не нужно понимать базовую XML-схему или программные интерфейсы формирования XML. Тем не менее разработчики приложений, использующие прокси, должны разбираться в различных типах WSDL, понимать разницу между документально-литеральными и RPC/литеральными WSDL, а также между упакованными и неупакованными сообщениями. Зная WSDL-типы, можно понять, какие SOAP-сообщения циркулируют между отправителем и получателем и как SOAP-сообщения используются сервером для разрешения операции конечной точки.

Ресурсы

Научиться

Получить продукты и технологии

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=WebSphere, SOA и web-сервисы
ArticleID=783108
ArticleTitle=Программные интерфейсы JAX-WS Сlient API в Web Services Feature Pack for WebSphere Application Server V6.1: Часть 2. Создание прокси-клиента
publish-date=12272011