Перейти к основному содержимому

Вызов веб-службы SOAP в Java

· 7 мин. чтения

1. Обзор

В этом руководстве мы узнаем, как создать клиент SOAP на Java с JAX-WS RI в Java 8 и 11.

Сначала мы создадим клиентский код с помощью утилиты wsimport, а затем протестируем его с помощью JUnit.

Для тех, кто только начинает, наше введение в JAX-WS предоставляет отличную информацию по этому вопросу.

2. Веб-сервис

Прежде чем мы начнем создавать клиент, нам нужен сервер. В этом случае нам нужен сервер, предоставляющий веб-службу JAX-WS.

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

2.1. Резюме реализации

Поскольку мы сосредоточены на создании клиента, мы не будем вдаваться в подробности реализации нашего сервиса.

Предположим, что интерфейс CountryService используется для представления веб-службы внешнему миру. Для простоты мы создадим и развернем веб-службу, используя API javax.xml.ws.Endpoint в нашем классе CountryServicePublisher .

Мы запустим CountryServicePublisher как приложение Java, чтобы опубликовать конечную точку, которая будет принимать входящие запросы. Другими словами, это будет наш сервер.

После запуска сервера переход по URL-адресу http://localhost:8888/ws/country?wsdl дает нам файл описания веб-службы. WSDL действует как руководство для понимания предложений службы и создания кода реализации для клиента.

2.2. Язык описания веб-сервисов

Давайте посмотрим на WSDL нашего веб-сервиса, страна :

<?xml version="1.0" encoding="UTF-8"?>
<definitions <!-- namespace declarations -->
targetNamespace="http://server.ws.soap.foreach.com/" name="CountryServiceImplService">
<types>
<xsd:schema>
<xsd:import namespace="http://server.ws.soap.foreach.com/"
schemaLocation="http://localhost:8888/ws/country?xsd=1"></xsd:import>
</xsd:schema>
</types>
<message name="findByName">
<part name="arg0" type="xsd:string"></part>
</message>
<message name="findByNameResponse">
<part name="return" type="tns:country"></part>
</message>
<portType name="CountryService">
<operation name="findByName">
<input wsam:Action="http://server.ws.soap.foreach.com/CountryService/findByNameRequest"
message="tns:findByName"></input>
<output wsam:Action="http://server.ws.soap.foreach.com/CountryService/findByNameResponse"
message="tns:findByNameResponse"></output>
</operation>
</portType>
<binding name="CountryServiceImplPortBinding" type="tns:CountryService">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="rpc"></soap:binding>
<operation name="findByName">
<soap:operation soapAction=""></soap:operation>
<input>
<soap:body use="literal" namespace="http://server.ws.soap.foreach.com/"></soap:body>
</input>
<output>
<soap:body use="literal" namespace="http://server.ws.soap.foreach.com/"></soap:body>
</output>
</operation>
</binding>
<service name="CountryServiceImplService">
<port name="CountryServiceImplPort" binding="tns:CountryServiceImplPortBinding">
<soap:address location="http://localhost:8888/ws/country"></soap:address>
</port>
</service>
</definitions>

В двух словах, это полезная информация, которую он предоставляет:

  • Мы можем вызвать метод findByName со строковым аргументом.
  • В ответ сервис вернет нам пользовательский тип страны .
  • Типы определяются в схеме xsd , сгенерированной по адресу http://localhost:8888/ws/country?xsd=1 :
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema <!-- namespace declarations -->
targetNamespace="http://server.ws.soap.foreach.com/">
<xs:complexType name="country">
<xs:sequence>
<xs:element name="capital" type="xs:string" minOccurs="0"></xs:element>
<xs:element name="currency" type="tns:currency" minOccurs="0"></xs:element>
<xs:element name="name" type="xs:string" minOccurs="0"></xs:element>
<xs:element name="population" type="xs:int"></xs:element>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="currency">
<xs:restriction base="xs:string">
<xs:enumeration value="EUR"></xs:enumeration>
<xs:enumeration value="INR"></xs:enumeration>
<xs:enumeration value="USD"></xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:schema>

Это все, что нам нужно для реализации клиента.

Давайте посмотрим, как в следующем разделе.

3. Использование wsimport для генерации клиентского кода

3.1. Для JDK 8

