There are two possible ways to develop a SOAP web service: application servers incorporating or stand-alone web application. In the first case a SOAP Engine or JAX-WS Runtime (like Axis, CXF or Metro) needs to be installed in the Application Server. A stand-alone application instead it’s not Application Server dependent and it can be deployed in a Servlet Container like Tomcat. Tomcat doesn’t support JAX-WS by itself, so we need to help it by embedding a JAX-WS runtime.
What we will see is a stand-alone web application that expose a SOAP web service that calculate the sum of a list of integer.
Ingredients
- Spring 3.1.1
- CXF 2.2.3
1. WSDL file
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="/MyService/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="MyService" targetNamespace="/MyService/"> <wsdl:types> <xsd:schema targetNamespace="/MyService/"> <xsd:element name="SumRequest"> <xsd:complexType> <xsd:sequence> <xsd:element name="addend" type="xsd:int" minOccurs="2" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="SumResponse"> <xsd:complexType> <xsd:sequence> <xsd:element name="sum" type="xsd:int"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema> </wsdl:types> <wsdl:message name="SumIn"> <wsdl:part element="tns:SumRequest" name="parameters"/> </wsdl:message> <wsdl:message name="SumOut"> <wsdl:part element="tns:SumResponse" name="parameters"/> </wsdl:message> <wsdl:portType name="MyService"> <wsdl:operation name="Sum"> <wsdl:input message="tns:SumIn"/> <wsdl:output message="tns:SumOut"/> </wsdl:operation> </wsdl:portType> <wsdl:binding name="MyServiceSOAP" type="tns:MyService"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/> <wsdl:operation name="Sum"> <soap:operation soapAction="/MyService/Sum"/> <wsdl:input> <soap:body use="literal"/> </wsdl:input> <wsdl:output> <soap:body use="literal"/> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="MyService"> <wsdl:port binding="tns:MyServiceSOAP" name="MyServiceSOAP"> <soap:address location="/"/> </wsdl:port> </wsdl:service> </wsdl:definitions>
2. The POM file
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.madbit.soap</groupId> <artifactId>spring-soap</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>war</packaging> <name>Spring SOAP</name> <properties> <spring.version>3.1.1.RELEASE</spring.version> <cxf.version>2.2.3</cxf.version> </properties> <dependencies> <!-- Spring framework --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <!-- Used for REST --> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>${spring.version}</version> </dependency> <!-- Web Service runtime --> <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-transports-http</artifactId> <version>${cxf.version}</version> </dependency> </dependencies> <build> <plugins> <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>${basedir}/target/generated/</sourceRoot> <wsdlOptions> <wsdlOption> <wsdl>${basedir}/src/main/resources/wsdl/MyService.wsdl</wsdl> </wsdlOption> </wsdlOptions> </configuration> <goals> <goal>wsdl2java</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
3. Generate the classes
Now, let’s execute the command mvn generate-sources
in the project directory. The cxf-codegen-plugin will generate the Web Service interface MyService.java, with the JAX-WS annotations. Will be also generated a JAXB2 annotated classes for every type defined in the WSDL file (in this case SumRequest and SumResponse). All those files will be placed under /target/generated.
Below the code of the generated classes. MyService.java
package org.madbit.myservice; import javax.jws.WebMethod; import javax.jws.WebParam; import javax.jws.WebResult; import javax.jws.WebService; import javax.jws.soap.SOAPBinding; import javax.jws.soap.SOAPBinding.ParameterStyle; import javax.xml.bind.annotation.XmlSeeAlso; @WebService(targetNamespace = "/MyService/", name = "MyService") @XmlSeeAlso({ObjectFactory.class}) @SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE) public interface MyService { @WebResult(name = "SumResponse", targetNamespace = "/MyService/", partName = "parameters") @WebMethod(operationName = "Sum", action = "/MyService/Sum") public SumResponse sum( @WebParam(partName = "parameters", name = "SumRequest", targetNamespace = "/MyService/") SumRequest parameters ); }
SumRequest.java
package org.madbit.myservice; import java.util.ArrayList; import java.util.List; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "", propOrder = { "addend" }) @XmlRootElement(name = "SumRequest") public class SumRequest { @XmlElement(type = Integer.class) protected List<Integer> addend; public List<Integer> getAddend() { if (addend == null) { addend = new ArrayList<Integer>(); } return this.addend; } }
SumResponse.java
package org.madbit.myservice; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "", propOrder = { "sum" }) @XmlRootElement(name = "SumResponse") public class SumResponse { protected int sum; public int getSum() { return sum; } public void setSum(int value) { this.sum = value; } }
4. Implement the Web Service endpoint
Now we it’s time to create the Web Service implementation, that is the class which contains the business logic of the service. Let’s call this class MyServiceImpl.java. It implements the MyService interface previously generated as following:
package org.madbit.soap; import org.madbit.myservice.MyService; import org.madbit.myservice.SumRequest; import org.madbit.myservice.SumResponse; public class MyServiceImpl implements MyService { @Override public SumResponse sum(SumRequest parameters) { int sum = 0; for(Integer i: parameters.getAddend()){ sum += i; } SumResponse response = new SumResponse(); response.setSum(sum); return response; } }
5. Exposing a Web Service Using CXF
Exposing a stand-alone SOAP endpoint using the SimpleJaxWsServiceExporter
or the support for JAX-WS in a Java EE container in conjunction with Spring is simple, but these solutions ignore the largest cross-section of developers—people developing on Tomcat. Here again, we have plenty of options.
Tomcat doesn’t support JAX-WS by itself, so we needto help it by embedding a JAX-WS runtime. There are many choices, and you’re free to take your pick. Two popular choices are Axis2 and CXF, both of which are Apache projects. CXF represents the consolidation of the Celtix and XFire projects, which each had useful SOAP support. For our example, we’ll embed CXF since it’s robust, fairly well tested, and provides support for other important standards like JAX-RS, the API for REST-ful endpoints. Setup is fairly straightforward. You’ll need to include the CXFdependencies on your classpath, as well as the Spring dependencies.
Let’s walk through the moving pieces for configuration. A good place to start is the web.xml file. In our simple example, web.xml looks like this:
<?xml version="1.0" encoding="UTF-8"?> <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>AppStore Gateway</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/myservice-servlet.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Servlet for SOAP --> <servlet> <servlet-name>cxf</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>cxf</servlet-name> <url-pattern>/soap/*</url-pattern> </servlet-mapping> </web-app>
This web.xml file will look pretty much as all Spring MVC applications do. The only exception here is that we’ve also configured a CXFServlet, which handles a lot of the heavy lifting required to expose our service. In the Spring MVC configuration file, weather-servlet.xml, we’ll declare a bean for the weather service implementation and export it as a web service by using the Spring namespace support that CXF provides for configuring services (as well as clients, which we’ll soon see).
The Spring context file is underwhelming; most of it is boilerplate XML namespace and Spring context file imports. The only two salient stanzas are below, where we first configure the service itself as usual. Finally, we use the CXF jaxws:endpointnamespace to configure our endpoint.
<?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:aop="http://www.springframework.org/schema/aop" xmlns:jaxrs="http://cxf.apache.org/jaxrs" xmlns:simple="http://cxf.apache.org/simple" xmlns:soap="http://cxf.apache.org/bindings/soap" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/aop�?� http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd http://cxf.apache.org/simple http://cxf.apache.org/schemas/simple.xsd http://cxf.apache.org/bindings/soap http://cxf.apache.org/schemas/configuration/soap.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd "> <import resource="classpath:META-INF/cxf/cxf-servlet.xml" /> <import resource="classpath:META-INF/cxf/cxf.xml" /> <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" /> <bean id="myServiceImpl" class="org.madbit.soap.MyServiceImpl" /> <jaxws:endpoint implementor="#myServiceImpl" address="/MyService"> <jaxws:binding> <soap:soapBinding style="document" use="literal" version="1.1" /> </jaxws:binding> </jaxws:endpoint> </beans>
We tell the jaxws:endpointfactory
to use our Spring bean as the implementation. We tell it at what address to publish the service using the address element. Note that we’ve found that using a forward slash at the beginning of the address is the only way toget the endpoint to work reliably across both Jetty and Tomcat; your mileage may vary. We also specify some extra niceties like what binding style to use.
You don’t need to, of course. This is mainly for illustration. The Java code stays the same as before, with the javax.jws.WebServicemethod
and javax.jws.WebMethodannotations
in place. Launch the application and your web container, and then bring upthe application in your browser. In this example, the application is deployed at the root context (/), so the SOAP endpoint is available at http://localhost:8080/spring-soap/soap/MyService. If you bring upthe page at http://localhost:8080/, you’ll see a directory of the available services and their operations. Click the linkfor the service’s WSDL—or simply append ?wsdlto
the service endpoint—to see the WSDL for the service. WSDL describes the messages and endpoint to clients. You can use it to infer a client to the service on most platforms.
Invoking a Web Service Using CXF
Let’s now use CXF to define a web service client. We’ll want one to work with our new weather service, after all! Our client is the same as in previous recipes, and there is no special Java configuration or coding to be done. We simply need the interface of the service on the classpath. Once that’s done, you can use CXF’s namespace support to create a client.
<?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:aop="http://www.springframework.org/schema/aop" 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://www.springframework.org/schema/aop ? http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd "> <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/> <import resource="classpath:META-INF/cxf/cxf.xml"/> <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/> <jaxws:client serviceClass="org.madbit.soap.MyService" address="http://localhost:8080/spring-soap/soap/MyService" id="myService"/> <bean class="org.madbit.soap.MyServiceClient" id="client"> <property name="myService" ref="myService"/> </bean> </beans>
We use the jaxws:clientnamespace support to define to which interface the proxy should be bound, and the endpoint of the service itself. That is all that’s required. Our examples from previous recipes works otherwise unchanged: here we inject the client into the MyServiceClient and invoke it.