Учебные материалы

Обработка ответа SOAP web-сервиса

Этот урок освещает процесс обработки ответа от SOAP web-сервиса с использованием Spring.

Что вы создадите

Вы создадите клиента, который собирает данные о погоде с удаленного web-сервиса на основе WSDL с использованием SOAP. Более подробную информацию о сервисе вы найдете на странице сервиса.

Сервис предоставляет прогноз погоды на основании zipcode. Вы будете использовать ваш собственный zip код.

Что вам потребуется

  • Примерно 15 минут свободного времени
  • Любимый текстовый редактор или IDE
  • JDK 6 и выше
  • Gradle 1.11+ или Maven 3.0+
  • Вы также можете импортировать код этого урока, а также просматривать web-страницы прямо из Spring Tool Suite (STS), собственно как и работать дальше из него.

Как проходить этот урок

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

Чтобы начать с нуля, перейдите в Настройка проекта.

Чтобы пропустить базовые шаги, выполните следующее:

Когда вы закончите, можете сравнить получившийся результат с образцом в gs-consuming-web-service/complete.

Настройка проекта

Для начала вам необходимо настроить базовый скрипт сборки. Вы можете использовать любую систему сборки, которая вам нравится для сборки проетов Spring, но в этом уроке рассмотрим код для работы с Gradle и Maven. Если вы не знакомы ни с одним из них, ознакомьтесь с соответсвующими уроками Сборка Java-проекта с использованием Gradle или Сборка Java-проекта с использованием Maven.

Создание структуры каталогов

В выбранном вами каталоге проекта создайте следующую структуру каталогов; к примеру, командой mkdir -p src/main/java/hello для *nix систем:

└── src
    └── main
        └── java
            └── hello

Создание файла сборки Gradle

Ниже представлен начальный файл сборки Gradle. Файл pom.xml находится здесь. Если вы используете Spring Tool Suite (STS), то можете импортировать урок прямо из него.

Если вы посмотрите на pom.xml, вы найдете, что указана версия для maven-compiler-plugin. В общем, это не рекомендуется делать. В данном случае он предназначен для решения проблем с нашей CI системы, которая по умолчанию имеет старую(до Java 5) версию этого плагина.

build.gradle

configurations {
    jaxb
}

