Skip to content

Web service JAX-WS

Синопсис

В этом посте рассмотрим пример создания веб сервиса спецификации JAX-WS.
Начну с того, что вообще означает это словосочетание «спецификация JAX-WS». Вообще это означает, что есть некий стандарт называемый JAX-WS или если полностью Java API for XML Web Services который задает некие правила или контракт, которого надо придерживаться при его реализации. Так вот в этой статье мы не будем его реализовывать, в смысле контракт, мы будем этим контрактом пользоваться. А точнее, мы будем пользоваться конкретной реализацией спецификации JAX-WS. В мире есть несколько реализаций спецификации JAX-WS, ну например Apache Axis2.
То есть главное что нужно понять, что JAX-WS это не библиотека, не фреймворк, не приложение, это скорее такая дока, где описаны спецификации, которые должны быть реализованы конкретным фреймворком. В этом примере мы создадим наш веб сервис абстрагируясь от конкретных фреймворков, то есть будем полагаться только на спецификации. Что это значит? Это значит, что согласно спецификации JAX-WS, чтобы интерфейс назначить веб сервисом, нужно применить к нему аннотацию @WebService не задумываясь о том, откуда эта аннотация берется. Мы просто знаем, что аннотация @WebService обязана быть, согласно спецификации JAX-WS. А вот потом, когда дело дойдет до деплоинга на сервер, тогда мы должны будем определиться с реализацией спецификации. В нашем случае, что для glassfish, что для tomcat будет использоваться реализация JAX-WS RI, только с гласфишом она уже идет из под коробки, а вот для tomcat нам придется докинуть реализацию JAX-WS RI в ручную, джарники которой мы загрузим с оф. сайта jax-ws.java.net и положим их в каталог lib сервлета, который мы будем деплоить в контейнер сервлетов tomcat.

Что такое Web Service

Чтобы понять что такое веб сервис, надо сначало понять как работает обычная программа, на локальной машине, которая состоит из обычных функций. Допустим есть программа, которая состоит из двух функций: main() и getSum(). Функция main() будет вызывать функцию getSum(), то есть функция getSum() предоставляет сервис для функции main() в пределах запущеной программы:

schema114
Если мы хотим чтобы функцией getSum() можно было воспользоваться не только в пределах выполняемой программы, а предоставить к ней доступ для всех программ на машине, то можно вынести объект, в котором этот метод реализован, в библиотеку, в этом случае видимость объекта вместе с функцией расширяется на всю локальную машину:

schema22
Но доступ к этому объекту вместе с функцией getSum() ограничен в пределах локальной машины. Если на какой-нибудь другой машине, какой-нибудь программе понадобиться функция getSum(), то придется переносить объект, предоставляющий функцию getSum() на ту другую машину вручную. Так вот, чтобы ничего никуда не переносить, то можно объект, предоставляющий функцию getSum() расшарить или опубликовать по сети всем другим программам, которые в нем нуждаются. Иначе говоря предоставить функцию getSum() по сети как сервис. В этом заключается идея веб сервисов, предоставляется обычная функция класса как сервис, а так как доступ к ней организуется по сети, то называется web service, а вызов метода называется удаленный вызов метода:

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

Сервисная часть. Структура проекта

Напишем веб сервис который будет вычислять площадь геометрических фигур, таких как окружность или прямоугольник. На стороне клиента будет определяться, площадь какой фигуры мы хотим получить и затем данные о геометрической фигуры будут передаваться на веб сервис, а веб сервис после обработки данных вернет площадь фигуры.
Приложение веб сервиса будет состоять из шести джава-классов, которые будут в себя включать интерфейс Figure.java, сущности Circle.java и Rectangle.java, фактори геометрических фигур FigureFactory.java, сам веб сервис PrintFigureService.java и его имплиментацию PrintFigureImpl.java:

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

print-figure-webservice
    ├──src
    │   ├─Figure.java     
    │   ├─Circle.java
    │   ├─Rectangle.java
    │   ├─FigureFactory.java
    │   ├─PrintFigureService.java
    │   └─PrintFigureImpl.java
    ├──WebContent
    │   └─WEB-INF
    │       ├─sun-jaxws.xml
    │       └─web.xml
    └──build.sh

