Глава 9 Spring in Action 2th edition_1 |
Building contract-first web services in Spring
This chapter covers
- Defining XML service contracts
- Creating document-centric web services
- Marshaling and unmarshaling XML messages
- Building template-based web service clients
Imagine that it’s the weekend and you’ve got a trip planned. Before you hit the road, you stop by your bank to deposit your paycheck and to pick up some spending cash.
This is not an unusual scenario, but what makes it interesting is that you bank at an unusual bank. When you walk in the door, there are no tellers to help you. Instead, you have full access to handle the transaction yourself. You have direct access to the ledger and to the vault, allowing you to handle all of the minute details of the transaction on your own. So, you perform the following tasks:
- 1 - You place your signed paycheck in a box designated for deposited checks.
- 2 - You edit your account’s ledger, incrementing the balance by the amount on the check.
- 3 - You take $200 from the vault and place it in your pocket.
- 4 - You edit your account’s ledger, decrementing the balance by $200.
- 5 - As a thank-you for all of the hard work you did, you pay yourself a service fee by pocketing another $50 bill on the way out the door.
Whoa! Steps 1–4 seem to be on the up and up. But isn’t step 5 a bit odd?
The problem (if that’s what you want to call it) with this bank is that they trust their customers with too much direct access to the internal workings of the bank. Instead of providing an appropriate interface to the inner workings of the bank (commonly known as a “teller”), they give you full access to the inner workings to do as you please. Consequently, you are able to perform an unrecorded and questionable withdrawal.
As nice as this is for the customer, most banks don’t work that way (if your bank really does allow you this kind of access, please email me—I’d really like to start banking there!). Most banks have tellers, ATM machines, and websites to allow you to manipulate your account. These interfaces to the bank are customer-facing abstractions to the vault and the ledger. While they may provide service with a smile, they only allow you to perform activities that fit within the bank’s business model.
Likewise, most applications do not allow direct access to the internal objects that make up the application. Take web applications, for instance. In a Spring MVC-based web application (which we’ll look at when we get to chapter 13), users interact with the application through controllers. Behind the scenes, there may be dozens or even hundreds of objects that perform the core tasks of the application. But the user is only allowed to interact with the controllers, which, in turn, interact with the back-end objects.
In the previous chapter, we saw that XFire is a quick and easy way to develop web services using remote exporters. But when we export an application bean as a web service, we’re exposing the application’s internal API, which carries with it some consequences: as I alluded to in the banking scenario, you must be careful not to accidentally expose too much of your application’s internal API. Doing so may give your web service clients more access to the inner workings of your application than they need.
In this chapter, you’ll learn an alternative way of building web services using the Spring-WS framework. We’ll separate the service’s external contract from the application’s internal API, and we’ll focus on sending messages between clients and the service, not on invoking remote methods.
I won’t deceive you: building web services with Spring-WS is not as simple as exporting them with XFire. However, I think you’ll find that it isn’t that much more difficult and that the architectural advantages that Spring-WS affords make it well worth considering.
Introducing Spring-WS
Spring Web Services (or Spring-WS, for short) is an exciting new subproject of Spring that is focused on building contract-first web services. What are contract-first web services? It might be easier to answer that question by first talking about their antithesis: contact-last web services.
In chapter 8 (see section 8.5.1), we used XFire to export bean functionality as a remote web service. We started by writing some Java code (the service implementation). Then we configured it as a <bean> in Spring. Finally, we used XFire’s XFireExporter to turn it into a web service. We never had to explicitly define the service’s contract (WSDL and XSD). Instead, XFire automatically generated the contract after the service was deployed. In short, the contract was the last thing defined, thus the designation of “contract-last.”
Contract-last web services are a popular approach to web service development for one basic reason: they’re easy. Most developers don’t have the intestinal fortitude required to understand WSDL, SOAP, and XML Schema (XSD). In the contract-last approach, there’s no need to manipulate complex WSDL and XSD files. You simply write a service class in Java and ask the web service framework to “SOAP-ify” it. If a web services platform such as XFire is willing to cope with the web services acronyms then why should we worry ourselves with it?
But there’s one small gotcha: when a web service is developed contract last, its contract ends up being a reflection of the application’s internal API. Odds are that your application’s internal API is far more volatile than you (or your service’s clients) would like the external API to be. Changes to the internal API will mean changes to your service’s contract, which will ultimately require changes in the clients that are consuming your service. A clever refactoring today may result in a new service contract tomorrow.
This leads to the classic web services versioning problem. It’s much easier to change a web service’s contract than to change the clients that consume that service. If your web service has 1,000 clients and you change your service’s contract then 1,000 clients will be broken until they are changed to adhere to the new contract. A common solution to this problem is to maintain multiple versions of a service until all clients have upgraded. This, however, would multiply maintenance and support costs, as you would have to support multiple versions of the same service.
A better solution is to avoid changing the service’s contract. And when the contract must be changed, the changes shouldn’t break compatibility with previous versions. But this can be difficult to do when the service’s contract is automatically generated.
In short, the problem with contract-last web services is that the service’s most important artifact, the contract, is treated as an afterthought. The focus of a contract-last web service is on how the service should be implemented and not on what it should do.
The solution to contract-last’s problems is to flip it on its head—create the contract first and then decide how it should be implemented. When you do, you end up with contract-first web services. The contract is written with little regard for what the underlying application will look like. This is a pragmatic approach, because it emphasizes what is expected of the service and not how it will be implemented.
You’re probably getting an uneasy feeling about now. It could be that unusually large burrito that you had for lunch... or it could be that you’re terrified that we’re going to have to create a WSDL file by hand.
Don’t worry. It’s not going to be as bad as you think. Along the way, I’ll show you several tricks that make it easy to create the service contract. (If that doesn’t make you feel better, I suggest you take an antacid and cut back on the spicy food at lunch.)
The basic recipe for developing a contract-first web service with Spring-WS appears in table 9.1.
Table 9.1 The steps for developing a contract-first web service.
Step | | |
---|---|---|
1 | Define the service contract. | This involves designing sample XML messages that will be processed by our web service. We’ll use these sample messages to create XML Schema that will later be used to create WSDL. |
2 | Write a service endpoint. | We’ll create classes that will receive and process the messages sent to the web service. |
3 | Configure the endpoint and Spring-WS infrastructure. | We’ll wire up our service endpoint along with a handful of Spring-WS beans that will tie everything together. |
To demonstrate Spring-based web services, we’re going to build a poker hand evaluation service. Figure 9.1 illustrates the requirements for this web service: given five cards, identify the poker hand in question.
Since we’re creating a contract-first web service, it’s only logical that the first thing we should do is define the service contract. Let’s get started.
Defining the contract (first!)
The single most important activity in developing a contract-first web service is defining the contract itself. When defining the contract, we’ll define the messages that are sent to and received from the service, with no regard for how the service is implemented or how the messages will be handled.
Even though the topic of this chapter is Spring-WS, you’ll find that this section is remarkably Spring free. That’s because the contract of a web service should be defined independent of the implementation of the service. The focus is on what needs to be said, not how it needs to be done. We’ll tie this all into Spring-WS starting in section 9.3. But for now, the techniques described in this section are applicable to contract-first services in general, regardless of the underlying framework.
A contract-first view of web services places emphasis on the messages that are sent to and received from services. Therefore, the first step in defining a service’s contract is determining what the messages will look like. We’ll start by creating sample XML messages for our web services that we’ll use to define the service contract.
Creating sample XML messages
In simple terms, our poker hand evaluation service takes a poker hand made up of five cards as input and produces a poker hand designation (e.g., Full House, Flush, etc.) as output. Writing a sample input message for the service as XML might look a little like this:
<EvaluateHandRequest
xmlns="https://www.springinaction.com/poker/schemas">
<card>
<suit>HEARTS</suit>
<face>TEN</face>
</card>
<card>
<suit>SPADES</suit>
<face>KING</face>
</card>
<card>
<suit>HEARTS</suit>
<face>KING</face>
</card>
<card>
<suit>DIAMONDS</suit>
<face>TEN</face>
</card>
<card>
<suit>CLUBS</suit>
<face>TEN</face>
</card>
</EvaluateHandRequest>
That’s fairly straightforward, isn’t it? There are five <card> elements, each with a <suit> and a <face>. That pretty much describes a poker hand. All of the <card> elements are contained within an <EvaluateHandRequest> element, which is the message we’ll be sending to the service.
As simple as the input message was, the output message is even simpler:
<EvaluateHandResponse
xmlns="https://www.springinaction.com/poker/schemas">
<handName>Full House</handName>
</EvaluateHandResponse>
The <EvaluateHandResponse> message simply contains a single <handName> element that holds the designation of the poker hand.
These sample messages will serve as the basis for our service’s contract. And, although this may bring about some disbelief on your part, you should know that by defining these sample messages, we’ve already finished the hardest part of designing the service contract. No kidding.
Forging the data contract
Now we’re ready to create the service contract. Before we do that, however, let’s conceptually break the contact into two parts:
- The data contract will define the messages going in and out of the service. In our example, this will include the schema definition of the <EvaluateHandRequest> and <EvaluateHandResponse> messages.
- The operational contract will define the operations that our service will perform. Note that a SOAP operation does not necessarily correspond to a method in the service’s API.
Both of these contract parts are typically (but not necessarily) defined in a single WSDL file. The WSDL file usually contains an embedded XML Schema that defines the data contract. The rest of the WSDL file defines the operational contract, includingone or more <wsdl:operation>elementswithin the <wsdl:binding> element.
Don’t worry yourself too much with the details of that last paragraph. I promised that creating the contract would be easy, so there’s no need for you to know the details of what goes into a WSDL file. The key point is that there are two distinct parts of the contract.
The data contract is defined using XML Schema (XSD). XSD allows us to precisely define what should go into a message. Not only can we define what elements are in the message, but we can also specify the types of those messages and place constraints on what data goes into the message.
Although it’s not terribly difficult to write an XSD file by hand, it’s more work than I care to do. So, I’m going to cheat a little by using an XSD inference tool. An XSD inference tool examines one or more XML files and, based on their contents, produces an XML schema that the XML files can be validated against.
Several XSD inference tools are available, but one that I like is called Trang. Trang is a command-line tool (available from www.thaiopensource.com/relaxng/ trang.html) that takes XML as input and produces an XSD file as output (see figure 9.2). Trang is Java based and thus can be used anywhere there’s a JVM. As the URL implies, Trang is useful for generating RELAX NG schemas (an alternative schema style), but is also useful for creating XML Schema files. For Spring-WS, we’ll be using Trang to generate XML Schema.
Once you’ve downloaded and unzipped Trang, you’ll find trang.jar in the distribution. This is an executable JAR file, so running Trang is simple from the command line:
% java -jar trang.jar EvaluateHandRequest.xml EvaluateHandResponse.xml PokerTypes.xsd
When running Trang, I’ve specified three command-line arguments. The first two are the sample message XML files that we created earlier. Because we’ve specified both message files, Trang is able to produce an XSD file that can validate the messages in both files. The last argument is the name of the file we want Trang to write the XSD to.
When run with these arguments, Trang will generate the data contract for our service in PokerTypes.xsd (listing 9.1).
Listing 9.1 PokerTypes.xsd, which defines the data contract for the web service
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="https://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace=
"https://www.springinaction.com/poker/schemas"
xmlns:schemas=
"https://www.springinaction.com/poker/schemas">
<xs:element name="EvaluateHandRequest">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded"
ref="schemas:card"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="card">
<xs:complexType>
<xs:sequence>
<xs:element ref="schemas:suit"/>
<xs:element ref="schemas:face"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="suit" type="xs:NCName"/>
<xs:element name="face" type="xs:NCName"/>
<xs:element name="EvaluateHandResponse">
<xs:complexType>
<xs:sequence>
<xs:element ref="schemas:handName"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="handName" type="xs:string"/>
</xs:schema>
Trang saved us a lot of trouble by inferring the XSD for our messages. We’re not completely off the hook, though. XSD isn’t perfect. As it infers the XSD, Trang makes some assumptions about what kind of data will be in your XML. Most of the time, those assumptions are okay. But often, we’ll need to fine-tune the generated XSD to be more precise.
For example, Trang assumed that the values of the <suit> and <face> elements should be defined as noncolonized1 names ( xs:NCName). What we actually want is for those elements to be simple strings (xs:string). So, let’s tweak the definitions of <suit> and <face> to be strings:
<xs:element name="suit" type="xs:string"/>
<xs:element name="face" type="xs:string"/>
- A noncolonized name is a name that isn’t qualified with a namespace related prefix. Therefore, it does not have a colon (:)—it isn’t “colonized.”
We also know that there are only four possible values for the <suit> element, so we could constrain the message a bit further:
<xs:element name="suit" type="schemas:Suit" />
<xs:simpleType name="Suit">
<xsd:restriction base="xs:string">
<xsd:enumeration value="SPADES" />
<xsd:enumeration value="CLUBS" />
<xsd:enumeration value="HEARTS" />
<xsd:enumeration value="DIAMONDS" />
</xsd:restriction>
</xs:simpleType>
Likewise, there are only 13 legal values for the <face> element, so let’s define those limits in XSD:
<xs:element name="face" type="schemas:Face" />
<xs:simpleType name="Face">
<xsd:restriction base="xs:string">
<xsd:enumeration value="ACE" />
<xsd:enumeration value="TWO" />
<xsd:enumeration value="THREE" />
<xsd:enumeration value="FOUR" />
<xsd:enumeration value="FIVE" />
<xsd:enumeration value="SIX" />
<xsd:enumeration value="SEVEN" />
<xsd:enumeration value="EIGHT" />
<xsd:enumeration value="NINE" />
<xsd:enumeration value="TEN" />
<xsd:enumeration value="JACK" />
<xsd:enumeration value="QUEEN" />
<xsd:enumeration value="KING" />
</xsd:restriction>
</xs:simpleType>
Also, notice that Trang incorrectly assumes that the <EvaluateHandRequest> may contain an unlimited number of <card> elements (maxOccurs="unbounded"). But a poker hand contains exactly five cards. Therefore, we’ll need to adjust the definition of <EvaluateHandRequest> accordingly:
<xs:element name="EvaluateHandRequest">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="5" maxOccurs="5"
ref="schemas:card"/>
</xs:sequence>
</xs:complexType>
</xs:element>
As for <EvaluateHandResponse>, it’s fine as is. We could constrain the possible values returned in the <handName> element, but it’s not necessary. So, we’ll leave it unchanged.
Now we have the data contract for the poker hand evaluation service, but what about the operational contract? Aren’t we going to need some WSDL to completely define the web service?
Yes, we’ll absolutely need WSDL—after all, WSDL is the standard for defining web services. We could write the WSDL by hand, but that’s no fun. And, again, I promised you that this would be easy. But I’m going to have to ask you to wait awhile to see where the operational contract comes into play. I’ll show you how the WSDL gets created in section 9.4.6 when we wire a WSDL definition bean in Spring.
But first, we need to create a service endpoint. The contract only defines the messages sent to and from the service, not how they’re handled. Let’s see how to create message endpoints in Spring-WS that will process messages from a web service client.
Handling messages with service endpoints
As you’ll recall from the opening of this chapter, a well-designed application doesn’t allow direct access to the internal objects that do the fine-grained tasks of a system. In Spring MVC, for example, a user interacts with the application through controllers, which in turn translate the user’s requests into calls to internal objects.
It may be helpful to know that Spring MVC and Spring-WS are a lot alike. Whereas a user interacts with a Spring MVC application through one of several controllers, a web service client interacts with a Spring-WS application through one of several message endpoints.
Figure 9.3 illustrates how message endpoints interact with their client. A message endpoint is a class that receives an XML message from the client and, based on the content of the message, makes calls to internal application objects to perform the actual work. For the poker hand evaluation service, the message endpoint will process <EvaluateHandRequest> messages.
Once the endpoint has completed processing, it will return its response in yet another XML message. In the case of the poker hand evaluation service, the response XML is an <EvaluateHandResponse> document.
Spring-WS defines several abstract classes from which message endpoints can be created, as listed in table 9.2.
For the most part, all of the abstract endpoint classes in table 9.2 are similar. Which one you choose is mostly a matter of taste and which XML parsing technology you prefer (e.g., SAX versus DOM versus StAX, etc.). But AbstractMarshallingPayloadEndpoint is a bit different from the rest of the pack in that it supports automatic marshaling and unmarshaling of XML messages to and from Java objects.
Table 9.2 The message endpoint options available with Spring-WS.
Abstract endpoint class in package org.springframework.ws.server.endpoint | |
---|---|
AbstractDom4jPayloadEndpoint | Endpoint that handles message payloads as dom4j Elements |
AbstractDomPayloadEndpoint | Endpoint that handles message payloads as DOM Elements |
AbstractJDomPayloadEndpoint | Endpoint that handles message payloads as JDOM Elements |
AbstractMarshallingPayloadEndpoint | Endpoint that unmarshals the request payload into an object and marshals the response object into XML |
AbstractSaxPayloadEndpoint | Endpoint that handles message payloads through a SAX ContentHandler implementation |
AbstractStaxEventPayloadEndpoint | Endpoint that handles message payloads using event-based StAX |
AbstractStaxStreamPayloadEndpoint | Endpoint that handles message payloads using streaming StAX |
AbstractXomPayloadEndpoint | Endpoint that handles message payloads as XOM Elements |
We’ll have a look at AbstractMarshallingPayloadEndpoint a little later in this chapter (in section 9.3.2). First, though, let’s see how to build an endpoint that processes XML messages directly.
Building a JDOM-based message endpoint
Our poker hand evaluation web service takes an <EvaluateHandRequest> message as input and produces an <EvaluateHandResponse> as output. Therefore, we’ll need to create a service endpoint that processes an <EvaluateHandRequest> element and produces an <EvaluateHandResponse> element.
Any of the abstract endpoint classes in table 9.2 will do, but we’ve chosen to base our endpoint on AbstractJDomPayloadEndpoint. This choice was mostly arbitrary, but I also like JDOM’s XPath support, which is a simple way to extract information out of a JDOM Element. (For more information on JDOM, visit the JDOM homepage at https://www.jdom.org.)
EvaluateHandJDomEndpoint (listing 9.2) extends AbstractJDomPayloadEndpoint to provide the functionality required to process the <EvaluateHandRequest> message.
Listing 9.2 An endpoint that will process the <EvaluateHandRequest>message
package com.springinaction.poker.webservice;
import java.util.Iterator;
import java.util.List;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.xpath.XPath;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.ws.server.endpoint.AbstractJDomPayloadEndpoint;
import com.springinaction.poker.Card;
import com.springinaction.poker.Face;
import com.springinaction.poker.PokerHand;
import com.springinaction.poker.PokerHandEvaluator;
import com.springinaction.poker.PokerHandType;
import com.springinaction.poker.Suit;
public class EvaluateHandJDomEndpoint
extends AbstractJDomPayloadEndpoint
implements InitializingBean {
private Namespace namespace;
private XPath cardsXPath;
private XPath suitXPath;
private XPath faceXPath;
protected Element invokeInternal(Element element)
throws Exception {
Card cards[] = extractCardsFromRequest(element);
PokerHand pokerHand = new PokerHand();
pokerHand.setCards(cards);
PokerHandType handType =
pokerHandEvaluator.evaluateHand(pokerHand);
return createResponse(handType);
}
private Element createResponse(PokerHandType handType) {
Element responseElement =
new Element("EvaluateHandResponse", namespace);
responseElement.addContent(
new Element("handName", namespace).setText(
handType.toString()));
return responseElement;
}
private Card[] extractCardsFromRequest(Element element)
throws JDOMException {
Card[] cards = new Card[5];
List cardElements = cardsXPath.selectNodes(element);
for(int i=0; i < cardElements.size(); i++) {
Element cardElement = (Element) cardElements.get(i);
Suit suit = Suit.valueOf(
suitXPath.valueOf(cardElement));
Face face = Face.valueOf(
faceXPath.valueOf(cardElement));
cards[i] = new Card();
cards[i].setFace(face);
cards[i].setSuit(suit);
}
return cards;
}
public void afterPropertiesSet() throws Exception {
namespace = Namespace.getNamespace("poker",
"https://www.springinaction.com/poker/schemas");
cardsXPath =
XPath.newInstance("/poker:EvaluateHandRequest/poker.card");
cardsXPath.addNamespace(namespace);
faceXPath = XPath.newInstance("poker:face");
faceXPath.addNamespace(namespace);
suitXPath = XPath.newInstance("poker:suit");
suitXPath.addNamespace(namespace);
}
// injected
private PokerHandEvaluator pokerHandEvaluator;
public void setPokerHandEvaluator(
PokerHandEvaluator pokerHandEvaluator) {
this.pokerHandEvaluator = pokerHandEvaluator;
}
}private PokerHandEvaluator pokerHandEvaluator;
public void setPokerHandEvaluator(
PokerHandEvaluator pokerHandEvaluator) {
this.pokerHandEvaluator = pokerHandEvaluator;
}
}
The invokeInternal() method is the entry point into this endpoint. When called, it is passed a JDOM Element object that contains the incoming message—in this case, an <EvaluateHandRequest>. invokeInternal() hands off the Element to the extractCardsFromRequest() method, which uses JDOM XPath objects to pull card information out of the <EvaluateHandRequest> element.
After an array of Card objects is returned, invokeInternal() then does the right thing and passes those Cards to an injected PokerHandEvaluator to evaluate the poker hand. PokerHandEvaluator is defined by the following interface:
package com.springinaction.poker;
public interface PokerHandEvaluator {
PokerHandType evaluateHand(PokerHand hand);
}
The actual implementation of PokerHandEvaluator isn’t relevant to the discussion of building web services with Spring-WS, so I’ll leave it out (but you can find it in the downloadable examples).
The fact that the endpoint calls PokerHandEvaluator’s evaluateHand() method is significant. A properly written Spring-WS endpoint shouldn’t perform any business logic of its own. It should only mediate between the client and the internal API. The actual business logic is performed in the PokerHandEvaluator implementation. Later, in chapter 13, we’ll see a similar pattern applied to Spring MVC controllers where a controller merely sits between a web user and a serverside object.
Once the PokerHandEvaluator has determined the type of poker hand it was given, invokeInternal() passes the PokerHandType object off to createResponse() to produce an <EvaluateHandResponse> element using JDOM. The resulting JDOM Element is returned and EvaluateHandJDomEndpoint’s job is done.
EvaluateHandJDomEndpoint is a fine example of how to implement a SpringWS endpoint. But there are an awful lot of XML specifics in there. Although the messages handled by Spring-WS endpoints are XML, there’s usually no reason why your endpoint needs to be written to know that. Let’s see how a marshaling endpoint can help us eliminate all of that XML parsing code.
Marshaling message payloads
As we mentioned before, AbstractMarshallingPayloadEndpoint is a little different from all of the other Spring-WS abstract endpoint classes. Instead of being given an XML Element to pull apart for information, AbstractMarshallingPayloadEndpoint is given an object to process.
Actually, as illustrated in figure 9.4, a marshaling endpoint works with an unmarshaler that converts an incoming XML message into a POJO. Once the endpoint is finished, it simply returns a POJO and a marshaler converts it into an XML message to be returned to the client. This greatly simplifies the endpoint implementation, as it no longer has to include any XML-processing code.
For example, consider listing 9.3, which shows EvaluateHandMarshallingEndpoint, a new implementation of our poker hand evaluation endpoint that extends AbstractMarshallingPayloadEndpoint.
Listing 9.3 The endpoint that will process the '''<EvaluateHandRequest> '''message
package com.springinaction.poker.webservice;
import org.springframework.ws.server.endpoint.
? AbstractMarshallingPayloadEndpoint;
import com.springinaction.poker.PokerHand;
import com.springinaction.poker.PokerHandEvaluator;
import com.springinaction.poker.PokerHandType;
public class EvaluateHandMarshallingEndpoint
extends AbstractMarshallingPayloadEndpoint {
protected Object invokeInternal(Object object)
throws Exception {
EvaluateHandRequest request =
(EvaluateHandRequest) object;
PokerHand pokerHand = new PokerHand();
pokerHand.setCards(request.getHand());
PokerHandType pokerHandType =
pokerHandEvaluator.evaluateHand(pokerHand);
return new EvaluateHandResponse(pokerHandType);
}
// injected
private PokerHandEvaluator pokerHandEvaluator;
public void setPokerHandEvaluator(
PokerHandEvaluator pokerHandEvaluator) {
this.pokerHandEvaluator = pokerHandEvaluator;
}
}
The first thing that you probably noticed about EvaluateHandMarshallingEndpoint is that it is much shorter than EvaluateHandJDomEndpoint. That’s because EvaluateHandMarshallingEndpoint doesn’t have any of the XML parsing code that was necessary in EvaluateHandJDomEndpoint.
Instead, the invokeInternal() method is given an Object to process. In this case, the Object is an EvaluateHandRequest:
package com.springinaction.poker.webservice;
import com.springinaction.poker.Card;
public class EvaluateHandRequest {
private Card[] hand;
public EvaluateHandRequest() {}
public Card[] getHand() {
return hand;
}
public void setHand(Card[] cards) {
this.hand = cards;
}
}
On the other end of invokeInternal(), an EvaluateHandResponse object is returned. EvaluateHandResponse looks like this:
package com.springinaction.poker.webservice;
import com.springinaction.poker.PokerHandType;
public class EvaluateHandResponse {
private PokerHandType pokerHand;
public EvaluateHandResponse() {
this(PokerHandType.NONE);
}
public EvaluateHandResponse(PokerHandType pokerHand) {
this.pokerHand = pokerHand;
}
public PokerHandType getPokerHand() {
return this.pokerHand;
}
public void setPokerHand(PokerHandType pokerHand) {
this.pokerHand = pokerHand;
}
}
So how is an incoming <EvaluateHandRequest> XML message transformed into an EvaluateHandRequest object? And, while we’re on the subject, how does an EvaluateHandResponse object end up being an <EvaluateHandResponse> message that gets sent to the client?
What you don’t see here is that AbstractMarshallingPayloadEndpoint has a reference to an XML marshaler. When it receives an XML message, it uses the marshaler to turn the XML message into an object before calling invokeInternal(). Then, when invokeInternal() is finished, the marshaler turns the object returned into an XML message.
A large part of Spring-WS is an object-XML mapping (OXM) abstraction. Spring-WS’s OXM comes with support for several OXM implementations, including:
- JAXB (versions 1 and 2)
- Castor XML
- JiBX
- XMLBeans
- XStream
You may be wondering which OXM I chose for EvaluateHandMarshallingEndpoint. I’ll tell you, but not yet. The important thing to note here is that EvaluateHandMarshallingEndpoint has no idea where the Object that is passed to invokeInternal() came from. In fact, there’s no reason why the Object even has to have been created from unmarshaled XML.
This highlights a key benefit of using a marshaling endpoint. Because it takes a simple object as a parameter, EvaluateHandMarshallingEndpoint can be unittested just like any other POJO. The test case can simply pass in an EvaluateHandRequest object and make assertions on the returned EvaluateHandResponse.
Now that we’ve written our service endpoint, we’re ready to wire it up in Spring.
Wiring it all together
We’re finally down to the final stage of developing a Spring-WS service. We need to configure the Spring application context with our endpoint bean and a handful of infrastructure beans required by Spring-WS.
Spring-WS is based on Spring MVC (which we’ll see more of in chapter 13). In Spring MVC, all requests are handled by DispatcherServlet, a special servlet that dispatches requests to controller classes that process the requests. Similarly, Spring-WS can be fronted by MessageDispatcherServlet, a subclass of DispatcherServlet that knows how to dispatch SOAP requests to Spring-WS endpoints.
- The “web” in web services seems to imply that all web services are served over HTTP. But that’s not necessarily true. Spring-WS has support for JMS, email, and raw TCP/IP-based web services. Nevertheless, since most web services are, in fact, served over HTTP, that’s the configuration I’ll talk about here.
MessageDispatcherServlet is a fairly simple servlet and can be configured in a web application’s web.xml with the following <servlet> and <servletmapping> elements:
<servlet>
<servlet-name>poker</servlet-name>
<servlet-class>org.springframework.ws.transport.http.
MessageDispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>poker</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
We’ll tweak this MessageDispatcherServlet’s configuration a little later, but this will get us started for now.
MessageDispatcherServlet is only the front end of Spring-WS. There are a handful of beans that we’ll need to wire in the Spring application context. Let’s see what those beans are and what they do.
Spring-WS: The big picture
Over the next several pages, we’re going to configure several beans in the Spring context. Before we get too deep in the XML, it is probably worthwhile to have a look at the big picture to see what we’re about to do. Figure 9.5 shows the beans we’ll define and how they relate to one another.
Figure 9.5 shows the beans that we’ll configure for the poker hand evaluation service and how they relate to one another. But what are these beans and what do they do? To summarize these six beans:
- payloadMapping—Maps incoming XML messages to an appropriate endpoint. In this case, we’ll use a mapping that looks up endpoints using the incoming XML’s root element (by its qualified name).
- evaluateHandEndpoint—This is the endpoint that will process the incoming XML message for the poker hand evaluation service.
- marshaller—The evaluateHandEndpoint could be written to process the incoming XML as a DOM or JDOM element, or even as a SAX event handler. Instead, the marshaller bean will automatically convert XML to and from Java objects.
- pokerHandEvaluator—This is a POJO that performs the actual poker hand processing. evaluateHandEndpoint will use this bean to do its work.
- endpointExceptionResolver—This is a Spring-WS bean that will automatically convert any Java exceptions thrown while processing a request into appropriate SOAP faults.
- poker—Although it’s not obvious from its name, this bean will serve the WSDL for the poker hand web service to the client. Either it can serve handcreated WSDL or it can be wired to automatically generate WSDL from the message’s XML Schema.
Now that we have a roadmap of where we’re going, let’s dive right into configuring Spring-WS, starting with the message handler adapter.
Mapping messages to endpoints
When a client sends a message, how does MessageDispatcherServlet know which endpoint should process it? Even though we’re only building one endpoint in this chapter’s example (the evaluate hand endpoint), it’s quite possible that MessageDispatcherServlet could be configured with several endpoints. We need a way to map incoming messages to the endpoints that process them.
In chapter 13, we’ll see how Spring MVC’s DispatcherServlet maps browser requests to Spring MVC controllers using handler mappings. In a similar way, MessageDispatcherServlet uses an endpoint mapping to decide which endpoint should receive an incoming XML message.
For the poker hand evaluation service, we’ll use Spring-WS’s PayloadRootQNameEndpointMapping, which is configured in Spring like this:
<bean id="payloadMapping"
class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<property name="endpointMap">
<map>
<entry key=
"{https://www.springinaction.com/poker/schemas}EvaluateHandRequest"
value-ref="evaluateHandEndpoint" />
</map>
</property>
</bean>
PayloadRootQNameEndpointMapping maps incoming SOAP messages to endpoints by examining the qualified name (QName) of the message’s payload and looking up the endpoint from its list of mappings (configured through the endpointMap property).
In our example, the root element of the message is <EvaluateHandRequest> with a namespace URI of https://www.springinaction.com/poker/schemas. This makes the QName of the message {https://www.springinaction.com/poker/schemas}EvaluateHandRequest. We’ve mapped this QName to a bean named evaluateHandEndpoint, which is our endpoint implementation that we created in section 9.3.2.
Wiring the service endpoint
Now we’re finally down to wiring the endpoint that will process our message. If you chose to use the JDOM-based endpoint then it is configured in Spring like this:
<bean id="evaluateHandEndpoint"
class="com.springinaction.poker.webservice.EvaluateHandJDomEndpoint">
<property name="pokerHandEvaluator"
ref="pokerHandEvaluator" />
</bean>
The only property that must be injected is the pokerHandEvaluator property. Remember that EvaluateHandJDomEndpoint doesn’t actually evaluate the poker hand, but delegates to an implementation of PokerHandEvaluator to do the dirty work. Thus, the pokerHandEvaluator bean should be configured like this:
<bean id="pokerHandEvaluator"
class="com.springinaction.poker.PokerHandEvaluatorImpl"/>
If the JDOM-based endpoint didn’t suit you and instead you chose to use EvaluateHandMarshallingEndpoint, a bit of extra configuration is involved:
<bean id="evaluateHandEndpoint"
class="com.springinaction.poker.webservice.EvaluateHandMarshallingEndpoint">
<property name="marshaller" ref="marshaller" />
<property name="unmarshaller" ref="marshaller" />
<property name="pokerHandEvaluator"
ref="pokerHandEvaluator" />
</bean>
Again, the pokerHandEvaluator property is injected with a reference to a PokerHandEvaluatorImpl. But the marshaling endpoint must have its marshaller and unmarshaller properties set as well. Here we’ve wired them with references to the same marshaller bean, which we’ll configure next.
Configuring a message marshaler
The key to translating objects to and from XML messages is object-XML mapping (OXM). Spring-OXM is a subproject of Spring-WS that provides an abstraction layer over several popular OXM solutions, including JAXB and Castor XML.
The central elements of Spring-OXM are its Marshaller and Unmarshaller interfaces. Implementations of Marshaller are expected to generate XML elements from Java objects. Conversely, Unmarshaller implementations are used to construct Java objects from XML elements.
AbstractMarshallingPayloadEndpoint takes advantage of the Spring-OXM marshalers and unmarshalers when processing messages. When AbstractMarshallingPayloadEndpoint receives a message, it hands it off to an Unmarshaller to unmarshal the XML message into an object that is passed to invokeInternal(). Then, when invokeInternal() is finished, the object returned is given to a Marshaller to marshal the object into XML that will be returned to the client.
Fortunately, you won’t have to create your own implementations of Marshaller and Unmarshaller. Spring-OXM comes with several implementations, as listed in table 9.3.
Table 9.3 Marshalers transform objects to and from XML. Spring-OXM provides several marshaling options that can be used with Spring-WS.
OXM solution | |
---|---|
Castor XML | org.springframework.oxm.castor.CastorMarshaller |
JAXB v1 | org.springframework.oxm.jaxb.Jaxb1Marshaller |
JAXB v2 | org.springframework.oxm.jaxb.Jaxb2Marshaller |
JiBX | org.springframework.oxm.jibx.JibxMarshaller |
XMLBeans | org.springframework.oxm.xmlbeans.XmlBeansMarshaller |
XStream | org.springframework.oxm.xstream.XStreamMarshaller |
As you can see, table 9.3 only lists marshaler classes. That’s not an oversight, though. Conveniently, all of the marshaler classes in table 9.3 implement both the Marshaller and the Unmarshaller interfaces to provide one-stop solutions for OXM marshaling.
The choice of OXM solution is largely a matter of taste. Each of the OXM options offered by Spring-WS has its good and bad points. XStream, however, has limited support for XML namespaces, which are necessary in defining the types for web services. Therefore, while Spring-OXM’s XStream may prove useful for general-purpose XML serialization, it should be disregarded for use with web services.
For the poker hand evaluator service, we chose to use Castor XML. Therefore, we’ll need to configure a CastorMarshaller in Spring:
<bean id="marshaller"
class="org.springframework.oxm.castor.CastorMarshaller">
<property name="mappingLocation"
value="classpath:mapping.xml" />
</bean>
Castor XML can do some basic XML marshaling without any additional configuration. But our OXM needs are a bit more complex than what default Castor XML can handle. Consequently, we’ll need to configure CastorMarshaller to use a Castor XML mapping file. The mappingLocation property specifies the location of a Castor XML mapping file. Here we’ve configured mappingLocation to look for a mapping file with the name mapping.xml in the root of the application’s classpath.
As for the mapping.xml file itself, it is shown in listing 9.4.
Listing 9.4 Castor XML mapping file for poker hand service types
<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC
"-//EXOLAB/Castor Object Mapping DTD Version 1.0//EN"
"https://castor.exolab.org/mapping.dtd">
<mapping xmlns="https://castor.exolab.org/">
<class name="com.springinaction.poker.webservice.EvaluateHandRequest">
<map-to xml="EvaluateHandRequest" />
<field name="hand"
collection="array"
type="com.springinaction.poker.Card"
required="true">
<bind-xml name="card" node="element" />
</field>
</class>
<class name="com.springinaction.poker.Card">
<map-to xml="card" />
<field name="suit"
type="com.springinaction.poker.Suit"
required="true">
<bind-xml name="suit" node="element" />
</field>
<field name="face"
type="com.springinaction.poker.Face"
required="true">
<bind-xml name="face" node="element" />
</field>
</class>
<class name="com.springinaction.poker.webservice.EvaluateHandResponse">
<map-to xml="EvaluateHandResponse"
ns-uri=
"https://www.springinaction.com/poker/schemas"
ns-prefix="tns" />
<field name="pokerHand"
type="com.springinaction.poker.PokerHandType"
required="true">
<bind-xml name="tns:handName" node="element"
QName-prefix="tns"
xmlns:tns=
"https://www.springinaction.com/poker/schemas"/>
</field>
</class>
</mapping>
Now we have configured an endpoint mapping bean, the endpoint implementation bean, and an XML marshaling bean. At this point the poker hand evaluator web service is mostly done. We could deploy it and stop for the day. But there are still a couple of beans left that will make the web service more complete. Let’s see how to make our web service more robust by declaring a bean that maps Java exceptions to SOAP faults.
Handling endpoint exceptions
Things don’t always work out as expected. What will happen if a message can’t be marshaled to a Java object? What if the message isn’t even valid XML? Maybe the service endpoint or one of its dependencies throws an exception—then what should we do?
If an exception is thrown in the course of processing a message, a SOAP fault will need to be sent back to the client. Unfortunately, SOAP doesn’t know or care anything about Java exceptions. SOAP-based web services communicate failure using SOAP faults. We need a way to convert any Java exceptions thrown by our web service or by Spring-WS into SOAP faults.
For that purpose, Spring-WS provides SoapFaultMappingExceptionResolver. As shown in figure 9.6, SoapFaultMappingExceptionResolver will handle any uncaught exceptions that occur in the course of handling a message and produce an appropriate SOAP fault that will be sent back to the client.
For our service, we’ve configured a SoapFaultMappingExceptionResolver in Spring that looks like this:
<bean id="endpointExceptionResolver"
class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.springframework.oxm.UnmarshallingFailureException">
SENDER,Invalid message received</prop>
<prop key="org.springframework.oxm.ValidationFailureException">
SENDER,Invalid message received</prop>
</props>
</property>
<property name="defaultFault"
value="RECEIVER,Server error" />
</bean>
The exceptionMappings property is configured with one or more SOAP fault definitions mapped to Java exceptions. The key of each <prop> is a Java exception that needs to be translated to a SOAP fault. The value of the <prop> is a two-part value where the first part is the type of fault that is to be created and the second part is a string that describes the fault.
SOAP faults come in two types: sender and receiver faults. Sender faults typically indicate that the problem is on the client (e.g., the sender) side. Receiver faults indicate that the web service (e.g., the receiver) received a message from the client but is having some problem processing the message.
For example, if a service receives an XML message that can’t be unmarshaled, the marshaler will throw an org.springframework.oxm.UnmarshallingFailureException. Because the sender created the useless XML, this is a sender fault. As for the message, it is simply set to “Invalid message received” to indicate the nature of the problem.
An org.springframework.oxm.ValidationFailureException is handled the same way.Any exceptions not explicitly mapped in the exceptionMappings property will be handled by the fault definition in the defaultFault property. In this case, we’re assuming that if the exception thrown doesn’t match any of the mapped exceptions, it must be a problem on the receiving side. Thus, it is a receiver fault and the message simply states “Server error.”
Serving WSDL files
Finally, I’m going to make good on my promise to show you where the WSDL file for the poker hand evaluation web service comes from. As you recall from section 9.2.1, we’ve already created the data portion of the contract as XML Schema in PokerTypes.xsd. Before we go any further, you may want to turn back to listing 9.1 to review the details of the data service.
Pay particular attention to the names I chose for the XML elements that make up our web service messages: EvaluateHandRequest and EvaluateHandResponse. These names weren’t chosen arbitrarily. I chose them purposefully to take advantage of a convention-over-configuration feature in Spring-WS that will automatically create WSDL for the poker hand evaluation service.
To make this work, we’ll need to configure Spring-WS’s DynamicWsdl11Definition. DynamicWsdl11Definition is a special bean that MessageDispatcherServlet works with to generate WSDL from XML Schema. This will come in handy, as we already have some XML Schema defined for the data portion of the contract. Here’s how I’ve configured DynamicWsdl11Definition in Spring:
<bean id="poker"
class="org.springframework.ws.wsdl.wsdl11.
DynamicWsdl11Definition">
<property name="builder">
<bean class="org.springframework.ws.wsdl.wsdl11.builder.
XsdBasedSoap11Wsdl4jDefinitionBuilder">
<property name="schema" value="/PokerTypes.xsd"/>
<property name="portTypeName" value="Poker"/>
<property name="locationUri"
value="https://localhost:8080/Poker-WS/services"/>
</bean>
</property>
</bean>
DynamicWsdl11Definition works by reading an XML Schema definition, specified here as PokerTypes.xsd by the schema property. It looks through the schema file for any element definitions whose names end with Request and Response. It assumes that those suffixes indicate a message that is to be sent to or from a web service operation and creates a corresponding <wsdl:operation> element in the WSDL it produces, as shown in figure 9.7.
For example, when DynamicWsdl11Definition processes the PokerTypes.xsd file, it assumes that the EvaluateHandRequest and EvaluateHandResponse elements are input and output messages for an operation called EvaluateHand. Consequently, the following definition is placed in the generated WSDL:
<wsdl:portType name="Poker">
<wsdl:operation name="EvaluateHand">
<wsdl:input message="schema:EvaluateHandRequest"
name="EvaluateHandRequest">
</wsdl:input>
<wsdl:output message="schema:EvaluateHandResponse"
name="EvaluateHandResponse">
</wsdl:output>
</wsdl:operation>
</wsdl:portType>
Notice that DynamicWsdl11Definition placed the EvaluateHand <wsdl:operation> within a <wsdl:portType> with the name Poker. It named the <wsdl:portType> using the value wired into its portTypeName property.
The last of DynamicWsdl11Definition’s properties that we’ve configured is locationUri. This property tells the client where the service can be found. The diagram in figure 9.8 breaks down the URL configured in the locationUri property.
In this case, I’m assuming that it will be running on the local machine, but you’ll want to change the URL if you’ll be running it on a different machine. Notice that the URL ends with /services, which matches the <servlet-mapping> that we created for MessageDispatcherServlet.
Speaking of <servlet-mapping>s, we’ll also need to add a new <servlet-mapping> '''''to web.xml so that '''''MessageDispatcherServlet '''''will serve WSDL definitions. The following '''''<nowiki><servlet-mapping> definition should do the trick:
<servlet-mapping>
<servlet-name>poker</servlet-name>
<url-pattern>*.wsdl</url-pattern>
</servlet-mapping>
Now MessageDispatcherServlet has been configured (through DynamicWsdl11Definition) to automatically produce WSDL for the poker hand evaluation service. The only question left unanswered at this point is where to find the generated WSDL.
The generated WSDL can be found at https://localhost:8080/Poker-WS/poker.wsdl. How did I know that? I know that MessageDispatcherServlet is mapped to *.wsdl, so it will attempt to create WSDL for any request that matches that pattern. But how did it know to produce WSDL for our poker service at poker.wsdl?
The answer to that question lies in one last bit of convention followed by MessageDispatcherServlet. Notice that I declared the DynamicWsdl11Definition bean to have an ID of poker. When MessageDispatcherServlet receives a request for /poker.wsdl, it will look in the Spring context for a WSDL definition bean named poker. In this case, it will find the DynamicWsdl11Definition bean that I configured.
Using predefined WSDL
DynamicWsdl11Definition is perfect for most situations, as it keeps you from having to write the WSDL by hand. But you may have special circumstances that require you to have more control over what goes into the service’s WSDL definition. In that case you’ll need to create the WSDL yourself and then wire it into Spring using SimpleWsdl11Definition:
<bean id="poker"
class="org.springframework.ws.
wsdl.wsdl11.SimpleWsdl11Definition">
<property name="wsdl"
value="/PokerService.wsdl"/>
</bean>
SimpleWsdl11Definition doesn’t generate WSDL (see figure 9.9); it just serves WSDL that you’ve provided through the wsdl property.
The only problem with predefined WSDL (aside from the trouble that it takes to create it) is that it is statically defined. This creates a problem for the part of the WSDL that specifies the service’s location. For example, consider the following (statically defined) chunk of WSDL:
<wsdl:service name="PokerService">
<wsdl:port binding="tns:PokerBinding" name="PokerPort">
<wsdlsoap:address
location="https://localhost:8080/Poker-WS/services"/>
</wsdl:port>
</wsdl:service>
Here the service is defined as being available at [1] WS/services. That is probably okay for development purposes, but it will need to be changed when you deploy it to another server. You could manually change the WSDL file every time you deploy it to another server, but that’s cumbersome and is susceptible to mistakes.
But MessageDispatcherServlet knows where it’s deployed and it knows the URL of requests used to access it. So, instead of tweaking the WSDL every time you deploy it to another server, why not let MessageDispatcherServlet rewrite it for you?
All you need to do is to set an <init-param> named transformWsdlLocations to true and MessageDispatcherServlet will take it from there:
<servlet>
<servlet-name>poker</servlet-name>
<servlet-class>org.springframework.ws.transport.http.
MessageDispatcherServlet</servlet-class>
<init-param>
<param-name>transformWsdlLocations</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
When transformWsdlLocations
When transformWsdlLocations is set to true, MessageDispatcherServlet will rewrite the WSDL served by SimpleWsdl11Definition to match the request’s URL.
Deploying the service
We’ve defined our contract, the endpoint has been written, and all of the SpringWS beans are in place. At this point, we’re ready to package up the web service application and deploy it. Since I chose to use Maven 2 for this project, creating a deployable WAR file is as simple as typing the following at the command line:
% mvn package deploy
Once Maven’s finished, there will be a Poker-WS.war file in the target directory, suitable for deployment in most web application servers.
Using Spring-WS to build a web service only demonstrates half of its capabilities. Spring-WS also comes with a client API based on the same message-centric paradigm that Spring-WS promotes on the service side. Let’s see how to build a client to consume the poker hand evaluation service using Spring-WS client templates.
Consuming Spring-WS web services
In chapter 8, you saw how to use JaxRpcPortProxyFactoryBean and XFireClientFactoryBean to build clients that communicate with remote web services. But both of those take a remote object view of web services, treating web services as remote objects whose methods can be invoked locally. Throughout this chapter, we’ve been talking about a message-centric approach to web services where clients send XML messages to a web service and receive XML messages back in response. A different paradigm on the service side demands a different paradigm on the client side as well. That’s where Spring-WS’s WebServiceTemplate comes in.
WebServiceTemplate is the centerpiece of Spring-WS’s client API. As shown in
figure 9.10, it employs the Template design pattern to provide the ability to send and receive XML messages from message-centric web services. We’ve already seen how Spring uses the Template pattern for its data access abstractions in chapter 5.
As we look at Spring-WS’s client API, you’ll find that it resembles the data access API in many ways.
To demonstrate WebServiceTemplate, we’ll create several different implementations of the PokerClient interface, which is defined as follows:
package com.springinaction.ws.client;
import java.io.IOException;
import com.springinaction.poker.Card;
import com.springinaction.poker.PokerHandType;
public interface PokerClient {
PokerHandType evaluateHand(Card[] cards)
throws IOException;
}
Each implementation will show a different way of using WebServiceTemplate to send messages to the poker hand evaluation web service.
But first things first… Let’s configure WebServiceTemplate as a bean in Spring.
Working with web service templates
As I’ve already mentioned, WebServiceTemplate is the central class in the SpringWS client API. Sending messages to a web service involves producing SOAP envelopes and communications boilerplate code that is pretty much the same for every web service client. When sending messages to a Spring-WS client, you’ll certainly want to rely on WebServiceTemplate to handle the grunt work so that you can focus your efforts on the business logic surrounding your client.
Configuring WebServiceTemplate in Spring is rather straightforward, as shown in this typical <bean> declaration:
<bean id="webServiceTemplate"
class="org.springframework.ws.client.core.WebServiceTemplate">
<property name="messageFactory">
<bean class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
</property>
<property name="messageSender" ref="messageSender"/>
</bean>
WebServiceTemplate needs to know how to construct the message that will be sent to the service and how to send the message. The object wired into the messageFactory property handles the task of constructing the message. It should be wired with an implementation of Spring-WS’s WebServiceMessageFactory interface. Fortunately, you won’t have to worry about implementing WebServiceMessageFactory, as Spring-WS comes with three suitable choices (shown in table 9.4).
Table 9.4 WebServiceTemplate relies on a message factory to construct the message sent to a web service. Spring-WS provides three message factory implementations to choose from.
Message factory | |
---|---|
AxiomSoapMessageFactory | Produces SOAP messages using the AXIs Object Model (AXIOM). Based on the StAX streaming XML API. Useful when working with large messages and performance is a problem. |
DomPoxMessageFactory | Produces Plain Old XML (POX) messages using a DOM. Use this message factory when neither the client nor the service cares to deal with SOAP. |
SaajSoapMessageFactory | Produces SOAP messages using the SOAP with Attachments API for Java (SAAJ). Because SAAJ uses a DOM, large messages could consume a lot of memory. If performance becomes an issue, consider using AxiomSoapMessageFactory instead. |
Since the messages sent to and from the poker hand evaluation service are rather simple, I’ve chosen to wire a SaajSoapMessageFactory into WebServiceTemplate’s messageFactory property. (This is also the default message factory used by MessageDispatcherServlet.) If I were to decide later that performance is an issue, switching to AXIOM-based messages would be a simple matter of rewiring the messageFactory property.
It’s not all SOAP
- Figure 9.10 is a bit misleading. It implies that Spring-WS only deals with SOAPbased web services. In fact, Spring-WS only uses SOAP if it is wired with an AxiomSoapMessageFactory or SaajSoapMessageFactory. The DomPoxMessageFactory supports POX messages that aren’t sent in a SOAP envelope. If you have an aversion to using SOAP, maybe DomPoxMessageFactory is for you. You may be interested in knowing that the upcoming Spring-WS 1.1 release will also include support for REST.
The messageSender property should be wired with a reference to an implementation of a WebServiceMessageSender. Again, Spring-WS provides a couple of appropriate implementations, as listed in table 9.5.
Table 9.5 Message senders send the messages to a web service. Spring-WS comes with two message senders.
Message sender | |
---|---|
CommonsHttpMessageSender | Sends the message using Jakarta Commons HTTP Client. Supports a preconfigured HTTP client, allowing advanced features such as HTTP authentication and HTTP connection pooling. |
HttpUrlConnectionMessageSender | Sends the message using Java’s basic facilities for HTTP connections. Provides limited functionality. |
The choice between CommonsHttpMessageSender and HttpUrlConnectionMessageSender boils down to a trade-off between functionality and another JAR dependency. If you won’t be needing the advanced features supported by CommonsHttpMessageSender (such as HTTP authentication), HttpUrlConnectionMessageSenderwillsuffice. But ifyou willneed those features then CommonsHttpMessageSender is a must—but you’ll have to be sure to include Jakarta Commons HTTP in your client’s classpath.
As the advanced features aren’t an issue for the poker hand evaluation web service, I’ve chosen HttpUrlConnectionMessageSender, which is configured like this in Spring:
<bean id="messageSender"
class="org.springframework.ws.transport.http.
HttpUrlConnectionMessageSender">
<property name="url"
value="https://localhost:8080/Poker-WS/services"/>
</bean>
The url property specifies the location of the service. Notice that it matches the URL in the service’s WSDL definition.
If I decide later that I’ll need to authenticate to use the poker hand evaluation web service, switching to CommonsHttpMessageSender is a simple matter of changing the messageSender bean’s class specification.
Sending a message
Once the WebServiceTemplate has been configured, it’s ready to use to send and receive XML to and from the poker hand evaluation service. WebServiceTemplate provides several methods for sending and receiving messages. This one, however, stands out as the most basic and easiest to understand:
public boolean sendAndReceive(Source requestPayload,
Result responseResult)
throws IOException
The sendAndReceive() method takes a java.xml.transform.Source and a java.xml.transform.Result as parameters. The Source object represents the message payload to send to the web service, while the Result object is to be populated with the message payload returned from the service.
Listing 9.5 shows TemplateBasedPokerClient, an implementation of the PokerClient interface that uses WebServiceTemplate’s sendAndReceive() method to communicate with the poker hand evaluation service.
Listing 9.5 Client that uses an injected WebServiceTemplate to send and receive XML messages from the poker hand evaluation service
package com.springinaction.ws.client;
import java.io.IOException;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom.transform.JDOMResult;
import org.jdom.transform.JDOMSource;
import org.springframework.ws.client.core.WebServiceTemplate;
import com.springinaction.poker.Card;
import com.springinaction.poker.PokerHandType;
public class TemplateBasedPokerClient
implements PokerClient {
public PokerHandType evaluateHand(Card[] cards)
throws IOException {
Element requestElement =
new Element("EvaluateHandRequest");
Namespace ns = Namespace.getNamespace(
"https://www.springinaction.com/poker/schemas");
requestElement.setNamespace(ns);
Document doc = new Document(requestElement);
for(int i=0; i<cards.length; i++) {
Element cardElement = new Element("card");
Element suitElement = new Element("suit");
suitElement.setText(cards[i].getSuit().toString());
Element faceElement = new Element("face");
faceElement.setText(cards[i].getFace().toString());
cardElement.addContent(suitElement);
cardElement.addContent(faceElement);
doc.getRootElement().addContent(cardElement);
}
JDOMSource requestSource = new JDOMSource(doc);
JDOMResult result = new JDOMResult();
webServiceTemplate.sendAndReceive(requestSource, result);
Document resultDocument = result.getDocument();
Element responseElement = resultDocument.getRootElement();
Element handNameElement =
responseElement.getChild("handName", ns);
return PokerHandType.valueOf(handNameElement.getText());
}
private WebServiceTemplate webServiceTemplate;
public void setWebServiceTemplate(
WebServiceTemplate webServiceTemplate) {
this.webServiceTemplate = webServiceTemplate;
}
}
Both Source and Result are interfaces that are a standard part of Java’s XML API and are available in the Java SDK. There are countless implementations of these interfaces to choose from, but as you can see in listing 9.5, I chose to use the JDOM implementations. This choice was mostly arbitrary but influenced by the fact that I am familiar with JDOM and know how to use it to construct XML messages.
TemplateBasedPokerClient’s evaluateHand() method starts by using JDOM to construct an <EvaluateHandRequest> message from the array of Card elements passed in. Once it has the request message, it calls sendAndReceive() on the WebServiceTemplate. It then uses JDOM to parse the result and find the PokerHandType that should be returned.
Notice that the WebServiceTemplate is injected through a setter method. Therefore, TemplateBasedPokerClient must be configured in Spring as follows:
<bean id="templateBasedClient"
class="com.springinaction.ws.client.TemplateBasedPokerClient">
<property name="webServiceTemplate" ref="webServiceTemplate" />
</bean>
The webServiceTemplate property is wired with a reference to the webServiceTemplate bean that we configured earlier.While reading through listing 9.5, you may have noticed that the bulk of the evaluateHand() method involves creating and parsing XML. In fact, only one line deals specifically with sending a message. Manually creating and parsing XML messages may be okay when the messages are very simple, but you can probably imagine the amount of code that would be required to construct complex message payloads. Even with the poker hand evaluation service, where the message payload is far from complex, the amount of XML processing code is staggering.
Fortunately, you don’t have to deal with all of that XML on your own. In section 9.4.4 you saw how an endpoint can use a marshaler to transform objects to and from XML. Now I’ll show you how WebServiceTemplate can also take advantage of marshalers to eliminate the need for XML processing code on the client side.
Using marshalers on the client side
In addition to the simple sendAndReceive() method we used in listing 9.5, WebServiceTemplate also provides marshalSendAndReceive(), a method for sending and receiving XML messages that are marshaled to and from Java objects.
Using marshalSendAndReceive() is a simple matter of passing in a request object as a parameter and receiving a response object as the returned value. In the case of the poker hand evaluation service, these objects are EvaluateHandRequest and EvaluateHandResponse, respectively.
Listing 9.6 shows MarshallingPokerClient, an implementation of PokerClient that uses marshalSendAndReceive() to communicate with the poker hand evaluation service.
Listing 9.6 MarshallingPokerClient, which takes advantage of a marshaler to convert objects to and from XML
package com.springinaction.ws.client;
import java.io.IOException;
import org.springframework.ws.client.core.WebServiceTemplate;
import com.springinaction.poker.Card;
import com.springinaction.poker.PokerHandType;
import com.springinaction.poker.webservice.EvaluateHandRequest;
import com.springinaction.poker.webservice.EvaluateHandResponse;
public class MarshallingPokerClient
implements PokerClient {
public PokerHandType evaluateHand(Card[] cards)
throws IOException {
EvaluateHandRequest request = new EvaluateHandRequest();
request.setHand(cards);
EvaluateHandResponse response = (EvaluateHandResponse)
webServiceTemplate.marshalSendAndReceive(request);
return response.getPokerHand();
}
private WebServiceTemplate webServiceTemplate;
public void setWebServiceTemplate(
WebServiceTemplate webServiceTemplate) {
this.webServiceTemplate = webServiceTemplate;
}
}
Wow! MarshallingPokerClient’s evaluateHand() method is much simpler and no longer involves any XML processing. Instead, it constructs an EvaluateHandRequest object and populates it with the Card array that was passed in. After calling marshalSendAndReceive(), passing in the EvaluateHandRequest object, evaluateHand() receives an EvaluateHandResponse, which it uses to retrieve the PokerHandType that it returns.
So, how does WebServiceTemplate know how to marshal/unmarshal EvaluateHandRequest and EvaluateHandResponse objects? Is it really that smart?
Well, no… not really. Actually, it doesn’t know anything about marshaling and unmarshaling those objects. However, as shown in figure 9.11, it can be wired with a marshaler and an unmarshaler that know how to handle the marshaling:
<bean id="webServiceTemplate"
class="org.springframework.ws.client.core.WebServiceTemplate">
<property name="messageFactory">
<bean class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
</property>
<property name="messageSender" ref="urlMessageSender"/>
<property name="marshaller" ref="marshaller" />
<property name="unmarshaller" ref="marshaller" />
</bean>
Here I’ve wired both the marshaller and unmarshaller properties with a reference to a marshaller bean, which is the same CastorMarshaller configured in section 9.4.4. But it could just as easily have been any of the marshalers listed in table 9.3.
MarshallingPokerClient is much cleaner than TemplateBasedPokerClient. But there’s still a little bit more we can do to trim the fat. Let’s see how to use Spring-WS’s WebServiceGatewaySupport class to eliminate the need to explicitly wire in a WebServiceTemplate.
Using web service gateway support
As you’ll recall from chapter 5 (see sections 5.3.3, 5.4.3, 5.5.3, and 5.6.2), Spring’s data access API includes convenient support classes that provide templates so that the templates themselves do not need to be configured. In a similar way, SpringWS provides WebServiceGatewaySupport, a convenient support class that automatically provides a WebServiceTemplate to client classes that subclass it.
Listing 9.7 shows one final implementation of PokerClient, PokerServiceGateway, that extends WebServiceGatewaySupport.
Listing 9.7 ''WebServiceGatewaySupport, which provides a WebServiceTemplate through getWebServiceTemplate()
package com.springinaction.ws.client;
import java.io.IOException;
import org.springframework.ws.client.core.support.
WebServiceGatewaySupport;
import com.springinaction.poker.Card;
import com.springinaction.poker.PokerHandType;
import com.springinaction.poker.webservice.EvaluateHandRequest;
import com.springinaction.poker.webservice.EvaluateHandResponse;
public class PokerServiceGateway
extends WebServiceGatewaySupport
implements PokerClient {
public PokerHandType evaluateHand(Card[] cards)
throws IOException {
EvaluateHandRequest request = new EvaluateHandRequest();
request.setHand(cards);
EvaluateHandResponse response = (EvaluateHandResponse)
getWebServiceTemplate().marshalSendAndReceive(request);
return response.getPokerHand();
}
}
As you can see, PokerServiceGateway isn’t much different from MarshallingPokerClient. The key difference is that PokerServiceGateway isn’t injected with a WebServiceTemplate. Instead, it gets its WebServiceTemplate by calling getWebServiceTemplate(). Under the covers, WebServiceGatewaySupport will create a WebServiceTemplate object without one being explicitly defined in Spring.
Even though WebServiceTemplate no longer needs to be defined in Spring, the details of how to create a WebServiceTemplate must still be configured through WebServiceGatewaySupport’s properties. For PokerServiceGateway, this means configuring the messageFactory, messageSender, marshaller, and unmarshaller properties:
<bean id="pokerClientGateway"
class="com.springinaction.ws.client.PokerServiceGateway">
<property name="messageFactory">
<bean class="org.springframework.ws.soap.saaj.
SaajSoapMessageFactory"/>
</property>
<property name="messageSender" ref="messageSender"/>
<property name="marshaller" ref="marshaller" />
<property name="unmarshaller" ref="marshaller" />
</bean>
Notice that the properties are configured exactly as they were with WebServiceTemplate.
Summary
Traditionally, web services have been viewed as just another remoting option. In fact, some developers lovingly refer to SOAP as “CORBA with XML.”The problem with the web services as remoting view is that it leads to tight coupling between a service and its clients. When treated as remoting, a client is bound to the service’s internal API. The contract with the client is a side effect of this binding. Changes to the service could break the contract with the client, requiring the client to change or requiring the service to be versioned.
In this chapter, we’ve looked at web services from a different angle, taking a message-centric view. This approach is known as contract-first web services, as it elevates the contract to be a first-class citizen of the service. Rather than simply being remote objects, contract-first web services are implemented as message endpoints that process messages sent by the client and defined by the contract. Consequently, the service and its API can be changed without impacting the contract.
Spring-WS is an exciting new web service framework that encourages contractfirst web services. Based on Spring MVC, Spring-WS endpoints handle XML messages sent from the client, producing responses that are also XML messages.
If you’re like me, you’re probably a bit skeptical about all of the work that went into configuring a web service in Spring-WS. I won’t deny that contract-first web services require a bit more work than using XFire to SOAP-ify a bean in contractlast style. In fact, when I first looked at Spring-WS, I initially dismissed it as too much work and no benefit… crazy talk.
But after some more thought, I realized that the benefits of decoupling the service’s contract from the application’s internal API far outweigh the extra effort required by Spring-WS. And that work will pay dividends in the long run as we are able to revise and refactor our application’s internal API without worrying about breaking the service’s contract with its clients.
Web services, especially those that are contract first, are a great way for applications to communicate with each other in a loosely coupled way. Another approach is to send messages using the Java Message Service (JMS). In the next chapter, we’ll explore Spring’s support for asynchronous messaging with JMS.
------------------------
ТРИО теплый пол отзыв
Заработок на сокращении ссылок
Earnings on reducing links
Код PHP на HTML сайты
Категория: Книги по Java
Комментарии |