Gestern habe ich meine Web-Services noch erfolgreich zum Laufen gebracht. Allerdings bin ich dabei über einen ziemlicher Stolperstein öh… gestolpert. Kurz zu meiner Implementierung:

  • 3 Services (AgentService, ArtifactService, ProjectService), die per @WebService annotiert sind. Die Services greifen auf die jeweiligen BOs (AgentBO, ArtifactBO, ProjectBO) zu
  • Jeder Service besitzt die beiden Methoden findById(int _id) und findAll(). Die Rückgabewerte unterscheiden sich aber (AgentVO / AgentVO[], ArtifactVO / ArtifactVO[], ProjectVO / ProjectVO[])
  • Die Services sollen unter der URL /soap/projectService, /soap/artifactService und /soap/agentService erreichbar sein

Meine applicationContext.xml sah folgendermaßen aus

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ws="http://jax-ws.dev.java.net/spring/core"
xmlns:wss="http://jax-ws.dev.java.net/spring/servlet"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://jax-ws.dev.java.net/spring/core http://jax-ws.dev.java.net/spring/core.xsd
http://jax-ws.dev.java.net/spring/servlet http://jax-ws.dev.java.net/spring/servlet.xsd">
  <ws:service id="projectService" impl="de.ecw.deployment.server.services.ProjectService" />
  <ws:service id="agentService" impl="de.ecw.deployment.server.services.AgentService" />
  <ws:service id="artifactService" impl="de.ecw.deployment.server.services.ArtifactService" />
  <wss:bindings id="jax-ws.http">
    <wss:bindings>
      <wss:binding url="/soap/projectService" service="#projectService" />
      <wss:binding url="/soap/agentService" service="#agentService" />
      <wss:binding url="/soap/artifactService" service="#artifactService" />
    </wss:bindings>
  </wss:bindings>
</beans>

Kurze Erklärung dazu:

  • ws:service definiert einen zu veröffentlichenden Webservice. Im Attribut id wird eine Id (man glaubt es kaum) vergeben, über den sich in der XML-Datei und in Spring referenzieren lässt. impl legt den Namen der Klasse fest. Wichtig ist dabei, dass die Klasse einen Default-Konstruktur besitzt.
  • wss:bindings ist der Container für die einzelnen Service-Bindings. Über id=”jax-ws.http” wird festgelegt, dass als Protokoll HTTP benutzt werden soll.
  • wss:binding spezifiert nun ein näheres Binding. url definiert die URL, unter der der Service erreichbar ist (siehe Anforderungen), service verweist mit einem Anker auf unser jeweiliges ws:service-Tag

Die web.xml sah so aus:

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
  <display-name>DeployIt</display-name>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
<!-- Noch mehr Servlets, die nicht interessieren müssen -->
  <servlet>
    <servlet-name>jaxws-servlet</servlet-name>
    <servlet-class> com.sun.xml.ws.transport.http.servlet.WSSpringServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>jaxws-servlet</servlet-name>
    <url-pattern>/soap/*</url-pattern>
  </servlet-mapping>
</web-app>

Auch hier eine kurze Erklärung:

  • Über context-param > contextConfigLocation wird definiert, dass Spring diese XML-Datei für Injections und Konfiguration benutzen soll
  • listener -> listener-class definert, dass der ContextLoaderListener von Spring geladen wird, der wiederum über die Eigenschaft contextConfigLocation die Einstellungen lädt
  • servlet definiert das SOAP-Dispatcher-Servlet – WSSpringServlet ist dafür als DefaultAdapter vorgesehen
  • Mit servlet-mapping wird schließlich festgelegt, dass alle Anfragen die auf die URL /soap/* gehen, an das Servlet jaxws-servlet delegiert werden

Soweit, so gut – ich testete nun mit Hilfe des Tools soapUI, das übrigens sehr zu empfehlen ist, die Webservices.
Wer nun denkt “Das sieht alles gut aus, das funktioniert doch ganz bestimmt!”, denkt falsch. Überraschenderweise führte obige Kombination dazu, dass ich zwar projectService:findAll() aufrufen konnte, aber die beiden anderen Services mit einer Exception ihren Dienst quittierten:

com.sun.xml.ws.transport.http.servlet.WSServletDelegate <init>
INFO: WSSERVLET14: JAX-WS servlet initializing
com.sun.xml.ws.server.sei.EndpointMethodHandler invoke
SCHWERWIEGEND: null
java.lang.IllegalArgumentException
    at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:63)
    at java.lang.reflect.Field.set(Field.java:656)
    at com.sun.xml.bind.v2.runtime.reflect.Accessor$FieldReflection.set(Accessor.java:242)
    at com.sun.xml.bind.v2.runtime.reflect.NullSafeAccessor.set(NullSafeAccessor.java:72)
    at com.sun.xml.bind.v2.runtime.reflect.Accessor.setUnadapted(Accessor.java:166)
    at com.sun.xml.bind.v2.runtime.JAXBContextImpl$7.set(JAXBContextImpl.java:931)
    at com.sun.xml.ws.server.sei.EndpointResponseMessageBuilder$DocLit.build(EndpointResponseMessageBuilder.java:223)
    at ...

Die Lösung für diese überaus nichts-sagende Fehlermeldung ist: Jeder Methodenname muss in den Service-Klassen einmalig vorhanden sein: ProjectService.findAll() und AgentService.findAll() sind nicht möglich. Stattdessen muss es heißen ProjectService.findAllProjects() u.s.w.

I am asking you for a donation.

You liked the content or this article has helped and reduced the amount of time you have struggled with this issue? Please donate a few bucks so I can keep going with solving challenges.


4 Comments

prunkster · February 7, 2009 at 5:27 am

Nur eine kleine Anmerkung:

Der erste aufgeführte Service soolte eigentlich unter der URL “/soap/agentService” erreichbar sein, oder? 😉

Btt: da ich’s nicht mehr ganz testen konnte, wie laufen die Unittests? Hatte noch das Problem, dass (glaube im project) die testFind-Methoden nicht in der richtigen Reihenfolge abliefen, sobald man die TestAll ausgeführt hat. Woran liegt das, wenn in der jeweiligen Test-Klasse die Methoden der logischen Reihenfolge nach korrekt geordnet sind?

MfG

Schakko · February 7, 2009 at 2:23 pm

Wie meinen? *g*
Ich hab das mal oben angepasst.

Die Unittests habe ich mir nicht mehr weiter angeschaut, da ich ja seit Donnerstag auch wieder am Studieren bin.
Bezüglich deines Problems: Hast du da irgendetwas in der setUp() bzw. tearDown()-Methode implementiert? Muss ich mir mal Montag genauer anschauen.

Robert · July 2, 2009 at 11:41 pm

Hi,

where can I set the saop address. For example (http://www.myweb.de/myproject/)

best regards,
Robert

Schakko · July 3, 2009 at 8:00 am

@Robert: What do you mean exactly? The URI of the local SOAP service is defined in wss:binding url-attribute.

Leave a Reply