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