SOAP Web-сервис средствами Spring-WS

в 19:42, , рубрики: java, soap, spring, webservice, wsdl, метки: , , , ,

image
Когда-то поставили передо мной задачу начать разработку Web-сервисов и дали мне сорцы простейшего проекта без каких-либо объяснений. Проект, конечно же, не запускался. Что такое Spring и как он работает, я тоже представления не имел. Адекватных статей по разработке Web-сервисов средствами Spring ни русскоязычных, ни англоязычных я тоже не смог найти. Пришлось разбираться во всем самому, оказалось все не так страшно.
И вот недавно я решил посмотреть, какие новые возможности добавились в Spring с тех пор, и обновить старые сервисы, что в результате и сподвигло меня на написание данной статьи.

Данная статья является руководством по разработке простейшего Web-сервиса, использующего SOAP-протокол, средствами Spring-WS.

И так, писать будем простейший сервис, принимающий имя пользователя и отправляющий приветствие и текущее время на сервере.

Что же нам потребуется?

Подготовка к работе

Создаем новый проект 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:ASTlibstandart"/>
  <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="libhelloservice.jar" classpathref="xbean.classpath"/>    
  </target>
</project>

Параметр WS_HOME должен указывать на каталог, где располагается XMLBeans.
HelloService.xsd – путь к созданной схеме.
libhelloservice.jar – создаваемая java-библиотека.

Далее запускаем Ant-build (надеюсь, вы его уже установили).
В Eclipse можно запустить так: ПКМ по файлу build.xml=> Run As => Ant Build.
Если через командную строку:
ant -buildfile build.xml
Ну и ждем завершения построения. После чего, можем проверить каталог проекта WEB-INFlib на наличие соответствующей библиотеки (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).
В созданном проекте открываем необходимый запрос.
SOAP Web сервис средствами Spring WS
В поле Name вбиваем имя и жмем на кнопку «Send request»
SOAP Web сервис средствами Spring WS
В результате получаем ответ от сервера с приветствием и текущим временем.
SOAP Web сервис средствами Spring WS
Если что-то пошло не так, то опять перечитываем данную статью.

Что дальше?

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

Автор: AR1ES

* - обязательные к заполнению поля