O JAX-WS

teoretický úvod: Kapitolka z knihy Service-Oriented Architecture: A Field Guide to Integrating XML and Web Services

Keďže interoperabilita aplikácií je v dnešnej dobe veľmi dôležitá - aplikácie je potrebné navzájom prepájať do veľkých integračných riešení, potrebujeme vhodné technológie, aby si viaceré aplikácie - ktoré môžu potenciálne bežať aj na rozličných miestach na Internete mohli vymieňať údaje a nielen to - navzájom poskytovať rozličné služby - priamo volaním metód na diaľku (remote procedure call).

V predchádzajúcom príklade sme spoznali technológiu JMS, ktorá je vhodná na posielanie správ medzi rôznymi JAVA aplikáciami prostredníctvom schránok alebo frontov vytvorených na aplikačnom serveri. Technológia JAX-WS, o ktorej je táto stránka, poskytuje ešte všeobecnejší prostriedok - pomocou ktorého môžu komunikovať aplikácie napísané v rozličných programovacích jazykoch, bežiace na rôznych miestach na Internete a hlavne - nevymieňajú si len dáta ako v prípade JMS, ale priamo môžu volať svoje procedúry. JAX-WS je založená na štandarde SOAP (Simple Object Access Protocol) a funguje to v princípe takto:

Strana, ktorá poskytuje nejakú službu (web service), obsahuje bežnú javovskú triedu, ktorá je dekorovaná anotáciou @WebService. Jej public metódy (ktoré je vhodné dekorovať anotáciou @WebMethod) sú automaticky prístupné body pre túto službu pre všetkých klientov, ktorí chcú službu využiť. Trieda implementujúca web service sa môže pribaliť do ľubovoľnej java EE aplikácie - napríklad do prázdnej webovej aplikácie, ktorá neobsahuje nič iné. Podstatné je, že sa k nej automaticky vytvorí XML-kový súbor WSDL (Web Services Definition Language), ktorý túto službu (spolu so schémou tohto XML) popisuje. Keď aplikáciu obsahujúcu web service deployneme na server, tento súbor sa automaticky sprístupní na príslušnej URL (vieme si ju zistiť napr. z adminovského rozhrania Glassfish - v zozname aplikácií po kliknutí na danú aplikáciu cez "View Endpoint" ). Okrem toho nám Glassfish sprístupní aj tester, v ktorom môžeme službu priamo cez prehliadač otestovať.

Autor klientskej aplikácie, ktorá bude danú službu používať, potrebuje iba URL na ktorom je daný WSDL zavesený. Z neho si automaticky (cez wizzard v Netbeans alebo pomocou programu wsimport, ktorý je súčasťou Glassfish) vygeneruje triedy obsahujúce hlavičky metód, ktoré služba poskytuje. V programe túto službu zaradí cez anotáciu @WebServiceRef a získa objekt implementujúci komunikáciu s príslušnou službou a z neho vytiahne referenciu na objekt s metódami, ktoré služba poskytuje. Tieto metódy volá celkom bežne ako metódy akéhokoľvek iného javového objektu, ale interne, v pozadí, bez toho, aby sa o to progrmátor klientskej aplikácie staral, sa najskôr dáta odovzdávané volanej metóde cez technológiu JAXB serializujú do XML štruktúry vo formáte SOAP, tá sa prenesie na server cez bežný HTTP protokol, automaticky prevedie naspäť do natívneho binárneho formátu a zavolá potrebnú metódu poskytnutej služby. Hodnota, ktorú metóda vráti, sa rovnakým procesom odošle naspäť a do klientskej aplikácie sa vráti ako bežná javovská hodnota.

Keďže technológia JAXB nepodporuje ľubovoľné typy, tak v argumentoch a návratových hodnotách metód sa musíme obmedziť len na podporované typy. Triedy, ktoré si dodefinujeme môžu byť použité tiež. Ide o základné typy boolean, byte, byte[], double, float, int, long, short a javovské triedy java.awt.Image, java.lang.Object, java.lang.String, java.math.BigDecimal, java.math.BigInteger, java.net.URI, java.util.Calendar, java.util.Date, java.util.UUID, javax.activation.DataHandler, javax.xml.datatype.Duration, javax.xml.datatype.XMLGregorianCalendar, javax.xml.namespace.QName, javax.xml.transform.Source.

