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 resides 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 business object, the skeleton, and the stub.
The
business object
resides on the middle tier. It’s an instance of an object that
models the state and business logic of some real-world concept, such
as a person, order, or account. Every business object class has
matching stub and skeleton classes built specifically for that type
of business object. 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 business
object and skeleton reside on the middle tier, and the stub resides
on the client.
The stub and the skeleton are responsible for making the business object 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 .NET all use their own RMI protocols.[4] Every instance of the business object 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, which resides on the client machine and is connected via the network to the skeleton. The stub acts as the business object’s surrogate on the client and is responsible for communicating requests from the client to the business object 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 business object implements a public interface that declares its business methods. The stub implements the same interface as the business object, 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 business object 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, then invokes the corresponding business method on the business object. Any value that is returned from the method invoked on the business object 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 business 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
surprising. It defines the business logic and state for the
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. Use "localhost" or the IP Address of the skeleton if it's on a different machine. */ socket = new Socket("localhost",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
business object, 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 processes 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 business object 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 the stub. Socket socket = serverSocket.accept(); while (socket != null){ // Create an input stream to receive requests from the stub. ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); // Read the next method request from the stub. Block until the // request is sent. String method = (String)inStream.readObject(); // Evaluate the type of method requested. if (method.equals("age")){ // Invoke the business method on the server object. int age = myServer.getAge(); // Create an output stream to send return values back to // the stub. ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); // Send results back to the stub. outStream.writeInt(age); outStream.flush(); } else if(method.equals("name")){ // Invoke the business method on the 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 the stub. outStream.writeObject(name); outStream.flush(); } } } catch(Throwable t) {t.printStackTrace();System.exit(0); } } public static void main(String args [] ){ // Obtain a unique Person instance . 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 business object, 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
business object 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 business object. This allows us to talk more directly about the business object and its representation on the client.
Distributed object protocols such as CORBA, DCOM, and Java RMI
provide much 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 business objects. This eliminates
custom development of these constructs and allows much 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 such
as Java RMI and CORBA IIOP provide error- and exception-handling,
parameter-passing, and other services such as 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, 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 business objects. 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.
[4] The acronym RMI isn’t specific to Java RMI. This section uses the term RMI to describe distributed object protocols in general. Java RMI is the Java language version of a distributed object protocol.
Get Enterprise JavaBeans, Third 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.