jws

Jax-Ws AsyncHandler Example

In this example, we will show you how to implement an asynchronous SOAP web service and client using the AsyncHandler callback handler.

1. Introduction

There are two ways to invoke a web service, synchronously and asynchronously.

When invoking a web service synchronously, the client must wait for the response before resuming its work. When invoking a web service asynchronously, the client can work on other tasks while it waits for the response. When the service sends the response, the client will be notified and can, at that point, process the response.

The two approaches for working asynchronously with responses are “callback” and “polling”. In this example, we will demonstrate the callback approach using a callback handler.

1.1 Tools Used in this Example

  • Eclipse Oxygen
  • Maven 3.2.1
  • Tomcat 8.5.4

For Maven support within Eclipse, install M2Eclipse. Please visit the M2Eclipse website for more information.

To setup a Tomcat server for use in Eclipse, see Tomcat in Eclipse: 6 popular how to questions.

2. JAX-WS AsyncHandler Example

In this example, we’ll develop a simple web service that updates a message both synchronously and asynchronously. This exercise uses “Java-first” or “bottom-up” web services development.

2.1 Create the Web Service Provider

2.1.1 Create the Web Service Project

Let’s begin by creating a simple Maven Project.

  1. Select New -> Other… Maven Project. Click “Next”.
  2. Select “Create a simple project (skip archetype selection)” and click “Next”.
  3. Enter a Group Id and Artifact Id. Select “war” for Packaging and enter a Name and Description if desired. Click “Finish”.

Maven Project Configuration

At this point, you will see the following error: web.xml is missing and<failOnMissingWebXml> is set to true, since we elected to package the application as a war file.  To fix this, right-click on the project and select Java EE Tools -> Generate Deployment Descriptor Stub.

2.1.2 Update the POM File

Open the pom.xml and add the following Maven plugins just above the closing </project> tag:

pom.xml

<build>
  <pluginManagement>
    <plugins>
      <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.6.1</version>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
      </configuration>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-war-plugin</artifactId>
      <version>3.0.0</version>
      <configuration>
        <warSourceDirectory>src/main/webapp</warSourceDirectory>
        <webXml>src/main/webapp/WEB-INF/web.xml</webXml>
        <warName>JaxWsAsync</warName>
      </configuration>
      </plugin>
    </plugins>
  </pluginManagement>
</build>

Also, add the following dependencies below the closing </build> tag:

<dependencies>
  <dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-frontend-jaxws</artifactId>
    <version>3.1.11</version>
  </dependency>
  <dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-transports-http</artifactId>
    <version>3.1.11</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.3.8.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
   <version>4.3.8.RELEASE</version>
  </dependency>
</dependencies>

Save the changes and select: Maven->Update Project from the project context menu.

2.1.3 Create the Entity Class

Let’s create the entity class, TestMessage.

TestMessage.java

public class TestMessage {

	private String message;

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
}

2.1.4 Create the Service Endpoint Interface (SEI)

We’ll define two methods for updating the message of TestMessage:

  • A synchronous method that takes one parameter, the request data.
  • An asynchronous method that takes two parameters, the request data and a callback handler (AsyncHandler).

ChangeMessage.java

import java.util.concurrent.Future;

import javax.jws.WebService;
import javax.xml.ws.AsyncHandler;
import javax.xml.ws.ResponseWrapper;

@WebService
public interface ChangeMessage {

    @ResponseWrapper(localName = "changeMessageResponse", className = "com.javacodegeeks.examples.jaxws.service.TestMessage")
    public String changeMessage(String message);
    
    @ResponseWrapper(localName = "changeMessageResponse", className = "com.javacodegeeks.examples.jaxws.service.TestMessage")
    public Future changeMessageAsync(String message, AsyncHandler asyncHandler);
}

AsyncHandler is an interface.  The AsyncHandler callback handler is provided by the caller and is responsible for handling the response.  We’ll implement the AsyncHandler interface in the client code.

We suffix the asynchronous method’s name with “Async” as this is the naming convention.  It returns a Future that represents the pending result of the task.  The Future interface exposes methods to test for completion of a task and to retrieve the result of the task, as we will see in the client code.

2.1.5 Implement the Service Interface

Next, we’ll implement the SEI created in the previous step.

We’ll annotate the synchronous method with @UseAsyncMethod.  This sounds counterintuitive but it is correct. If the client transport supports asynchronous request processing, the CXF runtime will invoke the asynchronous method. Otherwise, the service will invoke the synchronous method.

ChangeMessageImpl.java

import java.util.concurrent.Future;

import javax.jws.WebService;
import javax.xml.ws.AsyncHandler;

import org.apache.cxf.annotations.UseAsyncMethod;
import org.apache.cxf.jaxws.ServerAsyncResponse;

@WebService(endpointInterface = "com.javacodegeeks.examples.jaxws.service.ChangeMessage", serviceName = "ChangeMessage")
public class ChangeMessageImpl implements ChangeMessage {

	@Override
	@UseAsyncMethod
	public String changeMessage(String message) {
        System.out.println("Executing changeMessage synchronously\n");
        return "echo message: " + message;
	}

	@Override
	public Future<?> changeMessageAsync(final String message, final AsyncHandler<TestMessage> asyncHandler) {
        System.out.println("Executing changeMessageAsync asynchronously\n");
        final ServerAsyncResponse<TestMessage> asyncResponse = new ServerAsyncResponse<TestMessage>() ;
        new Thread() {
            public void run() {
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                TestMessage payload = new TestMessage();
                payload.setMessage("message: " + message);
                asyncResponse.set(payload);
                System.out.println("Responding on another thread\n");
                asyncHandler.handleResponse(asyncResponse);
            }
        }.start();

        return asyncResponse;
	}

}

Let’s go over the asynchronous method.

First, we instantiate an org.apache.cxf.jaxws.ServerAsyncResponse<TestMessage> object. ServerAsyncResponse implements javax.xml.ws.Response and represents the SOAP response. Next, we create a new thread where we simulate a delay with Thread.sleep(). Inside the thread, we create a TestMessage object and set its message. Next, we set the payload of the SOAP response with TestMessage. We then set the response in the AsyncHandler that will handle said response with asyncHandler.handleResponse(asyncResponse). Finally, we return the ServerAsyncResponse.

Note: Since Response extends Future, we can return ServerAsyncResponse in the asynchronous method.

2.1.6 Create the Spring Configuration File

Create the cxf-servlet.xml file in the webapp/WEB-INF directory to set up the service endpoint.

cxf-servlet.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:jaxws="http://cxf.apache.org/jaxws"
xmlns:cxf="http://cxf.apache.org/core"
xmlns:soap="http://cxf.apache.org/bindings/soap"
xsi:schemaLocation="http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.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">

  <jaxws:endpoint id="changeMessage" implementor="com.javacodegeeks.examples.jaxws.service.ChangeMessageImpl"
		address="/ChangeMessage" />
</beans>

2.1.7 Configure the CXF servlet in web.xml

