
Когда-то поставили передо мной задачу начать разработку Web-сервисов и дали мне сорцы простейшего проекта без каких-либо объяснений. Проект, конечно же, не запускался. Что такое Spring и как он работает, я тоже представления не имел. Адекватных статей по разработке Web-сервисов средствами Spring ни русскоязычных, ни англоязычных я тоже не смог найти. Пришлось разбираться во всем самому, оказалось все не так страшно.
И вот недавно я решил посмотреть, какие новые возможности добавились в Spring с тех пор, и обновить старые сервисы, что в результате и сподвигло меня на написание данной статьи.
Данная статья является руководством по разработке простейшего Web-сервиса, использующего SOAP-протокол, средствами Spring-WS.
И так, писать будем простейший сервис, принимающий имя пользователя и отправляющий приветствие и текущее время на сервере.
Что же нам потребуется?
- IDE. Я использую Eclipse.
- Ant
- soapUI
- JDK
- JRE
- Spring Framework 3.1.2
- Spring-WS 2.1.0
- XMLBeans 2.5.0
- wsdl4j
- commons-logging-1.1.1
Подготовка к работе
Создаем новый проект Web-приложения. В Eclipse это: «File => New => Dynamic Web Project».
Я назвал проект: HelloService.
Далее копируем библиотеки из Spring, XMLBean, wsdl4j, commons-logging в каталог проекта WEB-INF/lib.
При желании можете добавить их к библиотекам сервера, чтобы не таскать их с каждым приложением.
Создание WSDL-схемы
По сути WSDL-схема предназначена для описания сервиса.
Вручную создавать её мы, конечно же, не будем. Схема будет сгенерирована автоматически средствами Spring'а, но об этом позднее.
Определяем входные и выходные данные
Входные данные:
- String имя.
Выходные данные:
- String приветствие;
- Time текущее время.
Создаем описание входных и выходных данных
В каталоге WEB-INF создаем файл HelloService.xsd. Данный файл нужен будет для генерации WSDL-схемы и создания соответствующих Java-классов.
Текст файла:
<?xml version="1.0" encoding="UTF-8"?> <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/HelloService" elementFormDefault="qualified"> <element name="ServiceRequest"> <complexType> <sequence> <element name="name" type="string" maxOccurs="1" minOccurs="1"/> </sequence> </complexType> </element> <element name="ServiceResponse"> <complexType> <sequence> <element name="hello" type="string" maxOccurs="1" minOccurs="1"/> <element name="currentTime" type="time" maxOccurs="1" minOccurs="1"/> </sequence> </complexType> </element> </schema>
Атрибут targetNamespace – используемое пространство имен. Т.е. все созданные объекты будут располагаться в пакете org.example.helloService.
Элементы ServiceRequest и ServiceResponse описывают соответственно входные и выходные данные (запрос/ответ).
Атрибуты minOccurs и maxOccurs определяют количество повторений данного компонента в пределах одного элемента. Если эти параметры не указывать, то по умолчанию они считаются равными 1. Для необязательного компонента необходимо указать minOccurs=0. При неограниченном количестве компонент: maxOccurs=unbounded.
Подробнее о XML-схемах можно прочитать здесь.
Создаем JavaBeans
На основании созданной схемы будем создавать Java классы. Для этого создаем файл build.xml:
<?xml version="1.0" encoding="UTF-8"?> <project name="imsjob" default="build" basedir="."> <property name="WS_HOME" value="C:\AST\lib\standart"/> <property name="encoding" value="UTF-8"/> <path id="xbean.classpath"> <fileset dir="${WS_HOME}"> <include name="*.jar"/> </fileset> </path> <taskdef name="xmlbean" classname="org.apache.xmlbeans.impl.tool.XMLBean" classpathref="xbean.classpath" /> <target name="init"> <echo message="Start init"/> </target> <target name="build" depends="init"> <xmlbean schema="HelloService.xsd" destfile="lib\helloservice.jar" classpathref="xbean.classpath"/> </target> </project>
Параметр WS_HOME должен указывать на каталог, где располагается XMLBeans.
HelloService.xsd – путь к созданной схеме.
lib\helloservice.jar – создаваемая java-библиотека.
Далее запускаем Ant-build (надеюсь, вы его уже установили).
В Eclipse можно запустить так: ПКМ по файлу build.xml=> Run As => Ant Build.
Если через командную строку:
ant -buildfile build.xmlНу и ждем завершения построения. После чего, можем проверить каталог проекта WEB-INF\lib на наличие соответствующей библиотеки (helloservice.jar).
Реализация сервиса
Создаем интерфейс и класс сервиса
Интерфейс сервиса: HelloService.java:
package org.example; import java.util.Calendar; public interface HelloService { public String getHello(String name) throws Exception; public Calendar getCurrentTime(); }
Реализация сервиса: HelloServiceImpl.java:
package org.example; import java.util.Calendar; import org.springframework.stereotype.Service; @Service public class HelloServiceImpl implements HelloService { public String getHello(String name) throws Exception { return "Hello, " + name + "!"; } public Calendar getCurrentTime() { return Calendar.getInstance(); } }
Данный код, я думаю, не нуждается в комментариях. Единственное, что у людей, не сталкивающихся ранее со Spring'ом, может вызвать вопросы, так это аннотация @ Service. Но об этом же расскажу чуть позже.
Endpoint
Endpoint – класс, который будет отвечать за обработку входящих запросов (своего рода точка входа).
Создаем файл HelloServiceEndpoint.java:
package org.example; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ws.server.endpoint.annotation.Endpoint; import org.springframework.ws.server.endpoint.annotation.PayloadRoot; import org.example.helloService.ServiceRequestDocument; import org.example.helloService.ServiceRequestDocument.ServiceRequest; import org.example.helloService.ServiceResponseDocument; import org.example.helloService.ServiceResponseDocument.ServiceResponse; @Endpoint public class HelloServiceEndpoint{ private static final String namespaceUri = "http://www.example.org/HelloService"; private HelloService helloService; @Autowired public void HelloService (HelloService helloService) { this.helloService = helloService; } @PayloadRoot(localPart = "ServiceRequest", namespace = namespaceUri) public ServiceResponseDocument getService(ServiceRequestDocument request) throws Exception { ServiceRequestDocument reqDoc = request; ServiceRequest req = reqDoc.getServiceRequest(); ServiceResponseDocument respDoc = ServiceResponseDocument.Factory.newInstance(); ServiceResponse resp = respDoc.addNewServiceResponse(); String userName = req.getName(); String helloMessage = testNewService.getHello(userName); Calendar currentTime = testNewService.getCurrentTime(); resp.setHello(helloMessage); resp.setCurrentTime(currentTime); return respDoc; } }
Что же здесь сделано?
Аннотация @Endpoint как раз и определяет, что данный класс будет обрабатывать входящие запросы.
namespaceUri – то же пространство имен, что и указывалось при создании xml-схемы.
Теперь вернемся немного назад и вспомним про аннотацию @ Service. Если не вдаваться в подробности, чтобы не перегружать читателя лишней информацией, то эта аннотацию говорит Spring'у создать соответствующий объект. А аннотация @Autowired служит для инъекции (автоматической подстановки) соответствующего объекта. Конечно же при построении простых приложений в использовании данных аннотаций отсутствует смысл, но я решил все-такие не исключать их в данном примере.
И так, идем далее.
Аннотация @PayloadRoot перед методом определяет, при получении какого запроса будет вызван данный метод. В нашем случае, это «ServiceRequest».
В остальном опять же все должно быть ясно. Обратите внимание, что ServiceRequest, ServiceResponse и т.д. – это как раз те классы, которые были созданы на основе нашей xml-схемы.
Spring-конфигурация сервиса
Вот и близится уже завершение.
Создаем файл service-ws-servlet.xml.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:sws="http://www.springframework.org/schema/web-services" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:component-scan base-package="org.example" /> <sws:annotation-driven /> <bean class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter"> <property name="marshaller" ref="marshaller" /> <property name="unmarshaller" ref="marshaller" /> </bean> <bean id="marshaller" class="org.springframework.oxm.xmlbeans.XmlBeansMarshaller"/> <sws:dynamic-wsdl id="HelloService" portTypeName="service" locationUri="/HelloService" > <sws:xsd location="/WEB-INF/HelloService.xsd" /> </sws:dynamic-wsdl> </beans>
sws:annotation-driven – говорит как раз о том, что в данном проекте используются аннотации.
А context:component-scan указывает на пакет, в котором будет производится поиск аннотаций, при этом поиск производится и в подпакетах.
Два последующих бина всегда будут неизменны. Суть их заключается в приеме и преобразовании запроса из Xml в Java-объект и дальнейшего обратного преобразования.
sws:dynamic-wsdl отвечает за автоматическую генерацию WSDL-документа на основе созданной Xml-схемы.
location указывает на путь к схеме.
locationUri – адрес (относительно контейнера), по которому будет доступна WSDL-схема.
В моем случае WSDL доступен по следующему адресу:
localhost/HelloService/HelloService.wsdl
Дескриптор развертывания
Ну и, наконец, последнее.
В каталоге WEB-INF изменяем или создаем файл web.xml.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <display-name>HelloService</display-name> <description>HelloService</description> <servlet> <servlet-name>service-ws</servlet-name> <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class> <init-param> <param-name>transformWsdlLocations</param-name> <param-value>true</param-value> </init-param> <servlet-mapping> <servlet-name>service-ws</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
Данный файл описывать уже не буду, большинство и так должны знать. Для несложных проектов он по сути не должен изменяться. Стоит отметить только, что имя сервлета(servlet-name) должно соответствовать имени файла Spring-конфигурации сервиса service-ws-servlet.xml.
Ну и далее деплоим приложение на сервер.
На этом создание сервиса завершено. Если ничего не пропустили, то сервис должен функционировать.
Проверка работоспособности
Самым первым признаком корректной работы является созданная WSDL-схема.
Для проверки просто переходим по адресу этой схемы (http://localhost/HelloService/HelloService.wsdl) и смотрим: там должен отобразиться xml-файл. Если ничего не отобразилось или какая ошибка появилась, перечитываем внимательно всю статью и ищем, что сделали не так.
Для дальнейшей проверки нам потребуется soapUI (у меня версия 3.0.1).
Устанавливаем и запускаем его.
Создаем новый проект: File => New soapUI Project. В поле Initial WSDL/WADL вставляем ссылку на WSDL-схему (http://localhost/HelloService/HelloService.wsdl).
В созданном проекте открываем необходимый запрос.

В поле Name вбиваем имя и жмем на кнопку «Send request»

В результате получаем ответ от сервера с приветствием и текущим временем.

Если что-то пошло не так, то опять перечитываем данную статью.
Что дальше?
Ну а дальше предстоит написание клиента для данного Web-сервиса. Но это уже материал для другой статьи, которая возможно будет написана позже, если данный материал кого-то заинтересует.