Warning
В первой структуре кажется, что джава классы помещены в дефолтовый пакет, на самом деле в каждом из них есть ключевое слово package указывающее принадлженость каждого класса к своему пакету, поэтому после компиляции классы будут разложены по нужным каталогам-пакетам.
В Linux (по крайней мере в Ubuntu/Mint) так делать можно, на семерке нет, поэтому будьте осторожны.

Кроме джава классов еще будет два конфигурационных xml файла, это web.xml для контейнера сервлетов и sun-jaxws.xml где будет информация об энд поинтах.
После сборки проекта в корне проекта появится каталог build с war-архивом готовым для деплоя на эппликэйшн сервер:

build
   ├──WEB-INF
   │   ├──classes
   │   │   ├──com
   │   │   │   ├──component
   │   │   │   ├──figure
   │   │   │   │   ├──Figure.class
   │   │   │   │   ├──Circle.class
   │   │   │   │   └──Rectangle.class
   │   │   │   └──Print.class
   │   │   ├──factory
   │   │   │   └──FigureFactory.class
   │   │   └──service
   │   │       ├──impl
   │   │       │   └──PrintFigureImpl.class
   │   │       └──PrintFigureService.class 
   │   ├──sun-jaxws.xml
   │   └──web.xml
   └──print-figure-service.war

Java code

Добавим в каталог src java код.

Figure.java

package com.component.figure;
  
public abstract class Figure {
    private String name;
  
    public Figure(String name) {
        this.name = name;
    }
  
    public String getName() {
        return this.name;
    }
  
    public abstract double square();
}

Circle.java

package com.component.figure;
  
public class Circle extends Figure {
    private int radius;
    public static double PI = 3.1415;
      
    public Circle(String name, int radius) {
        super(name);
        this.radius = radius;
    }
  
    @Override
    public double square() {
        return PI*this.radius*this.radius;
    }
}

Rectangle.java

package com.component.figure;
  
public class Rectangle extends Figure {
    private int width;
    private int height;
  
    public Rectangle(String name, int width, int height) {
        super(name);
        this.width = width;
        this.height = height;
    }
  
    @Override
    public double square() {
        return this.width*this.height;
    }
}

FigureFactory.java

package com.factory;

import com.component.figure.Figure;
import com.service.PrintFigureService;
import com.service.impl.PrintFigureImpl;
import com.component.figure.Circle;
import com.component.figure.Rectangle;
import java.util.List;

public class FigureFactory {
	public static Figure createFigure(String type, StringBuilder description, int[] values) {
		if (type.equals(PrintFigureService.CIRCLE)) {
			description.append("Radius is " + values[0] + ". ");
			return new Circle(type, values[0]);
		} else if (type.equals(PrintFigureService.RECTANGLE)) {
			description.append("First side is " + values[0] + ", second side is " + values[1] + ". ");
			return new Rectangle(type, values[0], values[1]);
		}
		return null;
	}
}

PrintFigureService.java

package com.service;

import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService
public interface PrintFigureService {
	public static String CIRCLE = "CIRCLE";
	public static String RECTANGLE = "RECTANGLE";

	@WebMethod
	public String showInfo(String type, int... values);
}

PrintFigureImpl.java

package com.service.impl;

import com.factory.FigureFactory;
import com.component.figure.Figure;
import com.service.PrintFigureService;
import javax.jws.WebService;

@WebService(endpointInterface = "com.service.PrintFigureService")
public class PrintFigureImpl implements PrintFigureService {

	@Override
	public String showInfo(String type, int... values) {		
		StringBuilder description = new StringBuilder();
		description.append("This is a " + type + ". ");
		Figure figure = FigureFactory.createFigure(type, description, values);
		description.append("Square of " + figure.getName() + " is " + figure.square());
		return description.toString();
	}	
}

Конфигурация веб сервиса

Добавьте два xml файла в каталог 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/j2ee/dtds/web-app_2_3.dtd">
 
<web-app>
    <listener>
        <listener-class>
                com.sun.xml.ws.transport.http.servlet.WSServletContextListener
        </listener-class>
    </listener>
    <servlet>
        <servlet-name>figure</servlet-name>
        <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>figure</servlet-name>
        <url-pattern>/figure</url-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>120</session-timeout>
    </session-config>
</web-app>

sun-jaxws.xml