The last step is to configure the CXFServlet in web.xml.  We’ll map the servlet to handle all requests coming through /services/*.

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
 <display-name>JaxWsAsyncHandlerExample</display-name>
 <servlet>
   <servlet-name>cxfservlet</servlet-name>
   <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
   <load-on-startup>1</load-on-startup>
   <async-supported>true</async-supported>
 </servlet>
 <servlet-mapping>
   <servlet-name>cxfservlet</servlet-name>
   <url-pattern>/services/*</url-pattern>
 </servlet-mapping>
</web-app>
	

We include the <async-supported> tag and set its value to “true”.  Notice that we also have to change the schema location and version to 3.1 to add support for the async-supported tag.

2.1.8 Start the ChangeMessage Service

Run maven clean to clear the target directory and maven install to create the war file. (Right-click the project and select Run as and you will see these options in the context menu.) The generated war file can be deployed to a Tomcat server by copying it to the webapps directory and starting the server.

Let’s test our web service from within Eclipse. Right-click the project and select Run As -> Run on Server. Select the Tomcat server and add our project to configure and click “Finish”. When the application starts, we will see a 404 error in the browser, since we do not have a welcome page specified in web.xml. Enter /services/ at the end of the URL and hit “Enter”. You will see a link to the WSDL file on the “Available SOAP services” page.

ChangeMessage Service

Click the link to see the WSDL file that was generated by the web services runtime.

Note: If you are using Eclipse’s internal browser, you may see a blank page. Copy the URL from the address bar and open the link in an external browser.

2.2 Create the Web Service Client

We will create a stand-alone Java client so we will first create a Java project for the client code.

2.2.1 Create the Client Project

Create a new Maven Project.

  1. Select New -> Other… Maven Project. Click “Next”.
  2. Select “Create a simple project (skip archetype selection)” and click “Next”.
  3. Enter a Group Id and Artifact Id. Select “jar” for Packaging and enter a Name and Description if desired. Click “Finish”.

Maven Project Configuration

2.2.2 Update the POM File

Open the pom.xml file and add the following Maven plugin just above the closing </project> tag:

pom.xml

<build>
 <pluginManagement>
   <plugins>
     <plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-compiler-plugin</artifactId>
       <version>3.6.1</version>
       <configuration>
         <source>1.8</source>
         <target>1.8</target>
       </configuration>
     </plugin>
   </plugins>
 </pluginManagement>
</build>

Also, add the following dependencies below the closing </build> tag:

 <dependencies>
   <dependency>
     <groupId>org.apache.cxf</groupId>
     <artifactId>cxf-rt-frontend-jaxws</artifactId>
     <version>3.1.11</version>
   </dependency>
   <dependency>
     <groupId>org.apache.cxf</groupId>
     <artifactId>cxf-rt-transports-http</artifactId>
     <version>3.1.11</version>
   </dependency>
 </dependencies> 

Save the changes and select Maven->Update Project… from the project context menu and click “OK”.

2.2.3 Create the Async Binding File

Create an asynchronous bindings file named async_binding.xml in the /src/main/resources folder. We will use this file when we generate the client code.

async_binding.xml

 
<bindings xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
 xmlns="http://java.sun.com/xml/ns/jaxws">
 <bindings node="wsdl:definitions">
  <enableAsyncMapping>true</enableAsyncMapping>
 </bindings>
</bindings>

The bindings file includes the <enableAsyncMapping> flag to generate the asynchronous method in the client code.

2.2.4 Generate the Client Stubs

Next, we’ll generate the client code. Open a terminal window and change to the src/main/java directory of the client Java project, for example, /JaxWsAsyncHandlerClientExample/src/main/java.

Run the wsimport command (shown below) to generate the client code.  (Replace <WSDL URL> with the one copied from your browser.)

Note: The web service must be running when you run the wsimport command.

wsimport -keep -p com.javacodegeeks.examples.jaxws.client -b ../resources/async_binding.xml <WSDL URL>

The -keep option saves the generated files. The -p option specifies the Java package we want to use for the files. The -b option specifies the location of our binding file.  Running the wsimport command will generate the following files:

ChangeMessage.class
ChangeMessage.java
ChangeMessage_Service.class
ChangeMessage_Service.java
ChangeMessage_Type.class
ChangeMessage_Type.java
ObjectFactory.class
ObjectFactory.java
TestMessage.class
TestMessage.java
package-info.class
package-info.java

2.2.5 Implement the AsyncHandler Class

The next step is to implement our AsyncHandler. Since AsyncHandler is a generic interface, we’ll use TestMessage as the type parameter, which is what we expect to receive in the response.

OurAsyncHandler.java

import javax.xml.ws.AsyncHandler;
import javax.xml.ws.Response;

public class OurAsyncHandler implements AsyncHandler<TestMessage>{
	
	private TestMessage tMessage;

	@Override
	public void handleResponse(Response<TestMessage> response) {
		
		try {
			tMessage = response.get();
		} catch (Exception e) {
			e.printStackTrace();
		}	
	}
	
	public String getResponse() {
		return tMessage.getMessage();
	}
}

The AsyncHandler interface defines one method, handleResponse(Response<T> response), which is called when the response is available. In our implementation of this method, we get the payload of the response by calling its get() method. We also add a method to retrieve the message of TestMessage.

2.2.6 Create the Client Application

Now, we’ll write a Java client to receive the message asynchronously. First, we create a web service client from the service endpoint. Then, we create an instance of OurAsyncHandler. Next, we call the asynchronous method that accepts the request data and asyncHandler as parameters. It returns a Future response that we check periodically for the arrival of the response using isDone(). Finally, we get the payload of the response.

Once the response returns, we’ll print out the TestMessage object’s message to the console and exit the program.

Notice that the main method declares that it throws InterruptedException. Since this application has not defined another thread to cause the interrupt, we won’t bother handling the exception.

MessageClient.java

import java.util.concurrent.Future;

public class MessageClient {

	public static void main(String[] args) throws InterruptedException {
		
		ChangeMessage_Service service = new ChangeMessage_Service();
		ChangeMessage port = service.getChangeMessageImplPort();
		
		OurAsyncHandler handler = new OurAsyncHandler();
		
		Future<?> response = port.changeMessageAsync("JCG rocks!", handler);
		while(!response.isDone()) {
			Thread.sleep(100);
		}

		String message = handler.getResponse();
		System.out.println("Service responded through callback with " + message);

		System.exit(0);
	}
}

2.2.7 Test the Asynchronous Service

Let’s test our web service. Right-click the Java client application and select Run As -> Java Application. You will notice a delay while our application checks for the response, since the service is sleeping for 10 seconds before returning the response. After this delay, you will see the message printed out on the console.

Service responded through callback with message: JCG rocks!

3. Conclusion

In this example, we showed how to implement an asynchronous SOAP web service and client using the AsyncHandler callback handler.

4. Download the Source Code

This was a JAX-WS AsyncHandler example.

Download

You can download the full source code of this example here:

  1. JaxWsAsyncServer
  2. JaxWsAsyncClient

Gilbert Lopez

Gilbert Lopez is an application developer and systems integration developer with experience building business solutions for large and medium-sized companies. He has worked on many Java EE projects. His roles have included lead developer, systems analyst, business analyst and consultant. Gilbert graduated from California State University in Los Angeles with a Bachelor of Science degree in Business.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
nanaji
nanaji
4 years ago

very good article.

Back to top button