В части 1 данной серии статей описываются клиентские программные интерфейсы Java API for XML Web Services (далее называемые JAX-WS) и объясняется использование модели программирования JAX-WS для создания клиента Dispatch. В настоящей статье вы познакомитесь с созданием динамического прокси-клиента. Вы научитесь:
- Создавать артефакты JAX-WS, используя имеющиеся инструменты.
- Создавать динамический прокси-клиент.
- Настраивать контекст запроса.
- Вызывать операции интерфейса конечной точки сервиса для создания счета, снятия денег со счета и проверки баланса счета.
Динамические прокси-клиенты 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. Создание артефактов 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" связано несколько проблем:
- При создании экземпляра сервиса WSDL-документ необходимо анализировать.
- Сетевые задержки снижают производительность, особенно если WSDL и его схемы имеют большие размеры.
- В процессе работы общедоступные WSDL (такие как eBay или Amazon) могут быть изменены новыми операциями, что приведет к несоответствию между сгенерированным интерфейсом конечной точки сервиса (SEI) и WSDL и заставит повторно создавать артефакты.
По этим причинам рекомендуется хранить WSDL локально и всегда использовать приведенный выше пример для указания актуальной конечной точки.
В следующих разделах описываются создание нового счета, снятие денег и проверка баланса счета.
Первый пример, представляющий типичную операцию, очень прост. Мы создадим новый счет с начальным балансом 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:input
|
IN
| Только accountNumber представлен в getAccountInfo.
|
wsdl:output
|
OUT
| Только owner и balanceare представлены в getAccountInfoResponse. |
wsdl:input и wsdl:output
|
INOUT
|
Классы 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-сообщения используются сервером для разрешения операции конечной точки.
Научиться
- Оригинал статьи: JAX-WS client APIs in the Web Services Feature Pack for WebSphere Application Server V6.1, Part 2: Creating a proxy client (EN).
- Программные интерфейсы JAX-WS Сlient API в Web Services Feature Pack for WebSphere Application Server V6.1, часть 1: создание клиента Dispatch (developerWorks, 2007 год): часть 1 данной серии является руководством по созданию клиента Dispatch при помощи различных программных интерфейсов JAX-WS Сlient API .
-
JSR 224: Java API for XML-Based Web Services (JAX-WS) 2.0: спецификация.
-
JSR 181: Web Services Metadata for the Java Platform: спецификация.
-
Web Services Description Language (WSDL) 1.1: спецификация WSDL.
-
SOAP 1.2 Primer: введение в спецификацию SOAP 1.2.
-
Профили WS-I: особенно важен базовый профиль.
Получить продукты и технологии

Нихил Такер (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.

Дэн Седов (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.