Tutorial: Spring-WS using annotations
Before we begin you can download the source code here: spring-ws
Introduction and preliminar advices
This is a BASIC tutorial, written also for people that don't have any experience in WS development. Thus some concepts could be explained in a very simple and basic way, but my goal isn't to write a formal article on web services.
Just one more advice: I am a layered applications fan. I like to split application in several packages. And I didn't make any exception in this tutorial.
You will find here (and also in the code attached) two projects:
a web project, where are placed all the configuration files and service definition
a library project that contains all the implementation.
However, you can use a single project: it's my own mania.
Why this article?
Lately I needed to develop a WS. It was the second time I had to in my blog. and this is the first I had to use the “contract-first” approach (it was a must, for design issues). There is a great difference between the contract-last and the contract-first approach. You must really change the way you think.
In contract-last, you think as a common programmer in terms of modules, classes and methods. Then you rely on some external tool, such as Axis2, to “publish” your service (we'll see later what “publish” means).
Using contract-first your primal concern is the “publishing” of the WS. So what do you need to publish your service? A WSDL! A WSDL is a contract that describes entirely a service in terms of types (requests and responses) and operations. This is the most formally correct way to approach WS development. Unfortunately, as often happens, correctness doesn't mean semplicity.
Also, keep in mind that a service is its definition, not the implementation. In other words, the primary focus when using contract-first approach is on the interface between the world and the service.
Later in this document, we''ll suppose a fundamental difference between the words method and operation: a method belongs to a class, an operation belongs to a WS. This is to emphasyze the difference between a WS and its implementation.
I found quite hard to change the way I approached WS development, so I decided to write down this tutorial, also to put down in words what I learnt.
That's the why, now let's see the what.
All the stuff you need
Eclipse (I'm curremtly using Ganymede)
java 1.5 or higher (annotations are available since version 1.5; I'm currently using Java1.6)
Maven (I'm currently using Maven 2.1.0)
Apache Tomcat (I use version 6.0.18)
XJC eclipse plugin (classes generation tool from an XSD document)
SoapUI (due to many issues with SoapUI eclipse plugin, I opt for the stand-alone version)
For setting them up, I recommend you to surf the web looking for how-tos.
I said what I had to. Now we can start with the important things.
Why Spring-WS?
Approaching WS development thinking at the contract, means defining its WSDL and then develop the code (java, C#,....) to fullfill the requests. But a WSDL file is very complex and isn't easily understandable. Spring-WS helps a lot in this: you can avoid thinking in terms of WSDL and focus yourself on XSD, that's much more simple. At the end you focus yourself only on types definition.
Furthermore, there is one more reason: I approached Spring framework in the last month, first for security. This led me to learn something about the Spring beans. I found it a very smart idea and also a simple framework for developers, so I began estimating Spring.
The last brick was AOP and the library provided by Spring.
Now my whole project (started as a simple Axis2 service) has become a Spring-based project that uses beans, security, aop and services. Just to say that Spring is somehow invasive: when you begin to use it (and to understand it) you would like to use the same approach everywhere in your project.
What do we want to build?
What we want to develop here is a WS that exposes two operations:
Hello(): respond with a simple string “hello!”
HelloTo(string): respond with “hello “ + string
Creating a new web project
Create new project
Select “Maven → Maven project”
Fill the forms as you prefer but choose “spring-ws-archetype” as archetype
Creating library project
As before , create a new Maven project. This time choose “maven-archetype-quickstart” as archetype.
Using maven, add a dependency to Spring-WS. I recommend to choose the complete Spring-WS jar.
Always using Maven, reference this project on the web one.
Implementation
We have now created the basic structure of our projects. Since we are using the contract-first approach, let's focus on the web project initially (that's where we'll put our service definition).
If we look at the content of the WEB-INF folder, we can see that along with web.xml there is one more file: spring-ws-servlet.xml. Open these two files.
In web.xml you can see that is declared a servlet named spring-ws that is mapped to respond on every path of the web-app. We can close web.xml as we won't need it anymore (it can be modified, maybe for changing the mapping of the servlet, but it's not our goal at the moment).
The second is an empty XML file, except for the root element <schema> and the namespace definition. The file name is important: it is a convention that the file name is [servlet-name]-servlet.xml (actually, there is the chance to change the file name by adding something to the web.xml, but again it's not our goal). This file is the application context of the servlet and will be used later to wire-up services, marshallers, endpoints, etc inside the Spring IoC container.
(if you find it unclear, I recommend to read something about the Spring IoC container)
We will fill this file once we have created our service and its implementation.
Now, the question we must answer to begin developing a contract-first WS is: “What do we want our service to do? What must it expose to the world and how the world can comunicate with him?”. As said before, our service will expose two (very usefull, indeed) operations:
Hello()
- HelloTo(String)
A service is all about requests and responses. I'm gonna say a thing you should forget right away (but it is the most natural way for a programmer to think of a request): a request can be thought of as a method call in OOP. In this case the request will go through the network using protocol such as SOAP and will be something similar to this
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:spr="http://www.innove.it/blog.spring-ws-annotations">
<soapenv:Header/>
<soapenv:Body>
<spr:HelloRequest/>
</soapenv:Body>
</soapenv:Envelope>
We have to
As said, we have a two operations service: it means that we have to create two requests. First of all, let's create their XSD representation (we are using contract-first). Let's create the file “tutorial.xsd” in WEB-INF folder and edit it in order to make a change and to add our types definition.
Initially it will look like the following:
<?xml version="1.0" encoding="UTF-8"?>
<schema
xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.example.org/tutorial"
xmlns:tns="http://www.example.org/tutorial"
elementFormDefault="qualified">
</schema>
We have to change the target namespace in the schema tag: I suppose that the namespace is http://www.innove.it/blog.spring-ws-annotations (it's an arbitrary choice)
The result is:
<?xml version="1.0" encoding="UTF-8"?>
<schema
xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.innove.it/blog.spring-ws-annotations"
xmlns:tns="http://www.innove.it/blog.spring-ws-annotations"
elementFormDefault="qualified">
</schema>
Now let's add the definition of our requests: the first is empty, the second has an inner element (a string)
<element name="HelloRequest">
<complexType></complexType>
</element>
<element name="HelloToRequest">
<complexType>
<all>
<element name="to" type="string"></element>
</all>
</complexType>
</element>
We are done with the requests, it's time to describe also our responses. Both the responses contain a string object.
<element name="HelloResponse">
<complexType>
<all>
<element name="result" type="string"></element>
</all>
</complexType>
</element>
<element name="HelloToResponse">
<complexType>
<all>
<element name="result" type="string"></element>
</all>
</complexType>
</element>
Thanks to Spring, this is all we are requested to do for creating the contract of our service.We can start thinking about implementation. The first thing to do is to implement the requests and the responses. We can do it by hand, but I recommed to use the XJC Eclipse plugin, because classes must be annotated in a particular manner to use JAXB.
Once you have installed the plugin (very easy: just download the jar, copy it to the plugins folder and restart Eclipse), we can generate the classes by right clicking on our xsd file and choose “JAXB 2.1 → Run XJC”: you will be requested to specify the destination folder and the destination package.
I assume for the rest of the document that the folder choosen is the library project source folder and that the package is it.innove.blog.tutorials.spring.ws.schema.
Let's look at the library project now: you can notice that some classes have been created in the specified package. Four of them implement in Java language the types described in our xsd.
Let's proceed implementing the remaining objects that will be used to populate the Spring IoC container.
As a first step, let'implement the service
packageit.innove.blog.tutorials.spring.ws.services;
/**
*@authorstefano
*
*/
publicclassHelloService
{
publicStringhello()
{
return"hello!";
}
publicStringhelloTo(Stringto)
{
return"hello "+to;
}
}
Nothing to say about that.
Let's begin with the core of the implementation: the endpoints. These are the actual entry point to the WS implementation.
or the annotations to accomplish this task. In this example we will tell Spring to use annotations.
In general, there should be a single endpoint class for every single request, as said. But, as we are using annotations, we can relax this constraint, obtaining that requests have a 1:1 multeplicity with methods (thus, a class can map several requests).
Explanation: for every request there must be an endpoint (you can think of it as an handler). The service class will never be accessed directly: it is responsibility of the endpoints to route a request to the correct method of the service class. Also, every endpoint is mapped to a particular request, so the multiplicity between endpoint and requests is 1:1. It is a Spring-WS responsibility to map endpoints and requests, and it will use the context configuration
Let,s look at the source code; after we will spend a couple of words about it
packageit.innove.blog.tutorials.spring.ws.endpoints;
importit.innove.blog.tutorials.spring.ws.schema.HelloRequest;
importit.innove.blog.tutorials.spring.ws.schema.HelloResponse;
importit.innove.blog.tutorials.spring.ws.schema.HelloToRequest;
importit.innove.blog.tutorials.spring.ws.schema.HelloToResponse;
importit.innove.blog.tutorials.spring.ws.services.HelloService;
importorg.springframework.ws.server.endpoint.annotation.Endpoint;
importorg.springframework.ws.server.endpoint.annotation.PayloadRoot;
/**
*@authorstefano
*
*/
@Endpoint
publicclassHelloServiceEndpoint
{
privateHelloServiceservice;
HelloServiceEndpoint(HelloServiceservice)
{
this.service=service;
}
@PayloadRoot(localPart="HelloRequest",namespace="http://www.innove.it/blog.spring-ws-annotations")
publicHelloResponsehello(HelloRequestrequest)
{
HelloResponseresponse=newHelloResponse();
response.setResult(service.hello());
returnresponse;
}
@PayloadRoot(localPart="HelloToRequest",namespace="http://www.innove.it/blog.spring-ws-annotations")
publicHelloToResponsehelloTo(HelloToRequestrequest)
{
HelloToResponseresponse=newHelloToResponse();
response.setResult(service.helloTo(request.getTo()));
returnresponse;
}
}
There are three important things to say about this class:
- All methods receive and return objects that are POJOs annotated as requested by JAXB
- The class is annotated with @Endpoint: this tells Spring that he can look in this class to find request mappings
- Methods are annotated with @PayloadRoot(...). The parameters match the name of the request type defined in the XSD and the targetNamespace of the XSD. These matches are very important.
We're almost there!
Then come back to the web project and open spring-ws-servlet.xml document.
We are gonne wire-up all the objects to build the context of the service. We'll make heavy use of Spring-beans here: if you are not used to this, I recommed to read the Spring documentation and perform some test. It is impossible to explore this concern here. You can refere to the Spring official documentation downloadable here.
Let's do this step-by-step.
The first thing to add is the definition of the service implementation.
We have implemented all we needed (actually, there is one last class to implement but we will see it later, because it would be impossible here to explain why we should extend a Spring class). Now we can focus on the servlet-context file.
<bean id="helloService" class="it.innove.blog.tutorials.spring.ws.services.HelloService" />
Nothing to say. Just remember the id because it will be referenced later in other beans.
Next, the marshalling.
<bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>it.innove.blog.tutorials.spring.ws.schema.HelloRequest</value>
<value>it.innove.blog.tutorials.spring.ws.schema.HelloResponse</value>
<value>it.innove.blog.tutorials.spring.ws.schema.HelloToRequest</value>
<value>it.innove.blog.tutorials.spring.ws.schema.HelloToResponse</value>
<value>it.innove.blog.tutorials.spring.ws.types.StringType</value>
</list>
</property>
</bean>
This one is worth a couple of words.
We are instantiating a bean responsible of the marshalling based on Jaxb and we are telling him which types he has to take care of. You must add here all the classes that has to be marshalled (serialized and deserialized).
Now, we must glue it together, always using the Spring IoC container.
Let's define the endpoint (we have only one endpoint: in real world endpoints will be several)
<bean id="helloServiceEndpoint" class="it.innove.blog.tutorials.spring.ws.endpoints.HelloServiceEndpoint">
<constructor-arg ref="helloService" />
</bean>
Note that the constructor is invoked passing him the service. I want to remark that this is beacause we defined the constructor in this way. Spring doesn't force us in any manner.
Go on wiring-up
<bean class="it.innove.blog.tutorials.spring.ws.marshalling.endpoints.adapters.PayloadMarshallingMethodEndpointAdapter">
<constructor-arg ref="marshaller" />
</bean>
This bean calls the methods on the endpoint and needs to know the marshaller to delegate the serialization / deserialization of objects.
Then
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping" />
This object enables Spring to use code annotations (Spring will search the beans to find the classes annotated with @Endpoint and he will use them to map requests).
Do you see the light at the end of the tunnel? It is not a mirage. And it's neither the train. One last effort.
We have never talked about WSDL. That's because Spring autogenerate it, but he must be instructed to. We have to define two more beans.
<bean id="schema" class="org.springframework.xml.xsd.SimpleXsdSchema">
<property name="xsd" value="/WEB-INF/spring-ws-annotations-tutorial.xsd" />
</bean>
<bean id="contract" class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
<property name="schema" ref="schema" />
<property name="portTypeName" value="Tutorial" />
<property name="locationUri" value="http://localhost:8080/spring-ws-annotations-tutorial-web/services/" />
<propertyname="targetNamespace"value="http://www.innove.it/blog.spring-ws-annotations/contract"/>
</bean>
The second defines the class that has the responsibility of WSDL generation. Note the id of the bean: it will be the name of the WSDL. In other words, once published your service,your WSDL will be published at the URL
http://[locationUri]/[bean-id].wsdl
The first defines a resource: our XSD schema.
That's nearly all. But there is one last thing to say. The one I left unsaid before, do you remember?
If you run the project as is, you will get a runtime error. The problem is in the definition of the helloServiceEndpointAdapter bean. After surfing the web I found the solution in Spring forum. Unfortunately, I,' not able to find that post anymore, so I can't quote exactly the author of the solution. If you find it, please let me know.
The solution is extending a class of the Spring library
packageit.innove.blog.tutorials.spring.ws.marshalling.endpoints.adapters;
importorg.springframework.oxm.Marshaller;
importorg.springframework.ws.server.endpoint.MethodEndpoint;
importorg.springframework.ws.server.endpoint.adapter.MarshallingMethodEndpointAdapter;
importorg.springframework.ws.server.endpoint.annotation.PayloadRoot;
publicclassPayloadMarshallingMethodEndpointAdapter extendsMarshallingMethodEndpointAdapter
{
publicPayloadMarshallingMethodEndpointAdapter(Marshallermarshaller)
{
super(marshaller);
}
@Override
protectedbooleansupportsInternal(MethodEndpointmethodEndpoint)
{
returnmethodEndpoint.getMethod().isAnnotationPresent(PayloadRoot.class);
}
}
and use this class in the bean helloServiceEndpointAdapter.
That's really all!
Hope you'll find it usefull
Stefano
Note
In the source code attached, files and classes names may differ from the ones in this document.
Last, I want to point out that this tutorial rise from the experiences I had in the last week: I'm still learning, and maybe some basic concepts may not be formally correct, even if the sample service works correctly. If you find something unprecise in this article, please let me know. Any hint will be appreciated.
For more information (and deeper explanations) about the Spring and the Spring-WS framework, I recommend you to refer to the official documentation that is downloadable from the SpringFramework wed site: www.springframework.org