Во-первых, давайте посмотрим, как сгенерировать клиентский код с помощью JDK 8.

Для начала добавим в наш pom.xml плагин для использования этого инструмента через Maven:

<plugin> 
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>wsimport-from-jdk</id>
<goals>
<goal>wsimport</goal>
</goals>
</execution>
</executions>
<configuration>
<wsdlUrls>
<wsdlUrl>http://localhost:8888/ws/country?wsdl</wsdlUrl>
</wsdlUrls>
<keep>true</keep>
<packageName>com.foreach.soap.ws.client.generated</packageName>
<sourceDestDir>src/main/java</sourceDestDir>
</configuration>
</plugin>

Во-вторых, давайте запустим этот плагин:

mvn clean jaxws:wsimport

Это все! Приведенная выше команда сгенерирует код в указанном пакете com.foreach.soap.ws.client.generated внутри sourceDestDir , который мы указали в конфигурации плагина.

Другой способ добиться того же — использовать утилиту wsimport . Он поставляется из коробки со стандартным дистрибутивом JDK 8 и находится в каталоге JAVA_HOME/bin .

Чтобы сгенерировать клиентский код с помощью wsimport , мы можем перейти в корень проекта и выполнить эту команду:

JAVA_HOME/bin/wsimport -s src/main/java/ -keep -p com.foreach.soap.ws.client.generated "http://localhost:8888/ws/country?wsdl"

Важно помнить, что конечная точка службы должна быть доступна для успешного выполнения подключаемого модуля или команды.

3.2. Для JDK 11

Начиная с JDK 11, wsimport был удален как часть JDK и больше не входит в стандартный дистрибутив.

Однако исходный код был открыт для фонда Eclipse.

Чтобы использовать wsimport для генерации клиентского кода для Java 11 и выше, нам нужно добавить зависимости jakarta.xml.ws-api , jaxws-rt и jaxws-ri в дополнение к jaxws-maven-plugin :

<dependencies>
<dependency>
<groupId>jakarta.xml.ws</groupId
<artifactId>jakarta.xml.ws-api</artifactId
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>3.0.0</version
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-ri</artifactId>
<version>2.3.1</version
<type>pom</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<wsdlUrls>
<wsdlUrl>http://localhost:8888/ws/country?wsdl</wsdlUrl>
</wsdlUrls>
<keep>true</keep>
<packageName>com.foreach.soap.ws.client.generated</packageName>
<sourceDestDir>src/main/java</sourceDestDir>
</configuration>
</plugin>
</plugins>
</build>

Теперь для генерации клиентского кода в пакете com.foreach.soap.ws.client.generated нам понадобится та же команда Maven, что и раньше:

mvn clean jaxws:wsimport

Далее давайте посмотрим на сгенерированные артефакты, одинаковые для обеих версий Java.

3.3. Сгенерированные POJO

На основе xsd , который мы видели ранее, инструмент сгенерирует файл с именем Country.java :

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "country", propOrder = { "capital", "currency", "name", "population" })
public class Country {
protected String capital;
@XmlSchemaType(name = "string")
protected Currency currency;
protected String name;
protected int population;
// standard getters and setters
}

Как мы видим, сгенерированный класс украшен аннотациями JAXB для маршалинга и демаршаллинга объекта в XML и обратно.

Кроме того, он генерирует перечисление валюты :

@XmlType(name = "currency")
@XmlEnum
public enum Currency {
EUR, INR, USD;
public String value() {
return name();
}
public static Currency fromValue(String v) {
return valueOf(v);
}
}

3.4. CountryService

Второй сгенерированный артефакт — это интерфейс, который действует как прокси для фактической веб-службы.

Интерфейс CountryService объявляет тот же метод, что и наш сервер, findByName :

@WebService(name = "CountryService", targetNamespace = "http://server.ws.soap.foreach.com/")
@SOAPBinding(style = SOAPBinding.Style.RPC)
@XmlSeeAlso({ ObjectFactory.class })
public interface CountryService {
@WebMethod
@WebResult(partName = "return")
@Action(input = "http://server.ws.soap.foreach.com/CountryService/findByNameRequest",
output = "http://server.ws.soap.foreach.com/CountryService/findByNameResponse")
public Country findByName(@WebParam(name = "arg0", partName = "arg0") String arg0);
}

