observer模式之EJB实现(0分)

V

VRGL

Unregistered / Unconfirmed
GUEST, unregistred user!
Motivation
I recently needed an infrastructure that would allow an arbitrary number of
Enterprise JavaBeans to observe changes to a collection of central business
entities. The application environment in which I am developing consists of
a number of EJB applications running on more than one host. All of the
applications are designed to work together as a single, integrated suite.
The database environment is distributed. A central store of data is shared
by the entire suite while each application maintains a separate store of
data that is specific to that application.
One part of the suite is responsible for accepting transactions from the
outside world. Each transaction arrives in the form of an XMLdo
cument.
A typical transaction might require changes to the central data store
as well as one or more application data stores. All changes must be
done within the scope of a single transaction, i.e. if one application
aborts the transaction then
all participants must abort the transaction.
Also, there are some data constraints that cross application boundaries.
It is possible for one application to abort a transaction because a
change made to the central repository is unacceptable within the context
of some application specific data. e.g. Application X may require that
a customer has a fax number. If a transaction attempts to grant Customer
A permission to use Application X and Customer Ado
es not have a fax
number then
Application X must abort the transaction.
It was the enforcement of these distributed data constraints that motivated
my use of the observer pattern. Each application needs to observe changes
in the shared repository as they occur -- and within the transaction in
which they occur. If a proposed change to the shared data is unacceptable
to any given application then
the transaction must be aborted before the
change is made permanent.
Implementation
What follows is essentially the process that I went through to implement
this pattern. I have omitted some of the more glaring mistakes to protect
the stupid guilty.
Note: All of the source files are available fordo
wnload. The java classes
are contained within a package called EJBObserverPattern.
The java.util.Observable class and the java.util.Observer interface work
great within the scope of a single Java VM. But I discovered that they
aren't much use in implementing the observer pattern across VMs with EJBs
as implementations of Observer. The first step in creating an EJB observer
might be to extend java.util.Observer to create a remote interface:
public interface RemoteObserver extends javax.ejb.EJBObject, java.util.Observer
{
}
If you did this you'd quickly find out that java.util.Observer.update(...)
do
es not declare java.rmi.RemoteException, something required of all methods
of a remote interface. At this point I found it necessary to create a new
observer interface and observable class that would work in the EJB universe.
I created my own observer and observable classes which parallel the respective
classes in java.util.
The EJBObserver Interface
A new interface is required that I called EJBObserver:
package EJBObserverPattern;
public interface EJBObserver
{
/**
* @param observable a reference to the object being observed
* @param arg the arguments being passed to the observer
*/
public void update (EJBObservable observable, Object arg)
throws java.rmi.RemoteException, javax.ejb.EJBException;
}
Note: A common pattern in EJB implementations is to define a superinterface
to be extended by both the remote interface and the bean itself. This is
commonly called a business interface. EJBObserver is a business interface.
Yet Another Note: My development environment adheres to the EJB 1.0
specification. Thus, I have to explicitly declare javax.ejb.EJBException.
EJB 1.1 and later redefines EJBException as a subclass of RuntimeException
making it unnecessary to explicitly declare it in a method signature.
The EJBObservable Class
My new class EJBObservable defines all the same methods as
java.util.Observable (why change a paradigm that works.) Thus,
my EJBObserver update method takes an EJBObservable object;
this mirrors java.util.Observer, which takes a java.util.Observable
object. I marked EJBObservable as serializable by implementing
java.io.Serializable. This is necessary since instances of this class
will be sent to remote EJB observers:
package EJBObserverPattern;
import java.rmi.RemoteException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.ejb.EJBException;
/**
* A class to be subclassed by any entity which may be observed
* by instances of EJBObserver.
*
* @see java.util.Observable
*/
public class EJBObservable implements java.io.Serializable
{
/**
* The collection of observers. Using a Set will guarantee
* no duplicate observers.
*/
protected Set mObservers = new HashSet();
protected boolean mChanged = false;
/*
The methods for adding, deleting, and counting observers are
all trivial, as are the methods for checking and setting the
'changed' indicator.
Their semantics are identical to the respective
methods in java.util.Observable.
*/
public void addObserver (EJBObserver observer)
{
mObservers.add (observer);
}
public void deleteObserver (EJBObserver observer)
{
mObservers.remove (observer);
}
public void deleteObservers ()
{
mObservers.clear();
}
public int countObservers ()
{
return mObservers.size();
}
public boolean hasChanged ()
{
return mChanged;
}
public void clearChanged ()
{
this.setChanged (false);
}
protected void setChanged (boolean changed)
{
mChanged = changed;
}
/**
* An overloaded form of notifyObservers that takes no argument. The semantics
* are identical to calling notifyObservers (..., null).
* @exception RemoteException thrown by a remote implementation of EJBObserver
* @exception EJBException thrown by a remote implementation of EJBObserver
*/
public void notifyObservers () throws RemoteException, EJBException
{
this.notifyObservers (null);
}
/**
* If this object has changed as indicated by the hasChanged method,
* notify all observers and call clearChanged to reset the hasChanged indicator.
* @param arg any object
* @exception RemoteException thrown by a remote implementation of EJBObserver
* @exception EJBException thrown by a remote implementation of EJBObserver
*/
public void notifyObservers (Object arg) throws RemoteException, EJBException
{
if (this.hasChanged())
{
for (Iterator i = mObservers.iterator();
i.hasNext();
)
{
EJBObserver obs = (EJBObserver) i.next();
obs.update (this, arg);
}
this.clearChanged();
}
}
}
Foobar -- A Subclass of EJBObservable
For testing purposes I created a subclass of EJBObservable called Foobar.
This is the thing that will be observed. Foobar has a single member variable
with a corresponding setter method. Setting the member variable also sets the
changed attribute inherited from the superclass EJBObservable. The semantics
of the changed attribute are identical to the respective attribute of
java.util.Observable:
package EJBObserverPattern;
public class Foobar extends EJBObservable
{
String mSomething = null;
public void setSomething (String s)
{
mSomething = s;
setChanged (true);
/* We could call notifyObservers right here, but this may or may not
be a good thing todo
. Each call to notifyObservers will result in
a remote method call for every registered observer. If there is
more than one attribute to 'set' then
it might be better
for the caller to set them all and then
call notifyObservers explicitly.
*/
}
public String toString ()
{
return mSomething;
}
}

