Michał Mech – W stronę Java

Zapiski programisty

Usługi sieciowe – szybki efekt dzięki CXF

ilość komentarzy: 2

Mój nauczyciel matematyki zwykł mówić, że “dobry matematyk to leniwy matematyk”. Zwykł tak komentować karkołomne metody obliczeń stosowane w sytuacjach kiedy można coś zrobić prościej. Lubię tę zasadę stosować również w odniesieniu do programowania.

Kiedy przyszło mi niedawno stworzyć usługi sieciowe (ang. Web Services) w Javie pomyślałem o Apache CXF. CXF jest szkieletem (ang. framework) pomagającym budować usługi korzystające z API takich jak JAX-WS i zdolne do komunikacji używając protokołów SOAP, REST czy HTTP.

Kontrakt

Przy tworzeniu usług sieciowych można stosować dwa podejścia:

  1. WSDL first – najpierw tworzymy kontrakt, jakim jest plik WSDL opisujący nasze usługi a następnie przechodzimy do implementacji;
  2. Implementation first – najpierw implementujemy logikę a później wystawiamy ją w formie usług sieciowych i generujemy WSDL;

Ja osobiście lubię zabrać się do pracy kiedy wiem co mam zrobić, ot takie dziwactwo. Stąd zakładam sytuację kiedy najpierw definiujemy kontrakt jaki realizowała będzie moja usługa sieciowa.
Dla zaprezentowania CXF utworzę bardzo prostą usługę sieciową, która będzie odpowiadała na zadane pytanie. Zakładam otrzymać ciąg znaków i ciąg znaków odeślę w odpowiedzi (taka pogawędka).

Kontrakt WSDL mógłby wyglądać tak (talk.wsdl):

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
	xmlns:tns="http://michalmech.pl/service/talk/"
	xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
	xmlns:xsd="http://www.w3.org/2001/XMLSchema"
	targetNamespace="http://michalmech.pl/service/talk/" name="talk">

	<wsdl:message name="Ask">
		<wsdl:part name="question" type="xsd:string" />
	</wsdl:message>

	<wsdl:message name="AskResponse">
		<wsdl:part name="answer" type="xsd:string" />
	</wsdl:message>

	<wsdl:portType name="talk">
		<wsdl:operation name="Ask">
			<wsdl:input message="tns:Ask" />
			<wsdl:output message="tns:AskResponse" />
		</wsdl:operation>
	</wsdl:portType>

	<wsdl:binding name="talkSOAP" type="tns:talk">
		<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
		<wsdl:operation name="Ask">
			<soap:operation soapAction="http://michalmech.pl/service/talk/ask" />
			<wsdl:input>
				<soap:body use="literal" />
			</wsdl:input>
			<wsdl:output>
				<soap:body use="literal" />
			</wsdl:output>
		</wsdl:operation>
	</wsdl:binding>

	<wsdl:service name="TalkOperations">
		<wsdl:port binding="tns:talkSOAP" name="talkSOAP">
			<soap:address location="http://michalmech.pl" />
		</wsdl:port>
	</wsdl:service>
</wsdl:definitions>

Aplikacja

Teraz przyda się Maven, którym utworzymy szybko projekt aplikacji internetowej i zajmiemy się zależnościami. Sam Maven nie jest przedmiotem tego wpisu więc wspomnę tylko, że zaczynamy od polecenia:

mvn archetype:generate

Po utworzeniu projektu plik talk.wsdl umieszczamy w zasobach naszego projektu: src/main/resources/
Oto kompletny plik POM, który zawiera zależności oraz informacje o budowaniu projektu:

<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>pl.michalmech.cxfdemo</groupId>
	<artifactId>wsdl-first</artifactId>
	<version>1.0-SNAPSHOT</version>
	<packaging>war</packaging>

	<name>CXF Demo - WSDL-first</name>

	<dependencies>
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-rt-core</artifactId>
			<version>2.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-rt-frontend-jaxws</artifactId>
			<version>2.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-rt-transports-http</artifactId>
			<version>2.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-common-utilities</artifactId>
			<version>2.2</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>3.0.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>3.0.0.RELEASE</version>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.5</source>
					<target>1.5</target>
				</configuration>
			</plugin>

			<plugin>
				<groupId>org.apache.cxf</groupId>
				<artifactId>cxf-codegen-plugin</artifactId>
				<version>2.1.4</version>
				<executions>
					<execution>
						<id>generate-sources</id>
						<phase>generate-sources</phase>
						<configuration>
							<sourceRoot>${basedir}/src/main/java</sourceRoot>
							<wsdlOptions>
								<wsdlOption>
									<wsdl>src/main/resources/talk.wsdl</wsdl>
								</wsdlOption>
							</wsdlOptions>
						</configuration>
						<goals>
							<goal>wsdl2java</goal>
						</goals>
					</execution>
				</executions>
			</plugin>

			<plugin>
				<groupId>org.mortbay.jetty</groupId>
				<artifactId>maven-jetty-plugin</artifactId>
				<version>6.1.22</version>
			</plugin>
		</plugins>
	</build>
</project>

Widzimy zależności od pakietów CXF oraz pluginu CXF Codegen, który odwali za nas trochę roboty. Spring Framework dostarczy nam swój kontener IoC a serwer Jetty przyda się do testowania aplikacji.

No dobrze czas na magię. Wspominałem o leniach no to wypadałoby teraz osiągnąć coś niewielkim nakładem pracy, co niniejszym czynimy wykonując polecenia:

mvn generate-sources
mvn eclipse:clean eclipse:eclipse

Pierwsze polecenie wygeneruje nam kod języka Java na podstawie kontraktu WSDL. Drugie utworzy pliki konfiguracyjne potrzebne do importu projektu do mojego ulubionego IDE. Na obecnym etapie projekt powinien wyglądać tak:

Zajmijmy się teraz implementacją naszej usługi. Nie będzie zbyt wyszukana:

package pl.michalmech.service;

import pl.michalmech.service.talk.Talk;

public class TalkImpl implements Talk {

	public String ask(String question) {
		return "I don't know answer for \"" + question + "\" question.";
	}
}

Aby nasza usługa sieciowa działała przyda nam się zdefiniowanie kontekstu aplikacji (applicationContext.xml) oraz deskryptora rozmieszczenia (web.xml):

src/main/webapp/WEB-INF/applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<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:jee="http://www.springframework.org/schema/jee"
	xmlns:cxf="http://cxf.apache.org/core"
	xmlns:jaxws="http://cxf.apache.org/jaxws"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
		http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
		http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

	<import resource="classpath:META-INF/cxf/cxf.xml" />
	<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
	<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

	<jaxws:endpoint implementor="pl.michalmech.service.TalkImpl" address="/service/talk" />
</beans>

src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_9" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>WEB-INF/*Context.xml</param-value>
	</context-param>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<servlet>
		<servlet-name>dispatcher</servlet-name>
		<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>dispatcher</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>
</web-app>

I znów nie musimy martwić się o servlet ponieważ o wszystko zadba za nas CXF.

Uruchomienie

Teraz przyda nam się Jetty. Uruchamiamy aplikację poleceniem:

mvn jetty:run

oraz sprawdzamy rezultaty w przeglądarce. Wyniki działania aplikacji powinny być następujące:
http://localhost:8080/wsdl-first

http://localhost:8080/wsdl-first/service/talk?wsdl
To wszystko co potrzebne było do uruchomienia prostej usługi sieciowej. Przykład jest trywialny i dotyka ledwie czubka góry lodowej zarówno możliwości CXF jak i zastosowań zamych usług sieciowych więc zachęcam do zabawy.

Do pobrania: wsdl-first.zip

Opublikowany przez Michał Mech

2010-02-02 o 01:52:48

Ilość komentarzy: 2 do 'Usługi sieciowe – szybki efekt dzięki CXF'

Śledź komentarze poprzez RSS lub wyślij sygnał do 'Usługi sieciowe – szybki efekt dzięki CXF'.

  1. Działa chyba ok :)

    Marcin Chyłek

    2010-02-03 o 12:44:24

  2. Strasznie dużo papierkowej roboty macie tam w tej Javie :)

    Karim Agha

    2010-02-04 o 03:35:51

Skomentuj wpis