This page describes how to write a JAX-WS, Spring Framework based web service.
JAX-WS makes it easier to write web services in Java. It shilds the programmer from implementation
specifics just like JDBC API did for databases.
The two popular implementations out there are the Apache CXF and the Java Reference Implementation. This page describes an application that will run on both.
The project will be tested in Tomcat 6 with the Reference Implementation and we will move to JBoss that has a built in CXF implementation.
Few Notes about the application servers
- Tomcat 6 does not ship with JAX-WS RI (needs to be installed) or the Spring Framework (needs to be included in the WAR)
- JBoss 5.1 has the CXF and Spring Framework built in to the server. Therefore don’t provide your own JAX-WS or spring implementation JARS.
- The Spring application context should be initialized first. Use the ContextLoaderListener to accomplish this.
- The JAX-WS endpoint servlet/class should be defined in the web.xml and annotated with the JAX-WS annotations. In addition you should use the @PostConstruct on one of the methods to initialize. Within the method use the SpringBeanAutowiringSupport. See the Spring user manual for an example code.
Software versions
- Tomcat 6 and/or JBoss 5.1
- w. Servlet Spec 2.5
- Java 6
- Spring 2.5.6.SEC01 – older version used since JBOSSWS 5.1 comes pre-packaged with it.
- Maven 3
- Soap UI eclipse plugin – to call the web service
pom.xml
<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>com.test</groupId> <artifactId>jax-ws-spring-jboss</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <artifactId>spring-context</artifactId> <groupId>org.springframework</groupId> <version>2.5.6.SEC01</version> </dependency> <dependency> <artifactId>spring-core</artifactId> <groupId>org.springframework</groupId> <version>2.5.6.SEC01</version> </dependency> <dependency> <artifactId>spring-beans</artifactId> <groupId>org.springframework</groupId> <version>2.5.6.SEC01</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>2.5.6.SEC01</version> </dependency> </dependencies> <build> <finalName>jax-ws-spring-jboss</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> </project>
src/main/java/com/test/Customer.java
package com.test;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name="customer",namespace="http://com/test/")
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String name;
public Customer() {
super();
}
public Customer(String id) {
super();
this.id = id;
}
public Customer(String id, String name) {
super();
this.id = id;
this.name = name;
}
@XmlElement(nillable=false,namespace="http://com/test/")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@XmlElement(nillable=false,namespace="http://com/test/")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
src/main/java/com/test/CustomerService.java
package com.test;
import java.util.List;
import javax.jws.WebMethod;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;
@WebService(targetNamespace="http://com/test/")
@SOAPBinding(style=Style.DOCUMENT)
public interface CustomerService {
@WebMethod @WebResult(name="customer")
List<Customer> getCustomer(String customerID);
}
src/main/java/com/test/CustomerServiceImpl.java
package com.test;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.jws.WebService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
@WebService(endpointInterface="com.test.CustomerService")
public class CustomerServiceImpl implements CustomerService {
@Autowired
private TestDataManager testDataManager;
public TestDataManager getTestDataManager() {
return testDataManager;
}
public void setTestDataManager(TestDataManager testDataManager) {
this.testDataManager = testDataManager;
}
@PostConstruct
public void postConstruct() {
System.out.println("postconstruct has run.");
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}
@Override
public List<Customer> getCustomer(String customerId) {
System.out.println("JAX-WS getCustomer called");
return testDataManager.getCustomers();
}
}
src/main/java/com/test/TestDataManager.java
package com.test;
import java.util.ArrayList;
import java.util.List;
public class TestDataManager {
/* (non-Javadoc)
* @see com.test.TestDataManager#getCustomers()
*/
public List<Customer> getCustomers() {
List<Customer> customerList = new ArrayList<Customer>();
customerList.add(new Customer("1"));
customerList.add(new Customer("2"));
return customerList;
}
}
src/main/webapp/WEB-INF/applicationContext.xml
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<bean id="testDataManager" class="com.test.TestDataManager"/>
</beans>
src/main/webapp/WEB-INF/sun-jaxws.xml
<?xml version="1.0" encoding="UTF-8"?>
<endpoints
xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"
version="2.0">
<endpoint
name="CustomerService"
implementation="com.test.CustomerServiceImpl"
url-pattern="/customerService"/>
</endpoints>
Running within JBossWS 5.1 container
As mentioned above the JBossWS continer comes prepackaged with Spring Framework 2.5.6.SEC01. To make things easier I have used this older version has been used for Tomcat 6 as well. But interoperatibility is not a problem between Tomcat 6 and JBoss then feel free to use a newer version of the framework.
Since JBoss comes with the Spring Framework and by default it is packaged into the WAR we will need to tell JBoss to use the “parent-first” class loading scheme.
This is done by creating the following file into the WEB-INF folder of the WAR.
src/main/webapp/WEB-INF/jboss-classloading.xml
<classloading xmlns="urn:jboss:classloading:1.0"
domain="yourDomain"
parent-first="true">
</classloading>
JBoss uses a different method to initialize the JAX-WS Endpoints. It expects the endpoints to be defined in web.xml as regular servlets. This eliminates the need for the sun-jaxws.xml file.
src/main/webapp/WEB-INF/web.xml
<web-app version="2.5" 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">
<!-- Initialize the spring framework first. -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Use the following with JBossWS CXF implementation
<servlet>
<servlet-name>customerService</servlet-name>
<servlet-class>com.test.CustomerServiceImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>customerService</servlet-name>
<url-pattern>/customerService</url-pattern>
</servlet-mapping>
-->
<!-- Use the following with Tomcat and JAX-WS RI
-->
<listener>
<listener-class>
com.sun.xml.ws.transport.http.servlet.WSServletContextListener
</listener-class>
</listener>
<servlet>
<servlet-name>customerService</servlet-name>
<servlet-class>
com.sun.xml.ws.transport.http.servlet.WSServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>customerService</servlet-name>
<url-pattern>/customerService</url-pattern>
</servlet-mapping>
<!-- Test the application using the following URL:
http://localhost:8080/jax-ws-spring-jboss/customerService?wsdl -->
</web-app>
The web.xml file above contains both versions of the initilization logic. Comment out the proper section to run the application in JBoss or Tomcat. This is the only file you will need to change when switching between application servers.
Test using the SOAP UI plugin
Soap UI is the best interface I have seen so far to develop test and debug web services. Use it to submit a request to the server and you will see the following response.
Request
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:test="http://com/test/">
<soapenv:Header/>
<soapenv:Body>
<test:getCustomer>
<!--Optional:-->
<arg0>1</arg0>
</test:getCustomer>
</soapenv:Body>
</soapenv:Envelope>
Response
<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:getCustomerResponse xmlns:ns2="http://com/test/">
<customer>
<ns2:id>1</ns2:id>
</customer>
<customer>
<ns2:id>2</ns2:id>
</customer>
</ns2:getCustomerResponse>
</S:Body>
</S:Envelope>