buildscript {
    repositories {
        maven { url "https://repo.spring.io/libs-release" }
        mavenLocal()
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.1.9.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'

repositories {
    mavenLocal()
    mavenCentral()
    maven { url 'https://repo.spring.io/libs-release' }
}

// tag::wsdl[]
task genJaxb {
    ext.sourcesDir = "${buildDir}/generated-sources/jaxb"
    ext.classesDir = "${buildDir}/classes/jaxb"
    ext.schema = "http://wsf.cdyne.com/WeatherWS/Weather.asmx?wsdl"

    outputs.dir classesDir

    doLast() {
        project.ant {
            taskdef name: "xjc", classname: "com.sun.tools.xjc.XJCTask",
                    classpath: configurations.jaxb.asPath
            mkdir(dir: sourcesDir)
            mkdir(dir: classesDir)

            xjc(destdir: sourcesDir, schema: schema,
                    package: "hello.wsdl") {
                arg(value: "-wsdl")
                produces(dir: sourcesDir, includes: "**/*.java")
            }

            javac(destdir: classesDir, source: 1.6, target: 1.6, debug: true,
                    debugLevel: "lines,vars,source",
                    classpath: configurations.jaxb.asPath) {
                src(path: sourcesDir)
                include(name: "**/*.java")
                include(name: "*.java")
            }

            copy(todir: classesDir) {
                fileset(dir: sourcesDir, erroronmissingdir: false) {
                    exclude(name: "**/*.java")
                }
            }
        }
    }
}
// end::wsdl[]

dependencies {
    compile("org.springframework.boot:spring-boot-starter")
    compile("org.springframework.ws:spring-ws-core")
    compile(files(genJaxb.classesDir).builtBy(genJaxb))

    jaxb "com.sun.xml.bind:jaxb-xjc:2.1.7"
}

jar {
	from genJaxb.classesDir
}

task wrapper(type: Wrapper) {
    gradleVersion = '1.11'
}

task afterEclipseImport {
    dependsOn genJaxb
}

Spring Boot gradle plugin предоставляет множество удобных возможностей:

  • Он собирает все jar'ы в classpath и собирает единое, исполняемое "über-jar", что делает более удобным выполнение и доставку вашего сервиса
  • Он ищет public static void main() метод, как признак исполняемого класса
  • Он предоставляет встроенное разрешение зависимостей, с определенными номерами версий для соответсвующих Spring Boot зависимостей. Вы можете переопределить на любые версии, какие захотите, но он будет по умолчанию для Boot выбранным набором версий

Генерация доменного объекта на основе WSDL

Интерфейс SOAP web-сервиса описан через WSDL. JAXB предоставляет легкий способ генерации Java классов из WSDL(а точнее: на основе XSD в секции <Types/> WSDL). WSDL для сервиса погоды может быть найден на http://wsf.cdyne.com/WeatherWS/Weather.asmx?wsdl.

Для геннерации Java классов из WSDL в Maven вам необходимо настроить плагин, описанный ниже:

<plugin>
    <groupId>org.jvnet.jaxb2.maven2</groupId>
    <artifactId>maven-jaxb2-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <schemaLanguage>WSDL</schemaLanguage>
        <generatePackage>hello.wsdl</generatePackage>
        <forceRegenerate>true</forceRegenerate>
        <schemas>
            <schema>
                <url>http://wsf.cdyne.com/WeatherWS/Weather.asmx?wsdl</url>
            </schema>
        </schemas>
    </configuration>
</plugin>

Эта настройка будет генерировать классы из WSDL, найденному по указанному URL, помещая эти классы в пакет hello.wsdl.

То же самое делается и в gradle, поместите следующее в ваш файл сборки:

task genJaxb {
    ext.sourcesDir = "${buildDir}/generated-sources/jaxb"
    ext.classesDir = "${buildDir}/classes/jaxb"
    ext.schema = "http://wsf.cdyne.com/WeatherWS/Weather.asmx?wsdl"

    outputs.dir classesDir

    doLast() {
        project.ant {
            taskdef name: "xjc", classname: "com.sun.tools.xjc.XJCTask",
                    classpath: configurations.jaxb.asPath
            mkdir(dir: sourcesDir)
            mkdir(dir: classesDir)

            xjc(destdir: sourcesDir, schema: schema,
                    package: "hello.wsdl") {
                arg(value: "-wsdl")
                produces(dir: sourcesDir, includes: "**/*.java")
            }

            javac(destdir: classesDir, source: 1.6, target: 1.6, debug: true,
                    debugLevel: "lines,vars,source",
                    classpath: configurations.jaxb.asPath) {
                src(path: sourcesDir)
                include(name: "**/*.java")
                include(name: "*.java")
            }

            copy(todir: classesDir) {
                fileset(dir: sourcesDir, erroronmissingdir: false) {
                    exclude(name: "**/*.java")
                }
            }
        }
    }
}

Т.к. gradle не имеет JAXB плагина(пока), он включает в себя ant задачу, которая делает файл сборки немного сложнее по сравнению с файлом сборки Maven.

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

Создание клиента сервиса погоды

Для создания клиента web-сервиса, вы просто расширяете класс WebServiceGatewaySupport.

src/main/java/hello/WeatherClient.java


package hello;

import java.text.SimpleDateFormat;

import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
import org.springframework.ws.soap.client.core.SoapActionCallback;

import hello.wsdl.Forecast;
import hello.wsdl.ForecastReturn;
import hello.wsdl.GetCityForecastByZIP;
import hello.wsdl.GetCityForecastByZIPResponse;
import hello.wsdl.Temp;

public class WeatherClient extends WebServiceGatewaySupport {

	public GetCityForecastByZIPResponse getCityForecastByZip(String zipCode) {
		GetCityForecastByZIP request = new GetCityForecastByZIP();
		request.setZIP(zipCode);

		System.out.println();
		System.out.println("Requesting forecast for " + zipCode);

		GetCityForecastByZIPResponse response = (GetCityForecastByZIPResponse) getWebServiceTemplate().marshalSendAndReceive(
				request,
				new SoapActionCallback(
						"http://ws.cdyne.com/WeatherWS/GetCityForecastByZIP"));

		return response;
	}

	public void printResponse(GetCityForecastByZIPResponse response) {
		ForecastReturn forecastReturn = response.getGetCityForecastByZIPResult();

		if (forecastReturn.isSuccess()) {
			System.out.println();
			System.out.println("Forecast for " + forecastReturn.getCity() + ", "
					+ forecastReturn.getState());

			SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
			for (Forecast forecast : forecastReturn.getForecastResult().getForecast()) {
				System.out.print(format.format(forecast.getDate().toGregorianCalendar().getTime()));
				System.out.print(" ");
				System.out.print(forecast.getDesciption());
				System.out.print(" ");
				Temp temperature = forecast.getTemperatures();
				System.out.print(temperature.getMorningLow() + "\u00b0-"
						+ temperature.getDaytimeHigh() + "\u00b0 ");
				System.out.println();
			}
		} else {
			System.out.println("No forecast received");
		}
	}

}

Клиент содержит два метода: getCityForecastByZip, который осуществляет SOAP обмен и printResponse, который печатает полученный ответ. Обратим внимение на предыдущий метод.

В этом методе, оба класса GetCityForecastByZIP и GetCityForecastByZIPResponse получены из WSDL в описанном на предыдущем шаге процессе генерации JAXB. Метод создает объект запроса GetCityForecastByZIP и устанавливает значение параметра zipCode. Затем он использует WebServiceTemplate, предоставленный базовым классом WebServiceGatewaySupport для совершения SOAP обмена. Он передает объект запроса GetCityForecastByZIP, также как SoapActionCallback передает SOAPAction заголовок запроса, т.к. WSDL описывает, что ему необходим этот заголовок в <soap:operation/> элементах. Он приводит ответ к объекту типа GetCityForecastByZIPResponse, который затем возвращается.

Настройка компонентов web-сервиса

Spring WS использует Spring Framework OXM модуль Jaxb2Marshaller для сериализации и десериализации XML запросов.

src/main/java/hello/WeatherConfiguration.java


package hello;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;

@Configuration
public class WeatherConfiguration {

	@Bean
	public Jaxb2Marshaller marshaller() {
		Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
		marshaller.setContextPath("hello.wsdl");
		return marshaller;
	}

	@Bean
	public WeatherClient weatherClient(Jaxb2Marshaller marshaller) {
		WeatherClient client = new WeatherClient();
		client.setDefaultUri("http://wsf.cdyne.com/WeatherWS/Weather.asmx");
		client.setMarshaller(marshaller);
		client.setUnmarshaller(marshaller);
		return client;
	}

}

marshaller указан на коллекцию сгенерированных доменных объектов и будет использован ими в сериализации и десериализации между XML и POJO.

weatherClient создан и настроен на URI сервиса погоды, указанным выше. Он также настроен на использование JAXB маршаллера.

Создание приложения исполняемым

Это приложение собрано для запуска из консоли и возвращает единственный прогноз погоды по заданному Zip коду.

src/main/java/hello/Application.java


package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;

import hello.wsdl.GetCityForecastByZIPResponse;

public class Application {

	public static void main(String[] args) {
		ApplicationContext ctx = SpringApplication.run(WeatherConfiguration.class, args);

		WeatherClient weatherClient = ctx.getBean(WeatherClient.class);

		String zipCode = "94304";
		if (args.length > 0) {
			zipCode = args[0];
		}
		GetCityForecastByZIPResponse response = weatherClient.getCityForecastByZip(zipCode);
		weatherClient.printResponse(response);
	}

}

main() метод передает управление вспомогательному классу SpringApplication, где WeatherConfiguration.class - аргумент его run() метода. Это сообщает Spring о чтении метаданных аннотации из WeatherConfiguration и управлении ею как компонента в Spring application context.

Сборка исполняемого JAR

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

./gradlew build

Затем вы можете запустить JAR-файл:

java -jar build/libs/gs-consuming-web-service-0.1.0.jar

Если вы используете Maven, вы можете запустить приложение, используя mvn spring-boot:run, либо вы можете собрать приложение с mvn clean package и запустить JAR примерно так:

java -jar target/gs-consuming-web-service-0.1.0.jar
Процедура, описанная выше, создает исполняемый JAR. Вы также можете вместо него собрать классический WAR-файл.

Запуск сервиса

Если вы используете Gradle, вы можете запустить ваш сервис из командной строки:

./gradlew clean build && java -jar build/libs/gs-consuming-web-service-0.1.0.jar
Если вы используете Maven, то можете запустить ваш сервис таким образом: mvn clean package && java -jar target/gs-consuming-web-service-0.1.0.jar.

Как вариант, вы можете запустить ваш сервис напрямую из Gradle примерно так:

./gradlew bootRun
С mvn - mvn spring-boot:run.

Сервис должен быть поднят и запущен через несколько секунд. Вы должны увидеть следующее:

Requesting forecast for 94304

Forecast for Palo Alto, CA
2013-01-03 Partly Cloudy °-57°
2013-01-04 Partly Cloudy 41°-58°
2013-01-05 Partly Cloudy 41°-59°
2013-01-06 Partly Cloudy 44°-56°
2013-01-07 Partly Cloudy 41°-60°
2013-01-08 Partly Cloudy 42°-60°
2013-01-09 Partly Cloudy 43°-58°

Вы можете указать другой zip код по типу java -jar build/libs/gs-consuming-web-service-0.1.0.jar 34769:

Requesting forecast for 34769

Forecast for Saint Cloud, FL
2014-02-18 Sunny 51°-79°
2014-02-19 Sunny 55°-81°
2014-02-20 Sunny 59°-84°
2014-02-21 Partly Cloudy 63°-85°
2014-02-22 Partly Cloudy 63°-84°
2014-02-23 Partly Cloudy 63°-82°
2014-02-24 Partly Cloudy 62°-80°

Итог

Поздравляем! Вы только что разработали клиент обработки ответо от web-сервиса на основе SOAP, используя Spring.

С оригинальным текстом урока вы можете ознакомиться на spring.io.

comments powered by Disqus