SOAP - JAX-WS v Jakarta EE

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 inom 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/Jakarta 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ť. Keď aplikáciu na server deploynete, vyskúšajte si ho!

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 IDE 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 programá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 jakarta.jws.WebMethod;
import jakarta.jws.WebParam;
import jakarta.jws.WebService;

@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 jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Named;
import jakarta.xml.ws.WebServiceRef;

@Named
@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://localhost: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>
    


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 jakarta.jws.WebMethod;
import jakarta.jws.WebParam;
import jakarta.jws.WebService;

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

    @WebMethod(operationName = "sum")
    public int sum(@WebParam(name = "obj") Two obj) {
        return obj.getA() + obj.getB();
    }
}
    


Takto vyzerá volanie metódy webovej služby z klientskej aplikácie (v našom prípade je to obyčajný request-scoped cdi bean, pričom metóda compute() je zavesená na tlačidlo v jsf stránke, ako vidno vyššie):

    @WebServiceRef(wsdlLocation = "http://localhost: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);
    }
    


Zodpovedajúce projekty pre Intellij (oba príklady skombinované do jedného - obsahuje dve samostatné služby):
- webservice webapp:WSFactorial_a_UserObj.zip
- klientská webapp: Factorial_a_UserObj_WSApp.zip