<?xml version="1.0" encoding="UTF-8"?>
<endpoints
  xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"
  version="2.0">
  <endpoint
      name="printFigure"
      implementation="com.service.impl.PrintFigureImpl"
      url-pattern="/figure"/>
</endpoints>

Теперь разберем два последних xml файл подробней. Первый из них это web.xml в котором содержится информация необходимая для контейнера сервлетов. В нем определен диспетчер сервлет com.sun.xml.ws.transport.http.servlet.WSServlet это класс, который вызывается контейнером при обращении к этому сервлету через URL, так например когда мы вводим адрес http://localhost:8080/print-figure-service/figure то, для того, чтобы найти нужный сервлет, строка делится на три сегмента (точнее сказать мы ее условно разделим на три сегмента, в рамках данного конкретного примера):

  1. http://localhost:8080 — адрес самого контейнера сервлета, общий для всех сервлетов, которые крутятся в одном контейнере
  2. /print-figure-service — уточнение к какому сервлету или приложению происходит обращение в контейнере
  3. /figure — класс сервлета. То есть это тот класс сервлета, на который передается управление, как только сервлет найден в контейнере

Однако тут стоит обратить внимание, что самого класса с названием /fugure в приложении нет, это часть URL адреса, которая отображается на реальный сервлет. В файле web.xml, который называется дескриптором развертывания, определен шаблон URL адреса и айдишник сервлета на который этот URL мапится. Обратите внимание на тег servlet-mapping в web.xml:

  <servlet-mapping>
    <servlet-name>figure</servlet-name>
    <url-pattern>/figure</url-pattern>
  </servlet-mapping>

То есть часть URL адреса /fugure ассоциируется с сервлетом по айдишнику figure, и в том же файле ищется сервлет с айдишником figure, это com.sun.xml.ws.transport.http.servlet.WSServlet:

 <servlet>
    <servlet-name>figure</servlet-name>
    <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

Таким образом адрес http://localhost:8080/print-figure-service/figure инициирует выполнение сервлета com.sun.xml.ws.transport.http.servlet.WSServlet, но почему именно его? Дело в том, что сервлет WSServlet знает к какому энд поинту обращаться в зависимости от URL адреса, в нашем же случае, WSServlet знает, что если в третьем сегменте URL адреса будет /figure, то обращаться нужно к энд поинту com.service.impl.PrintFigureImpl. А теперь переходим к вопросу от куда он это знает? Ответ — мы ему заранее говорим об этом в xml файле sun-jaxws.xml. Посмотрим на тег endpoint в файле sun-jaxws.xml:

<endpoint name="printFigure"
      implementation="com.service.impl.PrintFigureImpl"
      url-pattern="/figure"/>

Как мы видим у тега endpoint есть три атрибута, атрибут name — пока не столько интересен, это просто айдишник эндпоинта, а вот атрибут implementation говорит о том, какую имплементацию веб сервера вызывать, когда значение атрибута url-pattern будет совпадать с третьим сегментом URL адреса, то есть с /figure.

Компиляция вэб сервиса

Традиционно компилировать будем shell скриптом.

Info
Почему я не пользуюсь более удобными инструментами, такими как maven или ant? Чтобы понять все фазы сборки проекта, которые сборщики скрывают, надо все сделать самому руками, а потом можно переходить на сборщики. То есть в бою можно и нужно пользоваться сборщиками, а вот во время учебы надо пройти через все этапы сборки проекта самому.

Добавьте в корень проекта build.sh файл, и добавьте в файл такой скрипт:

build.sh

PATH_TO_PROJECT=`pwd`
PROJECT_NAME=print-figure-service

# Удаляем каталог с предыдущим билдом
if test -d $PATH_TO_PROJECT/build;
then
	rm -rf $PATH_TO_PROJECT/build
fi

# Создаем новый каталог, куда будем помещать билд
mkdir $PATH_TO_PROJECT/build

cp -r $PATH_TO_PROJECT/WebContent/WEB-INF $PATH_TO_PROJECT/build
mkdir $PATH_TO_PROJECT/build/WEB-INF/classes

