Witam po długiej przerwie. W ostatnich miesiącach zmieniłem pracę i w zabieganiu nie miałem czasu na blogowanie. Dziś zajmę się zaprezentowaniem jak postawić soap-a korzystając z cxf-a i oczywiście spring-a. Projekt ze źrodłami można pobrać stąd.Do poczytania czym jest SOAP odsyłam do wiki oraz dokumentacji JAX-WS.
Zacznijmy od części serwerowej. Tworzenie web serwisu można zacząć albo od napisania WSDL-a (contract first) albo od napisania interfejsu serwisu i wygenerowaniu WSDL-a (contract last). Skorzystam z contract last. Mój testowy interfejs wygląda następująco:
public interface HelloSOAP {
String sayHello(String name);
}
Zacznijmy zabawę z SOAP-em :-). W pierwszym kroku oznaczmy adnotacją nasz interfejs:
@WebService(targetNamespace = "spring.turo.pl") public interface HelloSOAPWszystkie atrybuty adnotacji @WebService są opcjonalne. Służą do oznaczenia endpoint-a web serwisu. Cóż przez to rozumiem ?
Oznaczamy tą adnotacją interfejs (bądź klasę) która deklaruje metody, które klient web serwisu będzie mógł wywołać. Innymi słowy - metody z tego interfejsu/klasy będą dostępne do wywołania. Moglibyśmy nic więcej w naszym interfejsie nie konfirugować - wtedy każda metoda byłaby zmapowana na operację serwisową o nazwie zgodnej z nazwą metody i parametrami arg0, arg1, ..., argN w WSDL-u. Nie chcę takiej sytuacji - wolę to przekonfigurować, spójrzmy jak:
@WebService(targetNamespace = "spring.turo.pl")
public interface HelloSOAP {
@WebMethod(operationName = "przywitajSie")
String sayHello(@WebParam(name = "firstName") String name);
}
Adnotacja @WebMethod pozawala na określenie metody jako metody web-serwisowej (możliwej do wywołania przez klienta) i pozwala m.in. na zmianę nazwy operacji w WSDL-u. Z powyższej konfiguracji wynika że operacja będzie dostępna pod nazwą 'przywitajSie' (a nie pod nazwą metody 'sayHello').Dodatkowo użyłem adnotacji @WebParam która pozwala na opisanie jak w WSDL-u będzie nazywał się parametr operacji (uniknę dzięki temu arg0, ...).
OK. Mamy interfejs - czas na jakąś implementację:
@WebService(endpointInterface = "pl.turo.spring.HelloSOAP")
public class HelloSOAPImpl implements HelloSOAP {
public HelloSOAPImpl() {
}
@Override
public String sayHello(@WebParam(name = "firstName") String name) {
return format("hello {0} :-)", name);
}
i czas na springa:<bean class="pl.turo.spring.HelloSOAPImpl" id="helloSOAP" />Zauważmy 2 rzeczy:
- Klasę też oznaczyłem adnotacją @WebService i podałem w niej interfejs po którym wystawiona będzie usługa.
- Serwis jest 'czystym' beanem springa.
- Mieć na classpath odpowiednie jar'y (odsyłam do dokumentacji albo do mojego przykładowego projektu).
- Przeedytować konfigurację springa:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.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-servlet.xml"/>
<jaxws:endpoint
id="endpoint"
implementor="#helloSOAP"
address="/helloSOAP" />
<bean class="pl.turo.spring.HelloSOAPImpl" id="helloSOAP" init-method="initPersons"/>
</beans>
Dodaliśmy import dwóch konfirugacji springa z cxf-a oraz skonfigurowaliśmy endpoint. Tutaj zauważmy, że:- Atrybut id nie ma żadnego znaczenia.
- Implementor to nasz bean. Wskazujemy go z '#' ponieważ cxf wymaga rzeczywistej referencji do obiektu pl.turo.spring.HelloSOAPImpl a nie proxy (spring może tworzyć proxy na beanie aplikując AOP, security, transakcje itd). Na obiekcie proxy nie ma adnotacji ktróre są potrzebne aby uruchomić usługę (adnotacja @WebService). Zatem nie możemy wstrzykiwać proxy, dzięki '#' mamy pewność, że nawet jeśli spring takie proxy na beanie helloSOAP będzie tworzył to jako implementacja naszego endpoint-u będziemy mieli rzeczywisty obiekt.
- Podaliśmy konfigurację pod jaką ścieżką (
address="/helloSOAP") chcemy wystawić naszą usługę.
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-java2ws-plugin</artifactId>
<version>${cxf.version}</version>
<dependencies>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-simple</artifactId>
<version>${cxf.version}</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>process-classes</id>
<phase>process-classes</phase>
<configuration>
<className>pl.turo.spring.HelloSOAP</className>
<genWsdl>true</genWsdl>
<verbose>true</verbose>
<attachWsdl>true</attachWsdl>
</configuration>
<goals>
<goal>java2ws</goal>
</goals>
</execution>
</executions>
</plugin>
Przy takiej konfiguracji mój wsdl pojawi się w katalogu target/generated/wsdl. Pozostaje jeszcze tylko skonfigurować web.xml'a:Mamy już skonfigurowaną usługę, czas na klienta.org.springframework.web.context.ContextLoaderListener contextConfigLocation /WEB-INF/spring/root-context.xml CXFServlet org.apache.cxf.transport.servlet.CXFServlet 1 CXFServlet /*
Aby móc wywołać metody serwisowe potrzebuję wygenerować sobie stub-y z wdsl-a. W tym celu ponownie posłużę się pluginem mavena dostarczonym przez cxf-a:
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${cxf.version}</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${project.build.directory}/generated-sources/java</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${project.basedir}/src/main/resources/HelloSOAP.wsdl</wsdl>
<serviceName>HelloSOAPService</serviceName>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
Ta konfiguracja mówi, że chcę wygenerować stub-y z podanego pliku wsdl (to jest ten, który chwilę wcześniej wygenerowałem po stronie serwerowej) dla usługi nazwanej HelloSOAPService (taką nazwę na usługa w wsdl-u). Wystarczy przebudować projekt (bądź tylko wygenerować źródła poleceniem mvn generate-sources) i plugin wygeneruje potrzebne nam klasy.Następnie należy dołożyć kawałek konfiguracji springa:
<jaxws:client id="helloClient"
serviceClass="pl.turo.spring.HelloSOAP"
address="http://localhost:8080/helloSOAP"/>
- Konfigurujemy beana będącego klientem naszego serwisu.
- Identyfikator beana służy jedynie do wyciągnięcia go z kontekstu springa.
- serviceClass składa się z 2 części : pl.turo.spring (odnosi się do wartości jaką podaliśmy w adnotacji @WebService(targetNamespace = "spring.turo.pl")) oraz HelloSOAP - nazwa naszego interfejsu serwisu.
- address - adres sieciowy pod jakim usługa będzie dostępna. Jak widać jest to adres który wcześniej skonfigurowaliśmy po stronie serwerowej (
address="/helloSOAP") .
Mamy już część serwerową oraz część kliencką : czas na test. Najpierw uruchamiam usługę na tomcat-cie z poziomu mojego IDE:
2011-05-22 09:17:35 org.apache.catalina.core.AprLifecycleListener init INFO: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: /usr/lib/jvm/jdk1.6.0_25/jre/lib/i386/server:/usr/lib/jvm/jdk1.6.0_25/jre/lib/i386:/usr/lib/jvm/jdk1.6.0_25/jre/../lib/i386:.::/usr/java/packages/lib/i386:/lib:/usr/lib 2011-05-22 09:17:35 org.apache.coyote.http11.Http11Protocol init INFO: Initializing Coyote HTTP/1.1 on http-8080 2011-05-22 09:17:35 org.apache.catalina.startup.Catalina load INFO: Initialization processed in 398 ms 2011-05-22 09:17:35 org.apache.catalina.core.StandardService start INFO: Starting service Catalina 2011-05-22 09:17:35 org.apache.catalina.core.StandardEngine start INFO: Starting Servlet Engine: Apache Tomcat/6.0.32 2011-05-22 09:17:35 org.apache.catalina.startup.HostConfig deployDescriptor INFO: Deploying configuration descriptor ROOT.xml INFO : org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization started INFO : org.springframework.web.context.support.XmlWebApplicationContext - Refreshing Root WebApplicationContext: startup date [Sun May 22 09:17:36 CEST 2011]; root of context hierarchy INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from ServletContext resource [/WEB-INF/spring/root-context.xml] INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [META-INF/cxf/cxf.xml] INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [META-INF/cxf/cxf-servlet.xml] INFO : org.springframework.beans.factory.support.DefaultListableBeanFactory - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@9fe84e: defining beans [cxf,org.apache.cxf.bus.spring.BusWiringBeanFactoryPostProcessor,org.apache.cxf.bus.spring.Jsr250BeanPostProcessor,org.apache.cxf.bus.spring.BusExtensionPostProcessor,helloSOAP,endpoint]; root of factory hierarchy INFO : org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization completed in 1079 ms 2011-05-22 09:17:37 org.apache.catalina.startup.HostConfig deployDirectory INFO: Deploying web application directory docs 2011-05-22 09:17:37 org.apache.catalina.startup.HostConfig deployDirectory INFO: Deploying web application directory examples 2011-05-22 09:17:37 org.apache.catalina.startup.HostConfig deployDirectory INFO: Deploying web application directory manager 2011-05-22 09:17:37 org.apache.catalina.startup.HostConfig deployDirectory INFO: Deploying web application directory host-manager 2011-05-22 09:17:37 org.apache.coyote.http11.Http11Protocol start INFO: Starting Coyote HTTP/1.1 on http-8080 2011-05-22 09:17:37 org.apache.jk.common.ChannelSocket init INFO: JK: ajp13 listening on /0.0.0.0:8009 2011-05-22 09:17:37 org.apache.jk.server.JkMain start INFO: Jk running ID=0 time=0/13 config=null 2011-05-22 09:17:37 org.apache.catalina.startup.Catalina start INFO: Server startup in 1724 ms Connected to server
Z logów widać, że wstała aplikacja springowa ładując 3 konfiguracje /WEB-INF/spring/root-context.xml, META-INF/cxf/cxf.xml oraz META-INF/cxf/cxf-servlet.xml.
Przetestujmy naszego klienta:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-config.xml");
HelloWS soap = (HelloWS) context.getBean("helloClient");
// przesyłamy stringa
System.out.println(soap.przywitajSie("Sławek"));
}
Tworzę fabrykę springa, pobieram skonfigurowanego wyżej beana i na nim mogę uruchamiać metody serwisowe.Zauważmy, że w interfejsie HelloSOAP (po stronie serwera) mamy deklarację metody:
@WebMethod(operationName = "przywitajSie") String sayHello(@WebParam(name = "firstName") String name);Interfejs na kliencie posiada metodę 'przywitajSie' a nie 'sayHello'.
Na mojej konsoli widzę wynik uruchomienie metody main:
INFO : org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@89cf1e: startup date [Sun May 22 09:34:53 CEST 2011]; root of context hierarchy INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [spring-config.xml] INFO : org.springframework.beans.factory.support.DefaultListableBeanFactory - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@113beb5: defining beans [helloClient.proxyFactory,helloClient]; root of factory hierarchy hello Sławek :-)
Serwis poprawnie odpowiedzieł.
Przesłaliśmy sobie stringa, a co z bardziej skomplikowanymi obiektami ?
Dołóżmy do interfejsu metody :
@WebService(targetNamespace = "spring.turo.pl")
public interface HelloSOAP {
/**
* Metoda testowa.
* @param name
* @return hello string :-)
*/
@WebMethod(operationName = "przywitajSie")
String sayHello(@WebParam(name = "firstName") String name);
/**
* Znajduje osobę po imieniu.
* @param firstName imię
* @return znaleziona osoba
*/
@WebMethod(operationName = "locatePerson")
Child getPersonFromName (@WebParam(name = "firstName") String firstName);
/**
* Znajduje ojca dla dziecka.
* @param child dziecko
* @return ojciec
*/
@WebMethod(operationName = "findParent")
Parent findParent(@WebParam(name = "child") Child child);
}
Do tego klasy modelu :public class Child {
private String firstName;
private String lastName;
private Long age;
// konstuktory, akcesory
}
public class Parent {
private String firstName;
private String lastName;
private Long age;
private Collection<Child> children;
// konstuktory, akcesory
}
Oraz naszą mokową implementację:@WebService(endpointInterface = "pl.turo.spring.HelloSOAP")
public class HelloSOAPImpl implements HelloSOAP {
private Set<Child> persons = new HashSet<Child>();
private Set<Parent> parents = new HashSet<Parent>();
public HelloSOAPImpl() {
}
public void initPersons () {
persons.add(new Child("ala", "mikołajowicz", 32L));
persons.add(new Child("janko", "muzykant", 15L));
parents.add(new Parent("michał", "michołajowicz", 50L,
new ArrayList<Child>(Arrays.asList(
new Child("jacek", "michołajowicz", 15L),
new Child("zygmunt", "michołajowicz", 12L)))
));
parents.add(new Parent("zyta", "grochowska", 43L,
new ArrayList<Child>(Arrays.asList(
new Child("benjamin", "grochowski", 5L),
new Child("maciej", "grochowski", 7L)
))));
}
@Override
public String sayHello(@WebParam(name = "firstName") String name) {
return format("hello {0} :-)", name);
}
@Override
public Child getPersonFromName(String firstName) {
if (firstName == null) {
throw new UnsupportedOperationException("imię musi zostać podane!!!");
}
Child retPerson = null;
for (Iterator<Child> iterator = persons.iterator(); iterator.hasNext();) {
Child next = iterator.next();
if (firstName.equalsIgnoreCase(next.getFirstName())) {
retPerson = next;
break;
}
}
return retPerson;
}
@Override
public Parent findParent(Child child) {
if (child == null) {
throw new UnsupportedOperationException("dziecko musi zostać podane!!!");
}
Parent retParent = null;
for (Iterator<Parent> iterator = parents.iterator(); iterator.hasNext();) {
Parent next = iterator.next();
if (next.getChildren().contains(child)) {
retParent = next;
break;
}
}
return retParent;
}
}
Poprawmy konfigurację springową :
<bean class="pl.turo.spring.HelloSOAPImpl" id="helloSOAP" init-method="initPersons"/>
Mój mokowy bean w metodzie initPersons (oznaczonej jako init-method) wypełnia sobie dane testowe.
Jeszcze raz należy przebudować projekt. Następnie ponownie skopiować wygenerowany wsdl do źródeł projektu klienckiego.
Prześlijmy obiekty :
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("/spring-config.xml");
HelloSOAP soap = (HelloSOAP) context.getBean("helloClient");
// przesyłamy obiekty
Child child = new Child();
child.setAge(5L);
child.setFirstName("benjamin");
child.setLastName("grochowski");
Parent parent = soap.findParent(child);
System.out.println(format("parent = {0} {1} lat {2}",
parent.getFirstName(), parent.getLastName(), parent.getAge()));
Child ala = soap.locatePerson("ala");
System.out.println(format("person = {0}", ala.getFirstName()));
}
I wynika na konsoli:INFO : org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@10a2d64: startup date [Sun May 22 09:50:09 CEST 2011]; root of context hierarchy INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [spring-config.xml] INFO : org.springframework.beans.factory.support.DefaultListableBeanFactory - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1d95da8: defining beans [helloClient.proxyFactory,helloClient]; root of factory hierarchy parent = zyta grochowska lat 43 person = ala
OK. Widać, że nieco bardziej złożone obiekty udało mi się przesłać. Po drodze zostały one zserializowane na xml-a (korzystając z jaxb) i na kliencie zdeserializowane.
Jak widać nie musiałem robić nic aby skorzystać z tej serializacji.
Na koniec link do dokumentacji z listą typów wspieranych przez JAX-WS:
http://download.oracle.com/javaee/6/tutorial/doc/bnazc.html
Brak komentarzy:
Prześlij komentarz