Как стать автором
Обновить

SOAP-сервер на Java при участии Apache CXF и Spring

Время на прочтение7 мин
Количество просмотров65K
imageЗа последнее время появилось несколько статей, рассказывающих о протоколе SOAP, а также описывающих процесс создания сервера на различных языках и платформах. Продолжим тему. В этой статье будет описываться создание сервера на языке Java с использование Apache CXF и Spring Framework. Предполагается, что читатель уже имеет общее представление об упомянутом протоколе, а также о работе с ant и maven. Для того, чтобы сделать задачу немного интереснее, добавим начальное условие: дана WSDL-схема, описывающая веб-сервис. Итак…
(Картинка из статьи на Wikipedia.)

0. Начальные условия


Задача, которую перед нами поставили: необходимо реализовать веб-сервис, соответствующий спецификации SOAP 1.1, основываясь на готовой WSDL-схеме. В качестве протокола для передачи «конвертов» будем использовать HTTP. Таким образом нашу задачу можно разбить на две составляющие:
  • создание java-интерфейса на основе WSDL-схемы;
  • реализация веб-сервиса на основе полученного интерфейса.

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

Вот так выглядит исходная WSDL-схема:
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions name="CurrentTimeService" targetNamespace="http://artspb.me/cts"
                  xmlns:tns="http://artspb.me/cts" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">

    <wsdl:types>
        <xsd:schema targetNamespace="http://artspb.me/cts">
            <xsd:element name="timeZoneId" nillable="true" type="xsd:string"/>
            <xsd:element name="currentTime" type="xsd:dateTime"/>
        </xsd:schema>
    </wsdl:types>

    <wsdl:message name="getCurrentTimeMsg">
        <wsdl:part element="tns:timeZoneId" name="timeZoneId"/>
    </wsdl:message>
    <wsdl:message name="currentTimeMsg">
        <wsdl:part element="tns:currentTime" name="currentTime"/>
    </wsdl:message>

    <wsdl:portType name="CurrentTimeService">
        <wsdl:operation name="getCurrentTime">
            <wsdl:input message="tns:getCurrentTimeMsg" name="getCurrentTime"/>
            <wsdl:output message="tns:currentTimeMsg" name="currentTime"/>
        </wsdl:operation>
    </wsdl:portType>

    <wsdl:binding name="CurrentTimeService_HttpBinding" type="tns:CurrentTimeService">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>

        <wsdl:operation name="getCurrentTime">
            <soap:operation soapAction="http://artspb.me/cts" style="document"/>
            <wsdl:input name="getCurrentTime">
                <soap:body use="literal"/>
            </wsdl:input>
            <wsdl:output name="currentTime">
                <soap:body use="literal"/>
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>

    <wsdl:service name="CurrentTimeService_HttpService">
        <wsdl:port binding="tns:CurrentTimeService_HttpBinding" name="CurrentTimeService_HttpPort">
            <soap:address location="http://localhost:8080/YetAnotherService/service/currentTimeService"/>
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

Сервис имеет весьма простую функциональность: возвращает текущее время в указанном часовом поясе. В случае, когда пояс не указан, сервис возвращает текущее время сервера.

В процессе работы мы будем использовать следующие инструменты:

Все их вместе очень удобно использовать с помощью IntelliJ IDEA, но этот инструмент не является обязательным.

Структура проекта


Определим структуру проекта. В папке build будем хранить скрипты для ant. В модуле ObjectLibrary будем держать исходную схему, а также сгенерированные по ней классы. Модуль CurrentTimeService будет основным, в нем будем держать реализацию сервиса, также по нему будем собирать war-файл.

В pom-файлах укажем необходимые модулям зависимости. Внешних зависимостей у нас будет не много: spring-web, log4j, cxf-rt-frontend-jaxws и cxf-rt-transports-http — все они доступны в http://repo1.maven.org/maven2.

1. Создание java-интерфейса на основе WSDL-схемы


Для автоматической генерации java-интерфейса на основе WSDL-схемы воспользуемся утилитой wsdl2java из пакета Apache CXF. Немного модифицируем исходный ant-скрипт для того, чтобы он предварительно очищал директорию, в которую будут складываться java-классы. Это может быть полезно в том случае, если в будущем исходная схема будет меняться. По-умолчанию директория не очищается, что может приводить к появлению «мусора» из классов, которые больше не используются. Такая проблема характерна для схем, имеющих в своем составе сложные составные типы.

<?xml version="1.0"?>
<project name="BuildObject">

    <property name="cxf.home" value="/home/art/tools/apache-cxf-2.5.2"/>

    <path id="cxf.classpath">
        <fileset dir="${cxf.home}/lib">
            <include name="*.jar"/>
        </fileset>
    </path>

    <target name="cxfWSDLToJava">
        <java classname="org.apache.cxf.tools.wsdlto.WSDLToJava" fork="true">
            <arg value="-verbose"/>
            <arg value="-d"/>
            <arg value="../ObjectLibrary/src/main/java"/>
            <arg value="../ObjectLibrary/src/main/resources/CurrentTimeService.wsdl"/>
            <classpath>
                <path refid="cxf.classpath"/>
            </classpath>
        </java>
    </target>

    <target name="regenerate.object.library">
        <delete dir="../ObjectLibrary/src/main/java"/>
        <antcall target="cxfWSDLToJava"/>
    </target>