V tomto príklade impliementujeme jednoduchú službu, ktorá počíta faktoriál n. Bude to trieda Faktorial s metódou public long faktorial(int n). Celý program vyzerá takto:
package factorial;

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

@WebService(serviceName = "Faktorial")
public class Faktorial {

    @WebMethod(operationName = "faktorial")
    public long faktorial(@WebParam(name = "n") int n) {
        long result = 1;
        while (n > 1) result *= n--;
        return result;
    }
}
V klientskej aplikácii si k nej cez wizzard (Web Services - Web Service Client) prípadne program wsimport automaticky vygenerujeme triedu Faktorial_Service, ktorej metóda getFaktorialPort() nám vráti referenciu na analogickú triedu Faktorial s rovnakou metódou. Výsledný program klientskej aplikácie potom vyzerá napríklad takto:
package faktapp;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.xml.ws.WebServiceRef;

@ManagedBean
@RequestScoped
public class FaktBean {

    public FaktBean() { }
    
    int n;
    long result;
    
    public int getN() { return n; }
    public void setN(int newN) { n = newN; }
    
    public long getResult() { return result; }
    public void setResult(long newResult) { result = newResult; }
    
    @WebServiceRef(wsdlLocation = "http://kempelen.ii.fmph.uniba.sk:8080/FaktorialService/Faktorial?wsdl")
    private Faktorial_Service faktorialService;
    
    public void compute() 
    {
        Faktorial faktorial = faktorialService.getFaktorialPort();
        
        result = faktorial.faktorial(n);
    }
}
a tento RequestScoped bean využijeme v JSF webstránke napríklad takto:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html">
    <h:head>
        <title>Faktorial Service Client App</title>
    </h:head>
    <h:body>
        Hello from Faktorial Service Client App
        <h:form>
            Enter N=<h:inputText value="#{faktBean.n}"/> 
            <h:commandButton label="xx" value="N!" action="#{faktBean.compute()}"/>
            Result=<h:outputText value="#{faktBean.result}"/>            
        </h:form>    
    </h:body>
</html>
Program sa dá vyskúšať tu: http://kempelen.ii.fmph.uniba.sk:8080/FaktorialApp/

V prípade, že potrebujeme cez argument metódy, ktorú služba poskytuje, odoslať veľa dát v zložitom používateľom definovanom type - za podmienky, že ten typ obsahuje iba typy podporované JAXB, tak môžeme. V type pre poriadok uveďme aj gettery/settery pre jednotlivé položky, napríklad:
package userobj;

public class Two 
{
        private int a;
        private int b;
        
        public int getA() { return a; }
        public void setA(int newA) { a = newA; }
        
        public int getB() { return b; }
        public void setB(int newB) { b = newB; }
}
a zodpovedajúca služba:
package userobj;

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

@WebService(serviceName = "UserObj")
public class UserObj {

    @WebMethod(operationName = "sum")
    public int sum(@WebParam(name = "obj") Two obj) {
        return obj.getA() + obj.getB();
    }
}
V klientskej aplikácii nám Netbeans pri automatickom vytvorení klienta z WSDL vytvorí aj triedu Two. So zodpovedajúcimi poliami, gettermi a settermi. Pri každom Clean&Build klientskej aplikácie sa zdrojový kód vygeneruje nanovo, ale len z lokálnej kópie. Preto, ak urobíme nejaké zmeny v rozhraní webservisovej triedy, alebo odkazovaných komplexných typov, musíme si lokálnu kópiu WSDL v NetBeans znovu aktualizovať - a to cez pravý klik na príslušný webservice a položku Refresh... Nájdeme ho v project exploreri v položke "Web Service References"

Takto vyzerá volanie metódy webovej služby z klientskej aplikácie (v našom prípade je to obyčajný request-scoped jsf bean, pričom metóda compute() je zavesená na tlačidlo v jsf stránke, ako vidno vyššie):
    @WebServiceRef(wsdlLocation = "http://kempelen.ii.fmph.uniba.sk:8080/WSwithUserObj/UserObj?wsdl")
    private UserObj_Service uoService;
    
    public void compute() 
    {
        UserObj uo = uoService.getUserObjPort();
        Two t = new Two();
        t.setA(a);
        t.setB(b);
        result = uo.sum(t);
    }
Výsledný program sa dá vyskúšať tu: UserObjWSApp.

Zodpovedajúce projekty pre Netbeans:
- webservice webapp:WSwithUserObj.zip
- klientská webapp: UserObjWSApp.zip