EJB is a component model for component transaction monitors, which are based on distributed object technologies. Therefore, to understand EJB you need to understand how distributed objects work. Distributed object systems are the foundation for modern three-tier architectures. In a three-tier architecture, as shown in Figure 1.1, the presentation logic resides on the client (first tier), the business logic on the middle tier (second tier), and other resources, such as the database, reside on the backend (third tier).
All distributed object protocols are built on the same basic architecture, which is designed to make an object on one computer look like it’s residing on a different computer. Distributed object architectures are based on a network communication layer that is really very simple. Essentially, there are three parts to this architecture: the object server, the skeleton, and the stub.
The
object server is the business object that
resides on the middle tier. The term “server” can be a
little confusing, but for our purposes the object on the middle tier
can be called the “object server” to distinguish it from
its counterparts, the stub and skeleton. The object server is an
instance of an object with its own unique state. Every object server
class has matching stub and skeleton classes built specifically for
that type of object server. So, for example, a distributed business
object called Person
would have matching
Person_Stub
and Person_Skeleton
classes. As shown in Figure 1.2, the object server
and skeleton reside on the middle tier, and the stub resides on the
client.
The stub and the skeleton are responsible for making the object server, which lives on the middle tier, look as if it is running locally on the client machine. This is accomplished through some kind of remote method invocation (RMI) protocol. An RMI protocol is used to communicate method invocations over a network. CORBA, Java RMI, and Microsoft DCOM all use their own RMI protocol.[3] Every instance of the object server on the middle tier is wrapped by an instance of its matching skeleton class. The skeleton is set up on a port and IP address and listens for requests from the stub. The stub resides on the client machine and is connected via the network to the skeleton. The stub acts as the object server’s surrogate on the client and is responsible for communicating requests from the client to the object server through the skeleton. Figure 1.2 illustrates the process of communicating a method invocation from the client to the server object and back. The stub and the skeleton hide the communication specifics of the RMI protocol from the client and the implementation class, respectively.
The stub implements an interface with the same business methods as the object itself, but the stub’s methods do not contain business logic. Instead, the business methods on the stub implement whatever networking operations are required to forward the request to the object server and receive the results. When a client invokes a business method on the stub, the request is communicated over the network by streaming the name of the method invoked, and the values passed in as parameters, to the skeleton. When the skeleton receives the incoming stream, it parses the stream to discover which method is requested, and then invokes the corresponding business method on the object server. Any value that is returned from the method invoked on the object server is streamed back to the stub by the skeleton. The stub then returns the value to the client application as if it had processed the business logic locally.
The best way to illustrate how distributed objects work is to show how you can implement a distributed object yourself, with your own distributed object protocol. This will give you some appreciation for what a true distributed object protocol like CORBA does. Actual distributed object systems such as DCOM, CORBA, and Java RMI are, however, much more complex and robust than the simple example we will develop here. The distributed object system we develop in this chapter is only illustrative; it is not a real technology, nor is it part of Enterprise JavaBeans. The purpose is to provide you with some understanding of how a more sophisticated distributed object system works.
Here’s a very simple distributed object called
PersonServer
that implements the Person
interface. The
Person
interface captures the concept of a person
business object. It has two business methods:
getAge()
and getName()
. In a
real application, we would probably define many more behaviors for
the Person
business object, but two methods are
enough for this example:
public interface Person { public int getAge() throws Throwable; public String getName() throws Throwable; }
The implementation of this interface,
PersonServer
, doesn’t contain anything at
all surprising. It defines the business logic and state for a
Person
:
public class PersonServer implements Person { int age; String name; public PersonServer(String name, int age){ this.age = age; this.name = name; } public int getAge(){ return age; } public String getName(){ return name; } }
Now we need some way to make the PersonServer
available to a remote client. That’s the job of the
Person_Skeleton
and
Person_Stub
. The Person
interface describes the concept of a person independent of
implementation. Both the PersonServer
and the
Person_Stub
implement the
Person
interface because they are both expected to
support the concept of a person. The PersonServer
implements the interface to provide the actual business logic and
state; the Person_Stub
implements the interface so
that it can look like a Person
business object on
the client and relay requests back to the skeleton, which in turn
sends them to the object itself. Here’s what the
stub looks like:
import java.io.ObjectOutputStream; import java.io.ObjectInputStream; import java.net.Socket; public class Person_Stub implements Person { Socket socket; public Person_Stub() throws Throwable { // Create a network connection to the skeleton. // Replace "myhost" with your own IP Address of your computer. socket = new Socket("myhost",9000); } public int getAge() throws Throwable { // When this method is invoked, stream the method name to the // skeleton. ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); outStream.writeObject("age"); outStream.flush(); ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); return inStream.readInt(); } public String getName() throws Throwable { // When this method is invoked, stream the method name to the // skeleton. ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); outStream.writeObject("name"); outStream.flush(); ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); return (String)inStream.readObject(); } }
When a method is invoked on the Person_Stub
, a
String
token is created and streamed to the
skeleton. The token identifies the method that was invoked on the
stub. The
skeleton parses the
method-identifying token, invokes the corresponding method on the
object server, and streams back the result. When the stub reads the
reply from the skeleton, it parses the value and returns it to the
client. From the client’s perspective, the stub processed the
request locally. Now let’s look at the skeleton:
import java.io.ObjectOutputStream; import java.io.ObjectInputStream; import java.net.Socket; import java.net.ServerSocket; public class Person_Skeleton extends Thread { PersonServer myServer; public Person_Skeleton(PersonServer server){ // Get a reference to the object server that this skeleton wraps. this.myServer = server; } public void run(){ try { // Create a server socket on port 9000. ServerSocket serverSocket = new ServerSocket(9000); // Wait for and obtain a socket connection from stub. Socket socket = serverSocket.accept(); while (socket != null){ // Create an input stream to receive requests from stub. ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); // Read next method request from stub. Block until request is // sent. String method = (String)inStream.readObject(); // Evaluate the type of method requested. if (method.equals("age")){ // Invoke business method on server object. int age = myServer.getAge(); // Create an output stream to send return values back to // stub. ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); // Send results back to stub. outStream.writeInt(age); outStream.flush(); } else if(method.equals("name")){ // Invoke business method on server object. String name = myServer.getName(); // Create an output stream to send return values back to // the stub. ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); // Send results back to stub. outStream.writeObject(name); outStream.flush(); } } } catch(Throwable t) {t.printStackTrace();System.exit(0); } } public static void main(String args [] ){ // Obtain a unique instance Person. PersonServer person = new PersonServer("Richard", 34); Person_Skeleton skel = new Person_Skeleton(person); skel.start(); } }
The Person_Skeleton
routes requests received from
the stub to the object server, PersonServer
.
Essentially, the Person_Skeleton
spends all its
time waiting for the stub to stream it a request. Once a request is
received, it is parsed and delegated to the corresponding method on
the PersonServer
. The return value from the object
server is then streamed back to the stub, which returns it as if it
was processed locally.
Now that we’ve created all
the machinery, let’s look at a simple
client that makes use of the
Person
:
public class PersonClient { public static void main(String [] args){ try { Person person = new Person_Stub(); int age = person.getAge(); String name = person.getName(); System.out.println(name+" is "+age+" years old"); } catch(Throwable t) {t.printStackTrace();} } }
This client application shows how the stub is used on the client.
Except for the instantiation of the Person_Stub
at
the beginning, the client is unaware that the
Person
business object is actually a network proxy
to the real business object on the middle tier. In Figure 1.3, the RMI loop diagram is changed to represent
the RMI process as applied to our code.
As you examine Figure 1.3, notice how the RMI loop
was implemented by our distributed Person
object.
RMI is the basis of distributed object systems and is responsible for
making distributed objects
location transparent. Location transparency means that a server
object’s actual location—usually on the middle
tier—is unknown and unimportant to the client using it. In this
example, the client could be located on the same machine or on a
different machine very far away, but the client’s interaction
with the business object is the same. One of the biggest benefits of
distributed object systems is location transparency. Although
transparency is beneficial, you cannot treat distributed objects as
local objects in your design because of the performance differences.
This book will provide you with good distributed object design
strategies that take advantage of transparency while maximizing the
distributed system’s performance.
When this book talks about the stub on the client, we will often refer to it as a remote reference to the object server. This allows us to talk more directly about the object server and its representation on the client.
Distributed
object protocols such as
CORBA, DCOM, and Java RMI provide a lot more infrastructure for
distributed objects than the Person
example. Most
implementations of distributed object protocols provide utilities
that automatically generate the appropriate stubs and skeletons for
object servers. This eliminates custom development of these
constructs and allows a lot more functionality to be included in the
stub and skeleton.
Even with automatic generation of stubs and skeletons, the
Person
example hardly scratches the surface of a
sophisticated distributed object protocol. Real world protocols like
Java RMI and CORBA IIOP provide error and exception handling,
parameter passing, and other services like the passing of transaction
and security context. In addition, distributed object protocols
support much more sophisticated mechanisms for connecting the stub to
the skeleton; the direct stub-to-skeleton connection in the
Person
example is fairly primitive.
Real distributed object protocols, like CORBA, also provide an Object Request Broker (ORB), which allows clients to locate and communicate with distributed objects across the network. ORBs are the communication backbone, the switchboard, for distributed objects. In addition to handling communications, ORBs generally use a naming system for locating objects and many other features such as reference passing, distributed garbage collection, and resource management. However, ORBs are limited to facilitating communication between clients and distributed object servers. While they may support services like transaction management and security, use of these services is not automatic. With ORBs, most of the responsibility for creating system-level functionality or incorporating services falls on the shoulders of the application developer.
[3] The acronym RMI isn’t specific to Java RMI; it was in use long before Java came along. This section uses RMI to describe distributed object protocols in general. Java RMI is the Java language version of a distributed object protocol.
Get Enterprise JavaBeans, Second 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.