</project>

Разместим этот скрипт в файле build/build.xml. Запустим таску regenerate.object.library, в результате работы которой в папке ObjectLibrary получим необходимый нам интерфейс.

package me.artspb.cts;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.xml.bind.annotation.XmlSeeAlso;

/**
 * This class was generated by Apache CXF 2.5.2
 * 2012-02-02T17:39:35.466+04:00
 * Generated source version: 2.5.2
 * 
 */
@WebService(targetNamespace = "http://artspb.me/cts", name = "CurrentTimeService")
@XmlSeeAlso({ObjectFactory.class})
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface CurrentTimeService {

    @WebResult(name = "currentTime", targetNamespace = "http://artspb.me/cts", partName = "currentTime")
    @WebMethod(action = "http://artspb.me/cts")
    public javax.xml.datatype.XMLGregorianCalendar getCurrentTime(
        @WebParam(partName = "timeZoneId", name = "timeZoneId", targetNamespace = "http://artspb.me/cts")
        java.lang.String timeZoneId
    );
}


Стоит иметь ввиду, что с помощью утилиты wsdl2java мы сразу могли бы получить готовый сервер (ключ -server) и даже реализацию для него (ключ -impl). Однако в рамках данной статьи мы будем использовать ее только для получения необходимого нам интерфейса, чтобы избежать жесткой привязки к сгенерированному коду.

2. Реализация веб-сервиса на основе полученного интерфейса


Реализацию интерфейса CurrentTimeService разместим в одноименном модуле. Код незамысловатый и в дополнительных комментариях не нуждается.

package me.artspb.cts;

import org.apache.log4j.Logger;

import javax.jws.WebParam;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;

public class CurrentTimeServiceImpl implements CurrentTimeService {
    private Logger logger = Logger.getLogger(CurrentTimeServiceImpl.class);
    
    public XMLGregorianCalendar getCurrentTime(
            @WebParam(partName = "timeZoneId", name = "timeZoneId", targetNamespace = "http://artspb.me/cts")
            String timeZoneId) {

        logger.debug("Operation getCurrentTime was requested.");

        XMLGregorianCalendar gregorianCalendar;

        try {
            gregorianCalendar = getXmlGregorianCalendar(timeZoneId);
        } catch (DatatypeConfigurationException e) {
            throw new RuntimeException(e);
        }

        logger.debug("Successful.");

        return gregorianCalendar;
    }

    private XMLGregorianCalendar getXmlGregorianCalendar(String id) throws DatatypeConfigurationException {
        TimeZone timeZone;

        if (!"".equals(id)) {
            logger.debug("TimeZoneId isn't null: " + id);

            timeZone = TimeZone.getTimeZone(id);
        } else {
            logger.debug("TimeZoneId is null. Will use default value.");

            timeZone = TimeZone.getDefault();
        }

        GregorianCalendar gregorianCalendar = new GregorianCalendar(timeZone);

        return DatatypeFactory.newInstance().newXMLGregorianCalendar(gregorianCalendar);
    }
}


Теперь, в соответствии с примером, добавим в ресурсы файлы serviceContext.xml и web.xml. Также не забудем про log4j.xml. Найти эти файлы можно в архиве с исходными кодами, ссылка на него приведена в конце статьи. После этого реализацию сервиса можно считать законченной, осталось собрать приложение и проверить его работоспособносcть.

3. Сборка и проверка


Для сборки проекта выполним задачу maven «package». Полученный war-файл развернем на сервере Tomcat. Будем считать, что он доступен на порту 8080.



Для проверки воспользуемся программой soapUI. WSDL-схема сервиса доступна по ссылке http://localhost:8080/CurrentTimeService/service/currentTimeService?wsdl, с ее помощью мы можем создать новый проект soapUI.

Отправляем запрос:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:cts="http://artspb.me/cts">
   <soapenv:Header/>
   <soapenv:Body>
      <cts:timeZoneId>PST</cts:timeZoneId>
   </soapenv:Body>
</soapenv:Envelope>


Получаем ответ:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
      <currentTime xmlns="http://artspb.me/cts">2012-02-02T08:48:24.402-08:00</currentTime>
   </soap:Body>
</soap:Envelope>


Вместо заключения


Поставленная задача достигнута: веб-сервис корректно обрабатывает запросы. Также нам удалось избежать «жесткой» привязки к генерируемой реализации, что делает код более гибким и позволяет прикладывать меньше усилий для внесения изменений в дальнейшем.

Успехов!

Основные ссылки вместе:
Теги:
Хабы:
Всего голосов 23: ↑19 и ↓4+15
Комментарии30

Публикации

Истории

Работа

Java разработчик
347 вакансий

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань