Now that we’ve covered servlets and web applications in detail, we’d like to return to the topic of web services. In the previous chapter, we introduced the concept of a web service as an extension of the basic HTTP web transaction, using XML content for application-to-application communication instead of consumption by a web browser client. In that chapter, we showed how easy it is to invoke an RPC-style web service, by using client-side classes generated from a WSDL description file. In this section, we’ll show the other side of that equation and demonstrate how to implement and deploy a web service.
The world of web services has evolved quickly, as have the APIs, buzzwords, and hype. The appeal of this style of interapplication communication using simple web protocols has, to some extent, been tarnished by the design-by-committee approach of many standards bodies and competitors adding features and layers to the web services concept. The truth is that web services were originally simple and elegant when compared to more elaborate protocols, largely because they did not support all of the same semantics—state management, callbacks, transactions, authentication, and security. As these features are added, the complexity returned. We will not cover all aspects of web services in detail but instead focus on the basic RPC style that is appealing for a wide variety of simple applications.
In Chapter 14, we walked through
generating and running the client side of a web service (the weather
service). In this chapter, we’ll build and deploy our own web service, a
simple one that echoes parameters back to the client: EchoService
. We’ll be using the built in JAX-WS
APIs tools and services container to run this example, although you could
deploy the service to Tomcat as well with some additional configuration
and packaging into a WAR file.
To build our client-side API in Chapter 14, we began by downloading the WSDL description file for the (existing) weather service. The WSDL, again, is an XML file that describes the functions of the service and the types of arguments and return values they use. From this description, the wsimport command was able to generate the client-side classes that we needed to invoke the service remotely from Java.
In creating our own web service, we have (at least) two choices. We could follow an analogous process, writing a WSDL document describing our service and using it to generate the necessary server-side framework. The wsimport class that we used before can be used to generate the necessary, annotated service interface for us and we could implement it with our code. However, there is a much easier way: going code-first.
The wsgen command complements wsimport by adding the capability to read annotated Java classes and generate WSDL and related service classes for us. Even better, if we deploy our class using the built-in JAX-WS endpoint publisher, it will take care of generating all of this for us. This means that to test a simple web service, all we really have to do is write a service class that marks the class and service methods with the correct annotations and invoke the publisher. It really couldn’t get much easier.
We’ll create a simple service that echoes a few different
kinds of values: an int
, a String
, and one of our own object types (a
data holder object), MyObject
. In the
next section, we’ll examine the data types and how they are handled in
more detail. Here is the code:
package
learningjava
.
service
;
import
javax.jws.*
;
import
javax.xml.ws.Endpoint
;
@WebService
public
class
Echo
{
@WebMethod
public
int
echoInt
(
int
value
)
{
return
value
;
}
@WebMethod
public
String
echoString
(
String
value
)
{
return
value
;
}
@WebMethod
public
MyObject
echoMyObject
(
MyObject
value
)
{
return
value
;
}
public
static
void
main
(
String
[]
args
)
{
Endpoint
endpoint
=
Endpoint
.
publish
(
"http://localhost:8080/echo"
,
new
Echo
()
);
}
}
public
class
MyObject
{
int
intValue
;
String
stringValue
;
public
MyObject
()
{
}
public
MyObject
(
int
i
,
String
s
)
{
this
.
intValue
=
i
;
this
.
stringValue
=
s
;
}
public
int
getIntValue
()
{
return
intValue
;
}
public
void
setIntValue
(
int
intValue
)
{
this
.
intValue
=
intValue
;
}
public
String
getStringValue
()
{
return
stringValue
;
}
public
void
setStringValue
(
String
stringValue
)
{
this
.
stringValue
=
stringValue
;
}
}
We’ve named our {[QUOTE-REPLACEMENT]}echo” methods individually to
differentiate them because WSDL doesn’t really handle overloaded
methods. (If we’d had a name collision, JAX-WS would give us a runtime
warning and choose one for us.) We’ve placed these into a learningjava.service
package because it will
be easier to work with the tools that way. This package name will be
used in the default namespace and package name for
generated client code. We could override the default using the
targetNamespace
attribute of the WebService annotation (and it would probably be wise to
do so in order to keep your interface stable).
To deploy our web service, we use the JAX-WS Endpoint
class publish()
method. This method takes a URI
string that indicates the desired host, port, and service path as well
as an instance of our class. Obviously, the only host that will work in
this arrangement is our local computer, which can normally be accessed
by the name: “localhost.” Here, we ran the service on port 8080 under
the path “/echo”.
After running the service, drive your web browser to the
service URL to get a test page. If you are running the server on the
same machine, the URL should be the same as the URI you passed to the
publish()
method. However, under some
circumstances you may have to substitute “127.0.0.1” for
“localhost.”
http:
//localhost:8080/echo
http:
//127.0.0.1:8080/echo
You should see a description of the service similar to the one shown in Figure 15-1. This tells you that the service is active and gives you its configuration information. You can click on the WSDL link to view the WSDL description file that was generated for our service. The WSDL URL should be your base service URL with “?wsdl” appended.
We can use the WSDL to generate a client and test our service,
just as we did in Chapter 14. In the following
command, we’ve specified that the generated classes should go into a
separate package, learningjava.client.impl
, to avoid confusion
between the generated classes and our original. We’ve also used the
-keep
option to retain the source
code instead of just the compiled class files (you may want to look at
them). The final argument is the URL for our generated WSDL, which you
can copy from the test page as shown previously.
%
wsimport
-
p
learningjava
.
client
.
impl
-
keep
http:
//localhost:8080/echo?wsdl
Next, we’ll create a small client that uses these generated classes to test the service:
package
learningjava
.
client
;
import
learningjava.client.impl.*
;
public
class
EchoClient
{
public
static
void
main
(
String
[]
args
)
throws
java
.
rmi
.
RemoteException
{
Echo
service
=
new
EchoService
().
getEchoPort
();
int
i
=
service
.
echoInt
(
42
);
System
.
out
.
println
(
i
);
String
s
=
service
.
echoString
(
"Hello!"
);
System
.
out
.
println
(
s
);
MyObject
myObject
=
new
MyObject
();
myObject
.
setIntValue
(
42
);
myObject
.
setStringValue
(
"Foo!"
);
MyObject
myObj
=
service
.
echoMyObject
(
myObject
);
System
.
out
.
println
(
myObj
.
getStringValue
()
);
}
}
As you can infer from our code, wsimport has generated an EchoService
class that represents our service.
Service classes may contain multiple service groups, so in order to get
our Echo
interface, we ask for the
Echo “port” with getEchoPort()
.
(Port is WSDL terminology for a service
interface.)
Run the client, and it should bounce the values between the client and server and display them. And there we are! As we said in the introduction, the actual code required to implement and invoke our service is quite minimal and the fact that Java now bundles a simple web service container with the standard edition makes Java an ideal platform for working with web services.
As you might guess, because the data for our service has to be expressed as XML in a standard way, there are some limitations to the type of objects that can be transferred. JAX-WS and WSDL support most of the common Java data types and many standard classes directly. Actually, it would be more appropriate to say that JAXB—the Java XML binding API—supports these Java types, as JAX-WS uses JAXB for this aspect. We’ll talk more about Java XML data binding and XML Schemas in Chapter 24.
JAX-WS and JAXB can also decompose JavaBeans-compliant data
classes composed of these standard types so that you can use your own
classes, as we saw with the MyObject
argument in our Echo
service.
Table 15-1 summarizes the directly supported types (those types that map directly to W3C Schema types; see Chapter 24 for more on XML mapping of Java types.
Table 15-1. Standard types
Maps and other complex collection types are not currently supported. To maintain the widest compatability for cross-platform web services, it’s best to stick with objects composed of simple data types and arrays or lists of those types.
As we said, JAX-WS can also work with our own object types, although there are several requirements and a caveat to mention. First, to be able to be marshaled, our objects must contain only fields that are supported data types (or further compositions of those). Next, our objects must follow two JavaBeans design patterns. It must have a public, no-args constructor and, if it contains any nonpublic fields, they must have “getter” and “setter” accessor methods. Chapter 22 provides more details about these issues.
Finally, unlike Java RMI, web services do not support the “behavior” or the real identity of our domain objects from end to end. When a Java client uses our WSDL document to generate implementation classes, they will be getting simple data-holder replicas of the classes we specify. These “value objects” will pass along all of the data content of our objects, but are not related to the originals in any other way. Our server-side implementation will, of course, receive the data in the form of our own “real” domain objects. That is why they need to have available constructors so that the server-side framework can create and populate them for us to consume.
Get Learning Java, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.