Описание проблемы и основные понятия
Для реализации классических SOAP веб-сервисов spring предлагает хороший фреймворк spring-ws. Он использует так называемую схему "contract-first", т.е. сначала необходимо определить схему используемых сообщений, а затем уже создать под нее требуемые java-классы. Обычно этим занимаются кодогенераторы, которые создадут классы по схеме.
Однако существуют и другие фреймворки, которые позволяют создавать веб-сервисы. В том числе и исторически более ранний стандарт JAX-WS. Это именно стандарт, т.е. по факту набор апи, реализацию которого предоставляют различные платформы. Например, реализация Metro, которая есть в Glassfish. В самом проекте эта реализация в качестве зависимости не притягивается - нужен только апи. Более того, это апи содержится в поставке JDK, поэтому объявлять что-то дополнительно не нужно совсем. Деплоить такой проект нужно в такой контейнер, в котором имеется эта реализация. Контейнер подцепляет объявленные веб-сервисы и они становятся доступными. Томкат такой реализации в совем составе не имеет, поэтому такой проект задеплоить в нем не получится.
Тем не менее проблема даже не в том, что томкат нельзя использовать, а в том, что из-за своего особого жизненного цикла, которым рулит контейнер, spring не управляет этими бинами и соответственно недоступна инжекция и прочие радости жизни спринга.
JAX-WS (и вариант для REST JAX-RS) сам по себе не так уж плох - в нем хватает аннотаций, чтоб разметить веб-сервис любой сложности, и они достаточно удобны. Проблем с ними не возникнет, если весь проект построен на JavaEE со своим DI, EJB и прочими плюшками, идущими из коробки. Тем не менее сейчас гораздо более популярным решением является spring со своим не менее большим стеком проектов на все случаи жизни и неудивительно, если захочется перейти на него. Однако мигрировать на spring-ws для крупного проекта может быть затратным по времени, особенно если использовался подход code-first, т.е. по имеющимся доменам генерируется wsdl. Один из вариантов оставить JAX-WS в строю и при этом пользоваться spring-ws с новыми веб-сервисами уже на новом фреймфорке описывается дальше в этой статье.
JAX-WS (и вариант для REST JAX-RS) сам по себе не так уж плох - в нем хватает аннотаций, чтоб разметить веб-сервис любой сложности, и они достаточно удобны. Проблем с ними не возникнет, если весь проект построен на JavaEE со своим DI, EJB и прочими плюшками, идущими из коробки. Тем не менее сейчас гораздо более популярным решением является spring со своим не менее большим стеком проектов на все случаи жизни и неудивительно, если захочется перейти на него. Однако мигрировать на spring-ws для крупного проекта может быть затратным по времени, особенно если использовался подход code-first, т.е. по имеющимся доменам генерируется wsdl. Один из вариантов оставить JAX-WS в строю и при этом пользоваться spring-ws с новыми веб-сервисами уже на новом фреймфорке описывается дальше в этой статье.
JAX-WS и Spring
Основной задачей интеграции JAX-WS сервисов в spring-ws-проект является именнно добавление классов сервисов в контекст спринга. Сделать это можно с помощью отдельной либы jaxws-spring.Этот вариант был взят из статьи вездесущего mkyong: https://www.mkyong.com/webservices/jax-ws/jax-ws-spring-integration-example. Там достаточно подробно все описывается и можно для простого случая делать именно по ней, но здесь я приведу еще некоторые комментарии к этой реализации для большего понимания, что и зачем вообще делается.
В статье используется xml-конфигурация веб-приложения и спринга, т.к. конфигурацию веб-сервисов в либе проще производить именно в xml-формате. Но если есть желание, то всегда эту часть можно изолировать и сконфигурировать все остальное в java-конфиге.
Итак, пусть для примера у нас есть интерфейс JAX-WS веб-сервиса:
Реализация этого сервиса такая:
Чтобы это все заработало, необходимо во-первых добавить бин реализации веб-сервиса в спринг-контекст, а во-вторых обеспечить работу веб-сервиса, чтобы он мог обрабатывать сообщения.
Для 1-го пункта создаем конфиг applicationContext.xml, в котором перечисляем нужные бины (естественно можно воспользоваться автосканом, но тогда не забудьте пометить эндпоинт какой-нибудь аннотацией):
После чего добавляем листенер в web.xml для загрузки контекста:
Пункт 2 реализуется как раз через сервлет диспатчера библиотеки jaxws-spring. Для этого подключаем ее к проекту (актуальную версию можно взять с сайта maven-репозитория):
Добавляем конфигурацию для веб-сервиса в контекст applicationContext.xml (изменения выделены жирным):
В статье используется xml-конфигурация веб-приложения и спринга, т.к. конфигурацию веб-сервисов в либе проще производить именно в xml-формате. Но если есть желание, то всегда эту часть можно изолировать и сконфигурировать все остальное в java-конфиге.
Итак, пусть для примера у нас есть интерфейс JAX-WS веб-сервиса:
@WebService(name = "IncomeWS") @SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE) public interface IncomeWS { @WebMethod GetYearIncomeResponse getYearIncome(GetYearIncomeRequest request); }Содержание реквеста и респонса здесь не играет никакой роли.
Реализация этого сервиса такая:
@WebService(endpointInterface = "org.salamansar.common.ws.IncomeWS", serviceName = "IncomeWS", portName = "IncomeWSPort") public class IncomesWSEndpoint implements IncomeWS { @Autowired private IncomeService service; //спринговый сервис, который мы хотим использовать @Override public GetYearIncomeResponse getYearIncome(GetYearIncomeRequest request) { ...//использование сервиса и формирование респонса } }Реализация спрингового сервиса так же тут не имеет значения.
Чтобы это все заработало, необходимо во-первых добавить бин реализации веб-сервиса в спринг-контекст, а во-вторых обеспечить работу веб-сервиса, чтобы он мог обрабатывать сообщения.
Для 1-го пункта создаем конфиг applicationContext.xml, в котором перечисляем нужные бины (естественно можно воспользоваться автосканом, но тогда не забудьте пометить эндпоинт какой-нибудь аннотацией):
<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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config/> <bean name="incomeService" class="org.salamansar.common.ws.IncomeService"/> <bean name="incomeEndpoint" class="org.salamansar.spring.IncomesWSEndpoint"/> </beans>Примечание: не збываем включать <context:annotation-config/>, чтобы работала инжекция через @Autowired. Если об этом забыть, при старте не будет ошибок, но при обращении к сервису вылетит NPE.
После чего добавляем листенер в web.xml для загрузки контекста:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
Пункт 2 реализуется как раз через сервлет диспатчера библиотеки jaxws-spring. Для этого подключаем ее к проекту (актуальную версию можно взять с сайта maven-репозитория):
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.4</version> </dependency>
<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:wss="http://jax-ws.dev.java.net/spring/servlet" xmlns:ws="http://jax-ws.dev.java.net/spring/core" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://jax-ws.dev.java.net/spring/core http://jax-ws.dev.java.net/spring/core.xsd http://jax-ws.dev.java.net/spring/servlet http://jax-ws.dev.java.net/spring/servlet.xsd"> <context:annotation-config/> <wss:binding url="/incomes-jaxws/IncomesWS"> <wss:service> <ws:service bean="#incomeEndpoint" /> </wss:service> </wss:binding> <bean name="incomeService" class="org.salamansar.common.ws.IncomeService"/> <bean name="incomeEndpoint" class="org.salamansar.spring.IncomesWSEndpoint"/> </beans>Здесь атрибут url, как можно догадаться по его названию, определяет адрес от рута приложения в контейнере. Далее необходим диспатчер, который доставит сообщения к компоненту. Для этого необходимо добавить сервлет:
<servlet> <servlet-name>jaxws-spring</servlet-name> <servlet-class>com.sun.xml.ws.transport.http.servlet.WSSpringServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>jaxws-spring</servlet-name> <url-pattern>/incomes-jaxws/*</url-pattern> </servlet-mapping>Тут следует обратить внимание как раз на этот путь "/incomes-jaxws/*". Нужно следить, чтобы все веб-сервисы, объявленые в контексте выше, принадлежали этому пути.
После этого веб-сервис можно деплоить на томкат и он станет доступен по указанному относительному адресу
/incomes-jaxws/IncomesWS. Скачать wsdl можно через url /incomes-jaxws/IncomesWS?wsdl.
Использование JAX-WS и Spring-WS в одном проекте
Итак, предположим появилась потребность добавить в проект веб-сервисы, но уже на spring-ws. Делается это ровным счетом так же, как и для проекта без JAX-WS конфига с единственным исключением - диспатчер должен обслуживать свой путь и соответственно в клиентском урле так же должен быть этот путь Так же рекомендую использовать схему с рутовым и дочерними конфигами. В этом случае бины, относящиеся только к spring-ws, будут в отдельном контексте и зависеть от рутового контекста, который в свою очередь так же используется и для JAX-WS веб-сервисов.
Для начала добавляем необходимые зависимости:
<dependency> <groupId>org.springframework.ws</groupId> <artifactId>spring-ws-core</artifactId> <version>3.0.1.RELEASE</version> </dependency> <dependency> <groupId>wsdl4j</groupId> <artifactId>wsdl4j</artifactId> <version>1.6.2</version> <scope>runtime</scope> </dependency>Далее следует создать xsd, которая содержит схему используемых сообщений. Содержание ее так же не имеет здесь значения. В данном примере я поместил ее в папку src/main/webapp/WEB-INF/xsd и назвал IncomesWS.xsd.
Примечание: Хранить xsd не обязательно в WEB-INF - это может быть отдельная папка, но при сборке нужно будет перенести в WEB-INF, чтобы у конфига wsdl, который мы добавим дальше, был к ним доступ.
Для генерации нужных классов, добавляем плагин xjc:
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>jaxb2-maven-plugin</artifactId> <version>2.3.1</version> <executions> <execution> <goals> <goal>xjc</goal> </goals> </execution> </executions> <configuration> <sources> <source>src/main/webapp/WEB-INF/xsd</source> </sources> </configuration> </plugin>Создаем эндпоинт с использованием сгенерированных классов:
@Endpoint public class SpringIncomesWSEndpoint { @Autowired private IncomeService service; @PayloadRoot(localPart = "getYearIncomeRequest", namespace = "http://salamansar.org/springws/incomes") @ResponsePayload public GetYearIncomeResponse getYearIncome(@RequestPayload GetYearIncomeRequest request) { ... //реализация эндпоинта } }IncomeService здесь тот же самый, который используется для jax-ws веб-сервиса. Этот сервис располагается в рутовом контексте, а для spring-ws предлагаю создать новый incomes-ws-servlet.xml, в котором будет объявлен наш эндпоинт и сконфигурирована wsdl.
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sws="http://www.springframework.org/schema/web-services" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd"> <context:annotation-config/> <sws:annotation-driven/> <sws:dynamic-wsdl id="IncomesWS" portTypeName="IncomesWS" locationUri="/incomes-spring/IncomesWS"> <sws:xsd location="WEB-INF/xsd/IncomesWS.xsd" /> </sws:dynamic-wsdl> <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory" /> <bean id="springIncomesWSEndpoint" class="org.salamansar.spring.SpringIncomesWSEndpoint" /> </beans>Элемент dynamic-wsdl по сути нужен только для того, чтобы сконструировать wsdl и ее можно было бы скачать. Так что если этого не требуется, то весь этот блок можно не добавлять.
Примечание: бин messageFactory нужно определять обязательно - он делает доступным маршаллинг из POJO-объекта и обратно. Сам маршаллер определять не нужно - по дефолту создастся Jaxb2Marshaller.
Обратите внимание - сам IncomeService не определяется в этом конфиге, а так же нет никаких импортов конфигов. Но этот сервис определен в рутовом конфиге и все корректно подтянется при загрузке иерархичного контекста.
Осталось добавить спринговый диспатчер в web.xml, который доставит сообщение к эндпоинту.
<servlet> <servlet-name>incomes-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> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>incomes-ws</servlet-name> <url-pattern>/incomes-spring/*</url-pattern> </servlet-mapping>Название конфига (incomes-ws-servlet.xml) и сервлета не случайно частично совпадают. По умолчанию spring ищет конфиг по шаблону <имя_сервлета>-servlet.xml, что и было сделано в этом примере. При желании, конечно, можно явно задать путь к конфигу для диспатчера.
После проведения всех указанных выше манипуляций, можно деплоить приложение в томкате, где будут доступны обе версии веб-сервисов по своим определенным урлам. Скачать WSDL для спрингового сервиса можно через урл incomes-spring/IncomesWS.wsdl. Реквесты же будут отрабатывать по любому урлу под incomes-spring/*, т. к. был настроен диспатчеринг по телу сообщения (через @PayloadRoot).
Комментарии