7.06.2016

Web Service – JAX-WS, что нужно знать



Давайте в этой статье мы расмотрим технологию 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>

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

Комментариев нет:

Отправить комментарий