Beliebte Suchanfragen
|
//

Einfache und Schnelle Webservices mit Mule ESB und Apache CXF

16.3.2010 | 5 Minuten Lesezeit

Ich möchte Euch in diesem Beitrag zeigen, wie wir bei codecentric in unseren Projekten den Mule ESB und Apache CXF einsetzen. Ich möchte zeigen wie einfach das Erstellen von Webservices ist und was man tun kann um das doch recht langsame Standardverhalten zu verbessern.

Sollte man überhaupt einen Webservice einsetzen? Nun eine gute Frage, und wahrscheinlich sogar am relevantesten für die Performance. Webservices eignen sich hervorragend um Interfaces und Services nach außen anzubieten, oder wenn man intern auf Grund von Firewallbeschränkungen oder anderen Programmiersprachen andere Protokolle wie RMI nicht verwenden kann. Da Ihr euch mit Webservices herumschlagt, gehe ich einfach mal davon aus daß dieser Punkt nicht zur Debatte steht.

Wir benutzen den Mule Enterprise Service Bus in einigen Projekten, aber damit möchte ich nicht sagen, daß er für alle geeignet ist. Dokumentation gibt es nur nach Anmeldung und die Releasepolitik ist durchaus fragwürdig. Ich bin nicht sonderlich glücklich damit, aber er funktioniert doch ganz gut sobald mal ihn einigermaßen begriffen hat und an einigen Stellen nachgebessert hat. Um Webservices zu erzeugen kann man diese entweder von Hand schreiben, oder Apache Axis oder Apache CXF verwenden. Ich bevorzuge CXF, da ich dessen API eleganter empfinde, und der generierte Code sauberer ist. Außerdem ist das Projekt noch lebendig und wird sogar von Mule Entwicklern betreut. CXF ist Standard bei Mule und wird von uns in Verbindung mit Spring Pojo Components angewendet.

Nun schauen wir uns mal unser Beispiel Webservice Interface an. Um die Sache etwas interessanter zu gestalten nutzen wir einen nicht trivialen Service der Domänenobjekte als Parameter erwartet und zurückliefert. Ein Wort der Warnung vorweg: Große Objektbäume erzeugen viel XML 🙂

1@WebService
2public interface RecommendationService {
3    @WebMethod
4    public Products recommendProducts(
5        @WebParam(name="user")
6        User user,
7        @WebParam(name="genre")
8        Genre genre
9    );
10}

Natürlich gibt es auch eine Implementierung für diesen Service, welcher im nächsten Schritt in Mule eingebunden wird.
Zu aller erst müssen wir Mule dazu bewegen überhaupt Webservices zu akzeptieren. Da wir Mule in der Regel im WAR deployen benutzen wir den Servlet Connector, allerdings kann auch der Jetty Connector beim eigenständigen Einsatz genutzt werden:

1<servlet:connector name="servletConnector" 
2                   servletUrl="http://localhost:8080/mule/services/recommendation?wsdl" />

Als Nächstes folgt die Webservicekonfiguration des Services:

1<model name="recommendationServiceModel">
2    <service name="recommendation">
3        <inbound>
4            <cxf:inbound-endpoint address="servlet://recommendation" synchronous="true" />
5        </inbound>
6        <component>
7            <spring-object bean="RecommendationService" />
8        </component>
9    </service>
10</model>

Und natürlich auch der Service selbst:

1<spring:bean id="RecommendationService"
2             class="de.codecentric.RecommendationServiceImpl" />

Man kann die Konfigurationen in eine Datei quetschen, allerdings empfehle ich diese aufzuteilen. Entweder nach Typ (service, component, mule) oder eine Konfiguration pro Service.
Das war es auch schon mit der gesamten Mule Konfiguration.
Nun könnten wir den Service schon testen, wenn wir einen einfachen Weg hätten die Parameter zu übergeben. Bis dahin begnügen wir uns mit dem Aufruf der WSDL.

1http://localhost:8080/mule/services/recommendation?wsdl

Beachtet auch eventuelle s welche auf einen ausgelagerten Teil der WSDL verweisen.

Einen Java Client für den Zugriff auf den Service zu erstellen ist ganz einfach mit Hilfe des wsdl2java Kommandos von CXF:

1wsdl2java -client -d src/main/java -p de.codecentric.client 
2  http://localhost:8080/mule/services/recommendation?wsdl

Wären wir nun ein externer Benutzer, so könnten und müssten wir mit den generierten Klassen arbeiten. Allerdings möchten wir insbesondere bei inkrementeller Entwicklung und den damit verbundenen häufigeren Änderungen an den Domänenobjekten diese nicht immer neu generieren. Außerdem bieten Domänenobjekte sinnvolle Businessmethoden welche nicht generiert werden, da sie nicht Teil des Webservices sind. Da CXF echt clever ist, können wir einfach folgende generierten Klassen löschen:

  • Genre
  • ObjectFactory
  • package-info
  • Products
  • User

