Когда-то поставили передо мной задачу начать разработку 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-сервиса. Но это уже материал для другой статьи, которая возможно будет написана позже, если данный материал кого-то заинтересует.