# Компилируем проект в каталог build/WEB-INF/classes
javac -d $PATH_TO_PROJECT/build/WEB-INF/classes src/*.java

# Создаем war архив
cd build
jar cvf $PROJECT_NAME.war WEB-INF
cd ..

После того, как shell скрипт добавлен, делаем его исполняемым, командой:

sudo chmod +x build.sh

и раним:

./build.sh

После этого в корне проекта появится каталог build в котором будет лежать war-архив print-figure-service.war.
Полностью весь проект серверной части можно скачать по ссылке:

Деплой веб сервиса на glassfish

Деплоить веб сервис будем на эппликэйшн сервер glassfish и в контейнер сервлетов tomcat.
Начнем с glassfish. Гласфиш должен быть уже запущен, это делается командой $GLASSFISH_DIR/bin/asadmin start-domain. Переходим по адресу http://localhost:4848, когда откроется админка glassfish, в панеле Common Tasks выбираем Applications и жмем Deploy…:

1
Жмем на батон Choose File для выбора war-архива:

2
После того как war-архив выбран, жмем на OK и war-архив начнет деплоиться на эппликэйшн сервер:

3
Если war-архив не содержит никаких не точностей, то он за деплоится без ошибок и появится на странице Applications:

4

Деплой веб сервиса на tomcat

Если мы деплоим на эппликэйшн сервер glassfish то чтобы сервис заработал, кроме как собственно деплоя, больше ничего делать не надо. А вот чтобы сервис заработал в контейнере сервлетов tomcat (это немножко не тоже самое, что эппликэйшн сервер), то нужно еще докинуть кое-какие библиотеки, которые с томкатом по умолчанию не идут. Это библиотеки одной из реализаций веб сервиса JAX-WS. Для этого сделайте вот что:

  1. Перейдите на сайт jax-ws.java.net
  2. Скачайте библиотеки JAX-WS RI на свой локальный жесткий диск (или на свой локальный SSD)
  3. Распакуйте архив
  4. Закиньте содержимое каталога lib, который найдете в каталоге jaxws-ri либо в каталог lib самого томкэта, либо в каталог WEB-INF/lib проекта.

Полностью серверную часть пригодную для томкэта можно скачать по ссылке:

Тест веб сервиса

После того как варник задеплоен, неважно куда либо на glassfish либо в tomcat, проверим что он работает.
Пройдите по ссылке http://localhost:8080/print-figure-service/figure?wsdl, если вернется WSDL-описание веб сервиса, значит сервис поднялся и работает. Это автосгенеренный xml, который доступен по URL адресу, его не надо никуда копировать, а просто по смотрите на него:

<!--
 Published by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is Metro/2.3
(tags/2.3-7528; 2013-04-29T19:34:10+0000) JAXWS-RI/2.2.8 JAXWS/2.2 svn-revision#unknown.
-->
<!--
 Generated by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is Metro/2.3
(tags/2.3-7528; 2013-04-29T19:34:10+0000) JAXWS-RI/2.2.8 JAXWS/2.2 svn-revision#unknown.
-->
<definitions xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsp="http://www.w3.org/ns/ws-policy" xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://impl.service.com/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/"
targetNamespace="http://impl.service.com/" name="PrintFigureImplService">
    <import namespace="http://service.com/" location="http://localhost:8080/print-figure-service/figure?wsdl=1"/>
    <binding xmlns:ns1="http://service.com/" name="PrintFigureImplPortBinding" type="ns1:PrintFigureService">
        <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
        <operation name="showInfo">
            <soap:operation soapAction=""/>
            <input>
                <soap:body use="literal"/>
            </input>
            <output>
                <soap:body use="literal"/>
            </output>
        </operation>
        </binding>
        <service name="PrintFigureImplService">
        <port name="PrintFigureImplPort" binding="tns:PrintFigureImplPortBinding">
            <soap:address location="http://localhost:8080/print-figure-service/figure"/>
        </port>
    </service>
</definitions>

Клиентская часть. Структура проекта

После того как сервис создан и задеплоин, приступим к клиентской части. Клиент у нас будет состоять из одного класса, который будет запрашивать площадь фигуры по переданным параметрам.
Структура проекта для клиента на первый взгляд выглядит просто:

webservice-client
    ├─src
    │   └─Client.java
    └─build.sh

Но после запуска скрипта build.sh в структуре каталогов появится кроме каталога build еще каталог webservice:

├─build
│   └─com
│       └─client
│           └─Client.class
└─webservice
    └─com
        └─service
            ├─impl
            │   ├─PrintFigureImplService.class
            │   ├─PrintFigureService.class
            │   ├─PrintFigureImplService.java
            │   └─PrintFigureService.java
            ├─package-info.class
            ├─ShowInfo.java
            ├─ObjectFactory.class
            ├─package-info.java
            ├─ShowInfoResponse.class
            ├─ObjectFactory.java
            ├─ShowInfo.class
            └─ShowInfoResponse.java

Что же это за каталог и что в нем? Дело в том, что когда скрипт запускается в нем выполняется команда запуска утилиты wsimport:

# Вытяним с сервера классы, которые будут поддерживать взаимодействие
# приложение с веб сервисом
wsimport -d webservice -keep http://localhost:8080/print-figure-service/figure?wsdl

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

Java code

Client.java

package com.client;

import com.service.impl.PrintFigureService;
import com.service.impl.PrintFigureImplService;
import java.util.List;
import java.util.ArrayList;

public class Client {
	public static void main(String [] args) {
		PrintFigureImplService figureService = new PrintFigureImplService();
		PrintFigureService service = figureService.getPrintFigureImplPort();
		List<Integer> arguments = new ArrayList<Integer>();
		try {
			arguments.add(Integer.parseInt(args[1]));
			arguments.add(Integer.parseInt(args[2]));
			System.out.println(service.showInfo(args[0], arguments));
		} catch (NumberFormatException exc) {
		    exc.printStackTrace();
		}
	}
}

Компиляция и запуск клиента

Добавьте в корень проекта клиента файл build.sh:

build.sh

PATH_TO_PROJECT=`pwd`

# Удаляем каталог с предыдущим билдом
if test -d $PATH_TO_PROJECT/build;
then
	rm -rf $PATH_TO_PROJECT/build
fi
# Создаем новый каталог, куда будем помещать билд
mkdir $PATH_TO_PROJECT/build

# Удаляем каталог с классами веб сервисов
if test -d $PATH_TO_PROJECT/webservice;
then
	rm -rf $PATH_TO_PROJECT/webservice
fi
# Создаем новый каталог который будет содержать классы веб сервисов
mkdir $PATH_TO_PROJECT/webservice
# Вытяним с сервера классы, которые будут поддерживать взаимодействие
# приложение с веб сервисом
wsimport -d webservice -keep http://localhost:8080/print-figure-service/figure?wsdl

# Компилируем проект в каталог build
javac -d $PATH_TO_PROJECT/build -cp webservice src/*.java

# Запускаем приложение с запросом посчитать нам площадь
# круга с радиусом 35 (второй параметр для круга игнорируется)
java -cp webservice:build com.client.Client "CIRCLE" 35 83

и запустите его командой ./build.sh. Сначала утилита wsimport пройдет по адресу http://localhost:8080/print-figure-service/figure?wsdl где найдет WSDL, анализирует его, с генерирует джава-плайн-текст классы и положит их в каталог webservice, с компилирует их туда же, вслед за этим с компилируется код самого клиента, и через скомпилированные классы обращения к веб сервисам обратиться к веб сервису который вернет информацию о площади окружности:

./build.sh
parsing WSDL...



Generating code...


Compiling code...

This is a CIRCLE. Radius is 35. Square of CIRCLE is 3848.3375

Полностью весь проект клиентской части можно скачать по ссылке:

Линки

Веб-сервисы. Шаг 1. Что такое веб-сервис и как с ним работать?
Веб-сервисы. Шаг 2. Как упростить написание клиента?
Deploy JAX-WS Web Services On Tomcat
JAX-WS Hello World Example – RPC Style
JAX-WS @WebService example
Creating a Simple Web Service and Client with JAX-WS
A Web Service Example: helloservice
Creating a Simple Web Service and Client with JAX-WS
Creating a Web service using the WSDL2WebService tool
Java TM API for XML Web Services
sun-jaxws.xml — When is it needed and when not?
Как написать Web Service с помощью JAX-WS
Java API for XML Web Services 2.0 Final Release

Поделиться в социальных сетях

Опубликовать в Google Plus
Опубликовать в LiveJournal
Опубликовать в Мой Мир
Опубликовать в Одноклассники
Опубликовать в Яндекс