The Test Harness
Again for the purposes of testing, I created a test harness called Tester.
This class simply creates an instance of Foobar, adds an observer, invokes
the setter method of Foobar and calls notifyObservers:
package EJBObserverPattern;
public class Tester
{
public static void main (String[] args)
{
try
{
Foobar f = new Foobar();
f.addObserver (???);
f.setSomething (args[0]);
f.notifyObservers (args[1]);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
Note: Tester will go through several revisions later in thedo
cument.
My intent is to show you the abstraction process as well as the final
result.
As you can see I've put the cart before the horse after the barndo
or
closed. Ido
not yet have an implementation of EJBObserver. This turned
out to be the interesting part.
Note that updating a remote observer via EJBObserver.update(...) will
involve a remote method invocation. The observer is located at some
arbitrary location in the EJB universe. The local manifestation of a
remote observer will be an instance of a remote interface obtained by
the usual means -- by invoking the create method of a home interface
obtained via JNDI.
So now I needed to implement the usual components of an EJB, the home and
remote interfaces and the bean itself. I used a stateless session bean.
Note that there can be an arbitrary number of EJBObserver implementations
in use at one time but only one is required to demonstrate the pattern.
Remote Interface of EJBObserver Implementation
First I created the remote interface, which I subclassed from the business
interface EJBObserver:
package EJBObserverPattern;
public interface RemoteObserver extends javax.ejb.EJBObject, EJBObserver
{
}
Home Interface of EJBObserver Implementation
Next I created the home interface, which is almost as trivial as
the remote interface. There's nothing special about it:
package EJBObserverPattern;
public interface RemoteObserverHome extends javax.ejb.EJBHome
{
public RemoteObserver create()
throws java.rmi.RemoteException, javax.ejb.CreateException;
}
The astute reader may notice that I'm going to have a problem with this
home interface. Rest assured that I was not astute. (What is a stute?)
I didn't recognize the problem until I ran into it. I'll describe the
exact problem and my solution later.
A stute is the adult form of a ware.
RemoteObserverBean
Now for the bean itself. Again, I implemented the superinterface EJBObserver.
The implementation of the update method simply writes to the system output
stream so I know that it worked:
package EJBObserverPattern;
import javax.ejb.*;
public class RemoteObserverBean implements EJBObserver, SessionBean
{
/**
* @param observable a reference to the object being observed
* @param arg the arguments being passed to the observer
*/
public void update (EJBObservable observable, Object arg)
{
System.out.println ("Hey! I've been updated! " +
"observable = " + observable.toString() +
" arg = " + arg.toString());
}
public void ejbCreate () { }
public void ejbActivate() { }
public void ejbPassivate() { }
public void ejbRemove() { }
public void setSessionContext(SessionContext ctx) { }
}
There is nothing special about the deployment descriptor for this bean.
I've included it in thedo
wnload file. The only detail worth noting is
that the EJB must be a well-behaved transactional component, preferably
using Component Managed Transactions. Otherwise any persistence operations
performed by an instance of EJBObserver may not be rolled back if some
other EJBObserver aborts the transaction.
I used Weblogic to build and deploy it. It all worked perfectly the first
time. Really. ... Whydo
you look skeptical?
Instantiate the Remote EJBObserver
At this point I cannibalized the appropriate code to get the new home and
remote interfaces. Plugging this code into Tester I ended up with the
following:
package EJBObserverPattern;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
public class Tester
{
public static void main (String[] args)
{
try
{
Foobar f = new Foobar();
Properties p = new Properties();
p.put(Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.WLInitialContextFactory");
p.put(Context.PROVIDER_URL,
"t3://localhost:7003");
InitialContext namingContext = new InitialContext(p);
RemoteObserverHome obsHome =
(RemoteObserverHome) PortableRemoteObject.narrow (
namingContext.lookup (RemoteObserver.class.getName()),
RemoteObserverHome.class );
RemoteObserver obs =
(RemoteObserver) PortableRemoteObject.narrow (
obsHome.create(),
RemoteObserver.class);
f.addObserver (obs);
f.setSomething (args[0]);
f.notifyObservers (args[1]);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
When I ran the above it worked as expected. The update method of the remote
observer was called and I got the expected output on the console. But this
isn't exactly what I wanted. Notice that I coupled myself directly to a
specific implementation of EJBObserver, namely RemoteObserver.
My intention from the begin
ning was to query some semi-static registry to
obtain the JNDI parameters that identify one or more observers. The parameters
stored in each entry of the registry would be the initial context factory,
the provider url, and the JNDI name of the home interface.
In other words, I wanted to invoke an arbitrary EJBObserver implementation
via remote polymorphism.
Decoupling from RemoteObserver was easy enough. I simply changed every
reference to EJBObserver -- which I should havedo
ne in the first place.
then
I ran into a problem with the home interface.
The aforementioned astute reader has by now realized that my home interface
RemoteObserverHomedo
es not have some convenient superinterface to use.
Any such interface would have to define the create method in order for
polymorphism to work. EJBHomedo
esn't define the create method.
Casting the home interface to EJBHome and invoking the create method
will generate a compile error.
Reflection of the Home Interface
When you know a class implements a method with a given signature but you
can't use polymorphism, reflection works great. Here's how Tester looked
after I used reflection to find and invoke the create method of an
arbitrary home interface:
package EJBObserverPattern;
import java.lang.reflect.Method;
import java.util.Properties;
import javax.ejb.EJBHome;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
public class Tester
{
public static void main (String[] args)
{
try
{
Foobar f = new Foobar();
String initialContextFactory = "weblogic.jndi.WLInitialContextFactory";
String providerUrl = "t3://localhost:7003";
String homeName = RemoteObserver.class.getName();
Properties p = new Properties();
p.put(Context.INITIAL_CONTEXT_FACTORY,
initialContextFactory);
p.put(Context.PROVIDER_URL,
providerUrl);
InitialContext namingContext = new InitialContext(p);
// Get a home interface and narrow it to EJBHome
EJBHome obsHome =
(EJBHome) PortableRemoteObject.narrow (
namingContext.lookup (homeName),
EJBHome.class );
// Find the create method via reflection
Method createMethod = obsHome.getClass().getDeclaredMethod (
"create", new Class[0] );
// Invoke the reflected create method to get a remote interface
Object remoteRef = createMethod.invoke (obsHome, new Class[0]);
// Narrow the remote reference to EJBObserver
EJBObserver obs =
(EJBObserver) PortableRemoteObject.narrow (
remoteRef,
EJBObserver.class);
f.addObserver (obs);
f.setSomething (args[0]);
f.notifyObservers (args[1]);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
The initial context factory, provider url, and EJB home name can easily
be retrieved from some external repository via JDBC. I won't show that
implementation. One could also imagine an additional mechanism by which
a remote application could register itself in the above repository.
I won't show that either. Such details aren't really part of this pattern.
Why Not Subclass EJBHome?
You might be asking yourself, "Self, why resort to reflection when I can
subclass EJBHome to define the create method that I need to make polymorphism
work?"
That's a reasonable question, Self. Let's try it. I did.
First let's create a new interface EJBObserverHome that extends EJBHome.
This will be the superinterface to be extended by any home interface
of a remote EJBObserver:
package EJBObserverPattern;
public interface EJBObserverHome extends javax.ejb.EJBHome
{
public EJBObserver create()
throws java.rmi.RemoteException, javax.ejb.CreateException;
}
Now let's redefine RemoteObserverHome to extend this interface:
package EJBObserverPattern;
public interface RemoteObserverHome extends EJBObserverHome
{
}
Looking good. Now let's rebuild RemoteObserverBean.
(build, build, build ... oops.)
DDCreator RemoteObserverBeanDD.ser
Error: Bean EJBObserverPattern.RemoteObserverBeando
es not comply
with the EJB 1.0 specification
Bean class: EJBObserverPattern.RemoteObserverBean
-----------------------------------------------
home.create() must always return the remote interface type
(Sound of hand slapping forehead.) Of course, a create method of a home interface
can't return some abstract class;
it must return the exact remote interface type
of the EJB that lives there. And if you try to override the create method in RemoteObserverHome
to return the right interface type then
the compiler will remind you that you can't change the
return type of an overridden method. Catch-22. And you can't reuse the same home interface for
every remote EJBObserver because there must be a one-to-one correspondence between a home interface
and a specific EJB implementation. Otherwise how would the home implementation know which bean to
create? Catch-23. (That last bit may have seemed obvious to you. I actually tried it.)
If anybody has another solution to this quandry -- besides reflection
-- please enlighten me.
One More Abstraction
There was one detail of this that bothered me. Looking up the home interface
and creating a remote interface is a lot of work to go through every time
I want to add an observer. What if the observable instance never changes?
That's a lot of work for nothing. When I add an observer I simply want to
save the minimum amount of information that will be necessary to instantiate
and invoke the remote observer when and if the time comes todo
so. There's
one more abstraction to make.
EJBObserverProxy
Patterns can be addictive. You can't eat just one. A proxy pattern was a
perfect fit. I created a new class called EJBObserverProxy that implemented
EJBObserver. EJBObserverProxy was designed to save all the information
needed to instantiate and invoke a remote observer. It also encapsulated
the reflective mechanism by which this was accomplished:
package EJBObserverPattern;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.util.Properties;
import java.util.Set;
import java.util.Iterator;
import java.util.HashSet;
import javax.ejb.EJBException;
import javax.ejb.EJBHome;
import javax.rmi.PortableRemoteObject;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class EJBObserverProxy implements java.io.Serializable, EJBObserver
{
private Properties mContextValues = null;
private String mJNDIName = null;
public EJBObserverProxy (
String initialContextFactory,
String providerUrl,
String jndiName)
{
Properties p = new Properties();
p.put(Context.INITIAL_CONTEXT_FACTORY,
initialContextFactory);
p.put(Context.PROVIDER_URL,
providerUrl);
init (p, jndiName);
}
public EJBObserverProxy (
Properties contextValues,
String jndiName )
{
init (contextValues, jndiName);
}
protected void init (
Properties contextValues,
String jndiName )
{
mContextValues = contextValues;
mJNDIName = jndiName;
}
public String getName ()
{
return mJNDIName;
}
public Properties getContextValues ()
{
return mContextValues;
}
public int hashCode ()
{
return (mContextValues.toString() + mJNDIName).hashCode();
}
public boolean equals (Object o)
{
EJBObserverProxy loc = (EJBObserverProxy) o;
return (
(mContextValues.equals (loc.getContextValues())) &&
(mJNDIName.equals (loc.getName()))
);
}
/**
* Instantiate the remote observer referenced by this object and update it.
*/
public void update (EJBObservable object, Object arg) throws RemoteException, EJBException
{
try
{
String beanHomeName = this.getName();
Properties p = this.getContextValues();
InitialContext namingContext = new InitialContext(p);
/* Get a reference to the named object and narrow it to EJBHome
to make sure it is a home interface.
*/
EJBHome obsHome = (EJBHome) PortableRemoteObject.narrow (
namingContext.lookup (beanHomeName), EJBHome.class );
/* We can't directly cast this reference to a home interface because each
EJB implementation will have its own home interface in some arbitrary
class hierarchy. EJBHomedo
esn't define a create() method
so we can't invoke it via polymorphism.
Let's use reflection to find and invoke the create() method.
Look for a method named 'create' which takes no parameters.
*/
Method createMethod = obsHome.getClass().getDeclaredMethod (
"create", new Class[0] );
/* Invoke the 'create' method of the home interface via our reflected Method.
*/
Object remoteObj = createMethod.invoke (obsHome, new Object[0]);
/* Narrow the resulting reference to the EJBObserver
interface, which each remote observer is obligated to implement.
*/
EJBObserver obs = (EJBObserver) PortableRemoteObject.narrow (
remoteObj, EJBObserver.class);
/* Update this observer with an EJBObservable instance and some argument.
Foobar is an instance of EJBObservable.
*/
obs.update (object, arg);
}
catch (NoSuchMethodException e)
{
throw new EJBException (e);
}
catch (NamingException e)
{
throw new EJBException (e);
}
catch (InvocationTargetException e)
{
throw new EJBException (e);
}
catch (IllegalAccessException e)
{
throw new RuntimeException (e.toString());
}
}
}
There are a number of things to note about EJBObserverProxy:
It encapsulates member variables that store the settings required to
lookup a home interface.
It defines hashCode and equals methods so that EJBObserverProxy will
be a well-behaved member of a Set. Recall that instances of EJBObserver
are stored in a HashSet within EJBObservable. The semantics defined by
Observable dictate that any given Observer must be unique within an
instance of Observable. A Set implementation uses the hashCode and equals
method to enforce uniqueness.
It implements java.io.Serializable since instances of this class will
be stored inside EJBObservable, which will be serialized as an argument
of a remote method call.
The update method encapsulates all of the nasty JNDI and reflection that
we need to remotely invoke the associated EJBObserver instance.
A reader suggested the use of javax.ejb.Handle to maintain a persistent
relationship to a remote observer. You can get a Handle to a remote
object and serialize it to persistent storage. The Handle can be
materialized from storage at any time to recreate the remote object.
I considered encapsulating a Handle object inside EJBObserverProxy but
I couldn't figure out how to implement hashCode such that two Handle
objects that pointed to the same remote interface would evaluate to
the same hash code. Remember that duplicate observers cannot be added
to a single observable. The HashSet implementation first compares hash
codes to determine if two objects might be equal. If the hash codes are
different then
itdo
esn't bother calling the equals method. So there was
no way (that I found) to eliminate duplicate observers using Handle objects.
Of course, the act of writing the above paragraph guarantees that it can be
done. Such is the nature of the universe.
New Tester using EJBObserverProxy
Now I could use EJBObserverProxy to greatly simplify Tester, thus simplifying
the eventual client code that will use this pattern:
package EJBObserverPattern;
public class Tester
{
public static void main (String[] args)
{
try
{
Foobar f = new Foobar();
EJBObserverProxy obs =
new EJBObserverProxy (
"weblogic.jndi.WLInitialContextFactory",
"t3://localhost:7003",
RemoteObserver.class.getName());
f.addObserver (obs);
f.setSomething (args[0]);
f.notifyObservers (args[1]);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
The term client in this context means the party that instantiates the
EJBObservable object. The client could easily be another EJB.
Motivation Redux
Recall that my motivation was to allow an arbitrary collection of EJBs to
observe changes in a central data repository. Any such EJB can now simply
extend EJBObserver to define its remote interface and register its JNDI
information with the manager of the central repository via some simple
database table. If a business entity within the central repository is
changed then
the central repository manager, after making the change,
instantiates a subclass of EJBObservable which encapsulates the updated
business entity. The EJBObserver registry is then
used to add the
appropriate observers and notifyObservers is called.
If you look again real close you might recognize a mediator pattern in
the above paragraph.
Any remote EJBObserver may abort the transaction by simply throwing a
system exception or calling setRollbackOnly on its EJBContext object.
Recall that a system exception is a java.lang.RuntimeException or
java.rmi.RemoteException, or any subclass directly or indirectly thereof.
Note that Testerdo
esn't start a new transaction before it calls
notifyObservers. This is an academic example. If I really wanted a single
transaction context I'd have to start a new client demarcated transaction.
In reality, the role of Tester will usually be filled by an EJB and container
managed transactions would be used.
This implementation can be used in any distributed application where an
Observer pattern makes sense -- not just in the distributed transaction
scenario that I've outlined.

About the Author
Greg Comeau is a Senior Software Engineer at Webb Interactive Services,
Inc. with offices in Denver and Boulder, Colorado. The author prefers
the latter but harbors no ill will against the former.
Comments on this article are welcome. Flames will go unanswered.
I'd rather be skiing. Have a nice day.
 
哥们儿,你哪里找来那么多资料啊?尤其是全是
关于ejb的,这可是我一直找的东西?
告诉我在哪儿找的,或者mail一份给我,ok?
wrlmmmp@263.net
 
就是关于j2ee很权威的网站theserverside.com呀
 
请问一下
j2ee 之后流行什么?
 
接受答案了.
 
顶部