Давайте в этой статье мы расмотрим технологию web service JAX-WS, которая позволяет нам при помощи аннотаций
превращать наш класс в веб службу, а потом автоматически генерировать и получить wsdl файл настройки, soap запросы и ответы. Такой метод разработки принято называть “contract-last”, для простоты развертывания web service, я буду использовать сервер приложений wildfly-8.2.0.Final, в него встроены библиотеки apache cxf. Основной протокол по которому
контактирует наш веб сервис это HTTP, по этому нам нужно собрать проект как web application, файл с расширением *.war и соблюсти правильность развертывания
проекта для сервера приложений wildfly. Приступим к реализации простого
приложения, напишем для начала интерфейс webService1:
package com.vit;
import javax.jws.WebMethod;
import javax.jws.WebService;
@WebService(targetNamespace =
"http://lopanov.com")
public interface webService1 {
@WebMethod(operationName
= "",action = "",exclude = false)
public String getName();
}
И напишем
сразу класс реализующий наш интерфейс, я потом подробно раскажу что мы сделали
и для чего подробно:
package com.vit;
import javax.jws.WebService;
@WebService(serviceName = "web",
portName = "HelloWorld", name = "Hello",
endpointInterface = "com.vit.webService1", targetNamespace =
"http://lopanov.com")
public class webService1Impl implements
webService1 {
@Override
public String getName() {
return new String("Hello my Name is WebService");
}
}
Собираем
все в war файл и
помещаем его в каталог JBOSS_HOME/Standalone/deployments, как устанавливать и запускать wildfly я писал в одной их прошлых статей, после деплоя, в логах сервера wildfly появятся строчки примерно
такого вида:
14:51:16,440 INFO [org.jboss.as.server.deployment] (MSC service
thread 1-4) JBAS015876: Starting deployment of "jboss_jaxws.war"
(runtime-name: "jboss_jaxws.war")
14:51:17,511 INFO [org.jboss.ws.cxf.metadata] (MSC service
thread 1-3) JBWS024061: Adding service endpoint metadata:
id=com.vit.webService1Impl
address=http://localhost:8080/jboss_jaxws/web
implementor=com.vit.webService1Impl
serviceName={http://lopanov.com}web
portName={http://lopanov.com}HelloWorld
annotationWsdlLocation=null
wsdlLocationOverride=null
mtomEnabled=false
14:51:17,885 INFO [org.apache.cxf.service.factory.ReflectionServiceFactoryBean]
(MSC service thread 1-3) Creating Service {http://lopanov.com}web from class
com.vit.webService1
14:51:18,560 INFO [org.apache.cxf.endpoint.ServerImpl] (MSC
service thread 1-3) Setting the server's publish address to be
http://localhost:8080/jboss_jaxws/web
14:51:18,705 INFO [org.jboss.ws.cxf.deployment] (MSC service
thread 1-3) JBWS024074: WSDL
published to:
file:/C:/wildfly-8.2.0.Final/standalone/data/wsdl/jboss_jaxws.war/web.wsdl
14:51:18,822 INFO [org.jboss.as.webservices] (MSC service
thread 1-5) JBAS015539: Starting service
jboss.ws.endpoint."jboss_jaxws.war"."com.vit.webService1Impl"
Полсе
деплоя приложения мы можем получить WSDL
файл, он содержит описание web сервиса. Его мы можем получить двумя
путями, он нам то же понадобиться в дальнейшем. Первое это зайти браузером в
веб сервис http://localhost:8080/jboss_jaxws/web (строку
ищем в логе сервера address=) и
присоединить к нему префикс ?wsdl
и так у нас получится сылка http://127.0.0.1:8080/jboss_jaxws/web?wsdl перейдите
по ней или просто перейдите в каталог который нам вывели в логе сервера WSDL published to: file:/C:/wildfly-8.2.0.Final/standalone/data/wsdl/jboss_jaxws.war/web.wsdl:
<?xml version="1.0" ?>
<wsdl:definitions
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:tns="http://lopanov.com" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:ns1="http://schemas.xmlsoap.org/soap/http" name="web"
targetNamespace="http://lopanov.com">
<wsdl:types>
<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://lopanov.com" elementFormDefault="unqualified"
targetNamespace="http://lopanov.com" version="1.0">
<xs:element name="getName"
type="tns:getName"></xs:element>
<xs:element name="getNameResponse"
type="tns:getNameResponse"></xs:element>
<xs:complexType name="getName">
<xs:sequence></xs:sequence>
</xs:complexType>
<xs:complexType name="getNameResponse">
<xs:sequence>
<xs:element minOccurs="0" name="return"
type="xs:string"></xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>
</wsdl:types>
<wsdl:message
name="getName">
<wsdl:part element="tns:getName"
name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:message name="getNameResponse">
<wsdl:part element="tns:getNameResponse"
name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:portType
name="webService1">
<wsdl:operation
name="getName">
<wsdl:input message="tns:getName"
name="getName">
</wsdl:input>
<wsdl:output message="tns:getNameResponse"
name="getNameResponse">
</wsdl:output>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding
name="webSoapBinding" type="tns:webService1">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"></soap:binding>
<wsdl:operation name="getName">
<soap:operation soapAction=""
style="document"></soap:operation>
<wsdl:input name="getName">
<soap:body use="literal"></soap:body>
</wsdl:input>
<wsdl:output name="getNameResponse">
<soap:body use="literal"></soap:body>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service
name="web">
<wsdl:port binding="tns:webSoapBinding"
name="HelloWorld">
<soap:address
location="http://localhost:8080/jboss_jaxws/web"></soap:address>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Для начала
взглянем как просто мы можем сделать из нашего класса web сервис, для этого нам всего лишь
требуется аннотация @WebService(). По умолчанию эта аннотация описывает web сервис, а точнее тег <wsdl:service
в wsdl файле, метод getName() преобразовывается в service operation –
операции какие можно вызвать у web
сервиса,
а еще точнее в тег <wsdl:operation
в файле wsdl, его я пометил аннотацией @WebMethod(). Перейдем
к описанию параметров аннотации @WebService(), первый
параметр name
= "Hello", он
описывает имя <wsdl:portType
в
файле wsdl, но если у нас есть java интерфейс webService1, то тег wsdl:portType
получит
в атрибуте name значение "webService1". Следуюций
параметр targetNamespace
= "http://lopanov.com" описывает
в файле wsdl - XML namespace который должен быть применен ко всем
тегам в файле wsdl по
умолчанию. Следующий параметр serviceName = "web" описывает
тег <wsdl:service, параметр
endpointInterface = "com.vit.webService1" сылается
на наш java интерфейс. По умолчанию все public методы класса становятся оперециями web сервиса, по этому аннотацию @WebMethod() можно не использовать,
но если вам нужно утановить имя тега <wsdl:operation, то нужно
использовать параметр operationName, по умолчанию он берет имя нашего java метода getName. Параметр action устанавливает в wsdl файле <soap:operation параметр soapAction. Еще однин
интересный параметр это exclude если его установить в true, то java метод класса не будет преобразуется в service operation, расмотрим пример:
@WebService()
public class webService1Impl implements
webService1 {
@Override//этот метод может быть вызван в web сервисе, преобразуется в web service operation
public String getName() {
printLog();//наш java метод можно использовать как
вспомогательный метод вывода лога
return new String("Hello my Name WebService");
}
@WebMethod(exclude = true)//нельзя вызвать метод в web сервисе, не преобразуется в web service operation
public void printLog() {
System.out.printf("%s","Print Log");
}
}
Напишем
программу клиент web
сервиса:
package com.vit;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import java.net.MalformedURLException;
import java.net.URL;
public class MainWebService {
public static void main(String args[]) {
new MainWebService();
}
MainWebService() {
try {
/*Указываем точное имя нашего web сервиса, оно состоит из значений атрибута name и targetNamespace файла wsdl:
<?xml version="1.0" ?>
<wsdl:definitions … name="web"
targetNamespace="http://lopanov.com"> */
QName serviceName = new QName("http://lopanov.com",
"web");
/*
вызываем наш web сервис, нужно указать адрес по
которому он развернут, берем из атрибута location
тега <soap:address location="http://localhost:8080/jboss_jaxws/web"> */
Service service = Service.create(new
URL("http://127.0.0.1:8080/jboss_jaxws/web"), serviceName);
/* вызвыаем наш java интерфейс, в файле wsdl это тег <wsdl:portType name="webService1">
*/
webService1 helloWorldService = service.getPort(webService1.class);
/*
вызываем метод интерфейса, а в понятиях web сервиса это операция <wsdl:operation name="getName">*/
System.out.println(helloWorldService.getName() + "\n");
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
Вот такой
вывод получаем на экране, при вызове нашего web сервиса:
Hello
my Name WebService
Вот какой бардак
получается, что бы работать с web
сервисами
нужно неплохо знать xml, xml schema, протокол wsdl и soap. Давайте дальше углубимся и расмотрим что
представляет из себя протокол soap, для
этого включим логгирование, не забываем что мы работаем со стеком wildfly 8 и apache
cxf, по этому это будет справедливо
только для этих технологий. Логирование включается посредством аннотации @org.apache.cxf.annotations.Logging
смотрим наш пример:
@WebService(serviceName = "web",
portName = "HelloWorld", name = "Hello",
endpointInterface = "com.vit.webService1", targetNamespace =
"http://lopanov.com")
@org.apache.cxf.annotations.Logging
public class webService1Impl implements
webService1 {
….
}
Так как мы
пользуемся встроеным cxf
в
wildfly 8 в виде модуля, то нам нужно сделать
еще одну вещь. По умолчанию у меня был настроен модуль в wildfly 8, я кратко опишу настройки в файле JBOSS_HOME/Standalone/configuration/standalone.xml:
<?xml version="1.0" ?>
<server
xmlns="urn:jboss:domain:2.2">
<extensions>
<extension module="org.jboss.as.clustering.infinispan"/>
….
<!—подключаем модуль webservices
-->
<extension
module="org.jboss.as.webservices"/>
....
</extensions>
….
</profile>
….
<subsystem
xmlns="urn:jboss:domain:webservices:1.2">
<wsdl-host>${jboss.bind.address:127.0.0.1}</wsdl-host>
<endpoint-config name="Standard-Endpoint-Config"/>
<endpoint-config name="Recording-Endpoint-Config">
<pre-handler-chain
name="recording-handlers" protocol-bindings="##SOAP11_HTTP
##SOAP11_HTTP_MTOM ##SOAP12_HTTP ##SOAP12_HTTP_MTOM">
<handler
name="RecordingHandler" class="org.jboss.ws.common.invocation.RecordingServerHandler"/>
</pre-handler-chain>
</endpoint-config>
<client-config name="Standard-Client-Config"/>
</subsystem>
….
</profile>
….
</server>
В wildfly 8 включены модули Apache CXF, но по
умолчанию они не задействованы в нашем проекте, т.е. если вы используете спецефичные
аннотации Apache CXF, они не будут работать, подробней можно почитать в документации. Для их включения нужно
создать файл манифест: META-INF/MANIFEST.MF. Дальше нам нужно прописать строчку в нем Dependencies: org.apache.cxf, уменя он
такой:
Manifest-Version: 1.0
Description: yourdescription
Dependencies:
org.apache.ws.security,org.apache.cxf
так как я использую
для сбора проекта maven, то мне
пришлось добавить plugin который бы добавлял мой файл манифест MANIFEST.MF в проект,
дальше привожу полный конфиг pom.xml с моими зависимостями. Плагин я выледил
желтым цветом:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.vit</groupId>
<artifactId>jboss_jaxws</artifactId>
<packaging>war</packaging>
<version>1.0</version>
<name>jboss_jaxws</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.ejb</groupId>
<artifactId>jboss-ejb-api_3.2_spec</artifactId>
<version>1.0.0.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.annotation</artifactId>
<version>3.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jboss-annotations-api_1.2_spec</artifactId>
<version>1.0.0.Final</version>
<scope>provided</scope>
</dependency>
<!---->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-core</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jboss-annotations-api_1.2_spec</artifactId>
<version>1.0.0.Final</version>
<scope>provided</scope>
</dependency>
</dependencies>
<repositories>
<!-- JBoss Repository used for Java EE 6 pieces -->
<repository>
<id>repository.jboss.org</id>
<name>JBoss Repository</name>
<url>http://repository.jboss.org/nexus/content/groups/public-jboss/</url>
</repository>
</repositories>
<build>
<finalName>jboss_jaxws</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<archive>
<manifestFile>src/main/webapp/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
Заново деплоим
наш web сервис, запускаем
клиента и получаем вывод, точнее смешанный лог HTTP и SOAP протокола:
09:07:41,176 INFO
[org.apache.cxf.services.web.HelloWorld.webService1] (default task-2)
Inbound Message
----------------------------
ID: 1
Address:
http://127.0.0.1:8080/jboss_jaxws/web?wsdl
Http-Method: GET
Content-Type:
Headers: {Accept=[text/html, image/gif,
image/jpeg, *; q=.2, */*; q=.2], connection=[keep-alive], Content-Type=[null],
Host=[127.0.0.1:8080], User-Agent=[Java/1.8.0_31]}
--------------------------------------
09:07:41,867 INFO
[org.apache.cxf.services.web.HelloWorld.webService1] (default task-3)
Inbound Message
----------------------------
ID: 2
Address:
http://localhost:8080/jboss_jaxws/web
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml; charset=utf-8
Headers: {Accept=[text/xml,
multipart/related], connection=[keep-alive], Content-Length=[268],
content-type=[text/xml; charset=utf-8], Host=[localhost:8080],
SOAPAction=[""], User-Agent=[JAX-WS RI 2.2
.9-b130926.1035
svn-revision#5f6196f2b90e9460065a4c2f4e30e065b245e51e]}
Payload: <?xml version="1.0"
?><S:Envelope
xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header></SOAP-ENV:Header><S:Body><ns2:getName
xmlns:ns2="http://lopanov.com"></ns2:getName></S:Body></S:Envelope>
--------------------------------------
09:07:42,029 INFO
[org.apache.cxf.services.web.HelloWorld.webService1] (default task-3)
Outbound Message
---------------------------
ID: 2
Response-Code: 200
Encoding: UTF-8
Content-Type: text/xml
Headers: {}
Payload: <soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"></SOAP-ENV:Header><soap:Body><ns2:getNameResponse
xmlns:ns2="http://lopanov.com"><return>Hello my Name
WebService</return></ns2:getNameResponse></soap:Body></soap:Envelope>
--------------------------------------
Самое
интересное в этом логе это секция Payload: один запрос операции нашего веб
сервиса, а точнее сообщение <ns2:getName
и
один ответ на наш запрос, сообщение <ns2:getNameResponse
Заметьте
что имена в soap
запросе
и ответе совпадают с именами из нашего wsdl файла, а если быть точнее с тегом <wsdl:message т. е. мы обмениваемся soap
сообщениями на основе параметров и типов сообщений которые описаны в нашем wsdl файле. Посмотрим на кусок wsdl файла:
<wsdl:portType name="webService1"> <!--наш веб сервис -->
<wsdl:operation name="getName"> <!--операция, метод интерфейса -->
<wsdl:input message="tns:getName"
name="getName"> <!--входящее сообщение, атрибут message="tns:getName" это сылка на сообщение - тег <wsdl:message name="getName"> -->
</wsdl:input>
<wsdl:output message="tns:getNameResponse"
name="getNameResponse"> <!-- исходящее сообщение, атрибут message="tns:getNameResponse"
это сылка на сообщение – тег <wsdl:message name="getNameResponse"> -->
</wsdl:output>
</wsdl:operation>
</wsdl:portType>
<wsdl:message name="getName">
<wsdl:part element="tns:getName"
name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:message
name="getNameResponse">
<wsdl:part element="tns:getNameResponse"
name="parameters">
</wsdl:part>
</wsdl:message>
Если
хотите преуспеть в написании веб сервисов вам придется опустится до изучения
протоколов и спецификаций, за счет этого достигается высший уровень программирования
веб сервисов.
Комментариев нет:
Отправить комментарий