Примечательно, что интерфейс помечен как javax.jws.WebService с SOAPBinding.Style как RPC, как определено WSDL службы.

Метод findByName аннотирован, чтобы объявить, что это javax.jws.WebMethod с ожидаемыми типами входных и выходных параметров.

3.5. СтранаСервисИмплСервис

Наш следующий сгенерированный класс, CountryServiceImplService , расширяет javax.xml.ws.Service .

Его аннотация WebServiceClient означает, что это клиентское представление службы:

@WebServiceClient(name = "CountryServiceImplService", 
targetNamespace = "http://server.ws.soap.foreach.com/",
wsdlLocation = "http://localhost:8888/ws/country?wsdl")
public class CountryServiceImplService extends Service {

private final static URL COUNTRYSERVICEIMPLSERVICE_WSDL_LOCATION;
private final static WebServiceException COUNTRYSERVICEIMPLSERVICE_EXCEPTION;
private final static QName COUNTRYSERVICEIMPLSERVICE_QNAME =
new QName("http://server.ws.soap.foreach.com/", "CountryServiceImplService");

static {
URL url = null;
WebServiceException e = null;
try {
url = new URL("http://localhost:8888/ws/country?wsdl");
} catch (MalformedURLException ex) {
e = new WebServiceException(ex);
}
COUNTRYSERVICEIMPLSERVICE_WSDL_LOCATION = url;
COUNTRYSERVICEIMPLSERVICE_EXCEPTION = e;
}

public CountryServiceImplService() {
super(__getWsdlLocation(), COUNTRYSERVICEIMPLSERVICE_QNAME);
}

// other constructors

@WebEndpoint(name = "CountryServiceImplPort")
public CountryService getCountryServiceImplPort() {
return super.getPort(new QName("http://server.ws.soap.foreach.com/", "CountryServiceImplPort"),
CountryService.class);
}

private static URL __getWsdlLocation() {
if (COUNTRYSERVICEIMPLSERVICE_EXCEPTION != null) {
throw COUNTRYSERVICEIMPLSERVICE_EXCEPTION;
}
return COUNTRYSERVICEIMPLSERVICE_WSDL_LOCATION;
}

}

Здесь важно отметить метод getCountryServiceImplPort . Учитывая полное имя конечной точки службы или QName и имя интерфейса конечной точки службы динамического прокси, он возвращает экземпляр прокси.

Чтобы вызвать веб-службу, нам нужно использовать этот прокси, как мы вскоре увидим.

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

4. Тестирование клиента

Далее мы напишем тест JUnit для подключения к веб-службе с использованием сгенерированного клиентского кода.

Прежде чем мы сможем это сделать, нам нужно получить экземпляр прокси службы на стороне клиента:

@BeforeClass
public static void setup() {
CountryServiceImplService service = new CountryServiceImplService();
CountryService countryService = service.getCountryServiceImplPort();
}

Для более сложных сценариев, таких как включение или отключение WebServiceFeature , мы можем использовать другие сгенерированные конструкторы для CountryServiceImplService .

Теперь давайте посмотрим на некоторые тесты:

@Test
public void givenCountryService_whenCountryIndia_thenCapitalIsNewDelhi() {
assertEquals("New Delhi", countryService.findByName("India").getCapital());
}

@Test
public void givenCountryService_whenCountryFrance_thenPopulationCorrect() {
assertEquals(66710000, countryService.findByName("France").getPopulation());
}

@Test
public void givenCountryService_whenCountryUSA_thenCurrencyUSD() {
assertEquals(Currency.USD, countryService.findByName("USA").getCurrency());
}

Как мы видим, вызов методов удаленного сервиса стал таким же простым, как вызов методов локально. Метод findByName прокси- сервера вернул экземпляр Country , соответствующий предоставленному нами имени . Затем мы использовали различные геттеры POJO для утверждения ожидаемых значений.

5. Вывод

В этой статье мы увидели, как вызвать веб-службу SOAP на Java с помощью JAX-WS RI и утилиты wsimport для Java 8 и 11.

В качестве альтернативы мы можем использовать другие реализации JAX-WS, такие как Apache CXF, Apache Axis2 и Spring, чтобы сделать то же самое.

Как всегда, исходный код доступен на GitHub для версий JDK 8 и JDK 11 .