Nun lassen sich die fehlenden Imports einfach durch die Domänenobjekte ersetzen. Außerdem muss die Referenz auf @XmlSeeAlso({ObjectFactory.class}) entfernt werden.

Danach sollten nur noch Interface und Implementierung des Services, sowie zwei Wrapper Objekte für Request und Response übrig bleiben. Führt man nun den Dummy Client mit CXF auf dem Classpath aus, so sollte der Webservice aufgerufen werden.

Intern löst der Aufruf von

1RecommendationServiceImplService ss = new RecommendationServiceImplService(wsdlURL, SERVICE_NAME);
2RecommendationService port = ss.getRecommendationServiceImplPort();

die Erstellung eines dynamischen Proxys mittels Reflection von der WSDL des Servers aus.

Man könnte jetzt aufhören. Wir haben erfolgreich einen Client erstellt der Domänenobjekte verwendet. Aber die Performance ist echt nicht gut.

Die WSDL wird vom Server bezogen und in eine Proxy Klasse übersetzt. Natürlich könnte man die WSDL importieren, doch dies müsste jedes mal gemacht werden wenn sich die Objekte verändern. Externe Benutzer müssen das tun, sind aber seltener Änderungen unterworfen. Trotzdem werden bei jedem Aufruf weiterhin Proxy Klassen generiert. Und das ist noch langsam genug. Ich habe die Laufzeit des gesamten Stacks gemessen und die Proxy Erzeugung übertrifft den Rest um Längen.

Was also tun? Um die Situation zu verbessern benutzen wir einen pool, mit Hilfe von commons pool GenericObjectPool.

1private final GenericObjectPool recommendationServicePool;
2 
3RecommendationServiceFactory recommendationServiceFactory = new RecommendationServiceFactory();
4recommendationServicePool = new GenericObjectPool(recommendationServiceFactory, new Config());

Der Pool benötigt also eine Factory um Instanzen zu erzeugen und eine Konfiguration. Die Konfiguration kann noch angepasst werden, aber die Defaults sind ausreichend gut. Die Implementierung der Factory sieht so aus:

1public class RecommendationServiceFactory implements PoolableObjectFactory  {
2public Object makeObject() throws Exception {
3  RecommendationServiceImplService service = new RecommendationServiceImplService();
4  RecommendationService port = service.getRecommendationServiceImplPort();
5  return port;
6}
7public boolean validateObject(Object arg0) {
8  // consider all controllers valid objects
9  return true;
10}
11public void destroyObject(Object arg0) throws Exception {}
12public void activateObject(Object arg0) throws Exception {}
13public void passivateObject(Object arg0) throws Exception {}
14}

Nun können wir unseren Service wie folgt aufrufen:

1RecommendationService port = (RecommendationService) recommendationServicePool.borrowObject();
2try {
3  Products products = port.recommendProducts(user, genre);
4} finally {
5  recommendationServicePool.returnObject(port);
6}

Auf keinen Fall sollte man vergessen geliehene Objekte wieder in den Pool zurückzugeben. Anders als in einer Bücherei bekommt man keine Mahnung 🙂

Zusammenfassung
Wir benutzen Mule ESB um Spring Komponenten basierte Webservices, welche Domänenobjekte verwenden, zu konfigurieren und zu deployen. Die Webservices werden mit Hilfe von Apace CXF bereitgestellt und in einem damit generiertem Client angesprochen. Durch die Benutzung eines Pools verhindern wir die unnötige Erzeugung von Proxyklassen.

Ich erwähnte bereits, daß der Pool eine erhebliche Verbesserung brachte. Dennoch sollte man nie einem Blogbeitrag trauen und immer selber messen. Dies ist sogar ganz einfach möglich indem man die Zeit des Aufrufs ohne Pool und des mehrfachen Aufrufs mit pool misst. Der erste Aufruf im Pool ist auch langsam, da das Objekt erzeugt wird. Auf meiner Maschine hat der ganze Stack 360ms benötigt. Alle folgenden Aufrufe benötigten 4-6ms. Das ist fast eine Verbesserung um den Faktor 100. Für die komplexen Aufgaben die der ESB ausführt sind 4ms sogar recht schnell. Muss doch ein HTTP request gemacht und innerhalb eines WARs im JBoss empfangen werden, die Daten ein und wieder ausgepackt werden, herausgefunden werden welcher Service mit welcher Methode aufgerufen werden soll etc.

Auf jeden Fall sollte man nicht zu vorschnell Webservices verurteilen. Mit den richtigen Hilfsmittel kann man sie relativ gut benutzen, und sie können auch recht schnell sein.

|

Beitrag teilen

//

Weitere Artikel in diesem Themenbereich

Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.