1 Introduction
This article explains how to implement and test a SOAP web service using the Spring Web Services project. This example uses JAXB2 for (un)marshalling. To develop the service, I’ll use the contract-first approach, which consists in definning the service contract first, and based on this contract implement the service.
The article is divided into the following sections:
2 Explaining the application
3 Implementing the service
3.1 Creating the contract
3.2 Generating Java classes
3.3 Implementing the SOAP endpoint
3.4 Configuring the application
4 Testing the service
5 Additional information
5.1 Implementing the client
5.2 How it works internally
2 Explaining the application
The example application processes orders. We’ve got a front controller (messageDispatcher servlet) that will handle order requests, invoke the service to process the order and return a result.
You can get the source code at github.
3 Implementing the service
3.1 Creating the contract
Since we will use the contract-first approach, the simplest way to create the contract is by first definning sample xml documents and from that, we will generate the contract using a tool. Below are the sample xml documents:
client-request.xml
client-response.xml
In order to create the schema, we can use Trang, which is an open source tool that will allow us to generate the xsd schema from the xml documents. I’ve included this library into the project build path (you can get this jar from Trang web site) and I’ve created an Ant task for executing the conversion:
generate-schema.xml
Once the Ant task is executed, it will generate the schemas. Since schemas have been automatically generated, it is possible that we need to make some modifications to adapt it to our needs. Let’s take a look:
client-request.xsd
client-response.xsd
We can add different validations to these schemas, but in this example I’ll just modify several types like clientId, productId and confirmationId (xs:string) and orderDate (xs:date). The mapping of XML data types to Java types is done by JAXB. You can check which are the mappings provided here.
To finish with the schema, we will copy the response element into the request schema. I’ve created a third schema with both response and request:
client-service.xsd
The last step would consist in writting the contract, generally expressed as a WSDL file. If you don’t want to create it by hand, the Spring-ws project provides us with a way to generate this file from an XSD schema. We will use this second approach as you will see in the configuring the application section.
3.2 Generating Java classes
We will use JAXB2 to generate request and response objects. The XJC compiler from JAXB will be responsible of converting these objects from the XSD schema that we generated before. It will be executed as an Ant task:
This task will create Java classes in the xpadro.spring.ws.types package (you may need to refresh the project).
3.3 Implementing the SOAP endpoint
The endpoint receives the unmarshalled message payload and uses this data to invoke the order service. It will then return the service response, which will be marshalled by the endpoint adapter:
Here's a brief description of the annotations used by the endpoint:
@Endpoint: Registers the class as a component. In this way, the class will be detected by component scan.
@PayloadRoot: Registers the endpoint method as a handler for a request. This annotation will define what type of request message can be handled by the method. In our example, it will receive messages where its payload root element has the same namespace as defined in the XSD schema we created, and its local name is the one defined for the request (clientDataRequest).
@RequestPayload: Indicates the payload of the request message to be passed as a parameter to the method.
@ResponsePayload, indicates that the return value is used as the payload of the response message.
3.4 Configuring the application
web.xml
Application configuration (like datasource, transactionManager...)
Loads the application context
This is the servlet that will act as a Front Controller to handle all SOAP calls. Its function is to derive incoming XML messages to endpoints, much like the DispatcherServlet of Spring MVC.
servlet-config.xml
This configuration contains web service infrastructure beans.
In the dynamic wsdl, it doesn’t matter what value you put in the locationUri attribute because it will be handled by the MessageDispatcherServlet. Hence, the wsdl will be available at:
http://localhost:8081/spring-ws/orders/whatever/orderDefinition.wsdl
4 Testing the service
The following example creates a mock client which will access the web service:
The configuration file used on this test is pretty simple, just contains scanning of the service components:
5 Additional information
5.1 Implementing a client
To facilitate the client to access the web service, Spring provides us with the WebServiceTemplate class. This class contains methods for sending and receiving messages and it also uses converters to (un)marshal objects.
I’ve created a test that acts as a client of the service:
The configuration test file contains WebServiceTemplate configuration:
Just remember to start the server with the deployed web service application before executing this test.
5.2 How it works internally
If you just want to implement a web service, the article finished in the previous section. For those curious about how this really works, I will try to explain how a request is mapped to the endpoint, just a little more low-level than explained until this point.
When a request arrives to the MessageDispatcher, it relies on two components:
- It asks the EndpointMapping which is the appropriate endpoint.
- With the information received from the mapping it uses an endpoint adapter to invoke the endpoint. The adapter also support argument resolvers and return type handlers.
Endpoint mapping
MessageDispatcher contains a list of endpoint mappings, each of them, containing a map of previously registered method endpoints. In our case, the JAXB mapping PayloadRootAnnotationMethodEndpointMapping has registered all methods annotated with @PayloadRoot. If the qualified name of the payload of the message resolves as a registered method, it will be returned to the MessageDispatcher. If we didn’t annotate our method it would fail to process the request.
Endpoint adapter
MessageDispatcher will then ask each of its endpoint adapters if it supports the current request. In our case, the adapter checks if the following conditions are both true:
- At least one of the arguments passed to the method is annotated with @RequestPayload
- If the endpoint method returns a response, it must be annotated with @ResponsePayload
If an adapter is returned, it will then invoke the endpoint, unmarshalling the parameter before passing it to the method. When the method returns a response, the adapter will marshal it.
The following diagram is a much reduced version of this step in order to keep it simple:
Conclusion
We've seen an introduction on how to implement a simple web service and then test it. If you are interested, you can also take a look at how to test the client-side with MockWebServiceServer.Labels: Spring, unit-testing, web services