Observer Pattern - An Enterprise JavaBean Implementation

zhaozj2021-02-11  194

Observer Pattern - An Enterprise JavaBean Implementation

Greg Comeau

Download: EJBOBSERVERPATTERN.ZIP

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 XML document. 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, IE IF One Application Aborts The Transaction 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. Eg Application X may require that a customer has A Fax Number. if a transaction attempts to base application x and customer a does 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 kilpentation must beforeorted before the changementation ies 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 area. The Java Classes Are Contained Withnin 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 are not 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 (...) does 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 createvable 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 reason to the object being observed * @Param arg the arguments being passed to the observer

* /

Public void Update (EJBSERVABLE 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 shich may be observed

* by instances of ejbobserver.

*

* @see java.util.observable

* /

Public class ejbobservable imports java.io.serializable

{

/ **

* The Collection of Observers. Using A set will Guarantee

* No duplicate observers.

* /

protected set mobservers = new hashset ();

protected boilean mchanged = false; / *

The Methods for Adding, Deleting, and Counting Observers Are

All Trivial, AS Are The Methods for Checking and Setting To

'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 Changd)

{

McHanged = Changed;

}

/ **

* An overloaded form of notifyobservers what 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 Implement 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 Implement OF EJBOBSERVER

* /

Public void NotifyobServers (Object Arg) THROWS RemoteException, EJBEXCEPTION

{

IF (this.haschanged ())

{

Iterator i = mobservers.iterator (); i.hasnext ();)

{

EJBOBSERVER OBS = (EJBOBSERVER) I.NEXT (); Obs.Update (this, arg);

}

THIS.CLAARCHANGED ();

}

}

}

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 change attribute area odentical 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 shing to do. Each Call to Notifyobservers Will Result in

A Remote Method Call for Every Registered Observer. if The IS

More Than One Attribute To 'Set' Then IT Might Be Better

For the Caller to set the all and the call notifyobservers evicitly.

* /

}

Public String Tostring ()

{

Return msomething;

}

}

Test Harnes

Again for the purposes of testing, i created a test harness Called Tester. This class simply create ingofoBar, 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 the document 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 barn door closed I do NOT YET HAVE AN IMPLEMENTATION OF EJBOBSERVER. This Turned Out To Be The Interesting Part.

Note that updating via EJBObserver.update a remote observer (...) 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 EJBSERVER IMPLEMENTATION

Next I created the home interface, Which is Almost As Trivial As The Remote Interface. 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 Assute. (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 Implement The Superinterface EJBOBSERVER. The Implementation of the Update Method Simply Writes To The System Output Stream SO i know That ITWORKED:

Package ejbobserverpattern;

Import javax.ejb. *;

Public Class RemoteobserverBean Implements EJBOBSERVER, SessionBean

{

/ **

* @Param Observable a reason to the object being observed

* @Param arg the arguments being passed to the observer

* /

Public void Update (EJBSERVABLE 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 the download 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. ... why do you look skeptical?

Instantiate The Remote EjBobserveract 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 folload:

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 profment ();

P.PUT (Context.Initial_Context_Factory,

"WebLogic.jndi.wlinitialContextFactory");

P.PUT (Context.Provider_URL,

"T3: // localhost: 7003");

InitialContext NamingContext = New InitialContext (P);

RemoteObserverhome obshom =

(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 is not exactly what I wanted. Notice that I coupled myself directly to a specific implementation of EJBObserver, namely RemoteObserver. My intention from the beginning 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 implementation via EJBObserver remote polymorphism.Decoupling from RemoteObserver was easy enough I simply changed every reference to EJBObserver -.. Which I should have done 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 RemoteObserverHome does not have some convenient superinterface to use. Any such interface would have to define the create method in order for polymorphism to work. EJBHome does not define the create method. Casting the Home Interface To Ejbhod 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 not 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 homea = remoteobserver.class.getname ();

Properties P = new profment ();

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,

EJBSERVER.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 some external repository via JDBC from. I will not 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 TO MAKE POLYMORPHISM WORK?"

That's a reasonable question, self. let's try it. I DID.

First Let's Create A New Interface EJBSERVERHOME That Extends ejbhome. This will be the superinterface to be extended by Any Home Interface of A Remote EJBOBSERVER:

Package ejbobserverpattern;

Public Interface EJBSERVERHOME EXTENDS JAVAX.EJB.EJBHOME

{

Public EjBobserver Create ()

Throws java.rmi.RemoteException, javax.ejb.createException;

}

Now Let's Redefine RemoteobserHome 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.RemoteobserverBean Does 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 not 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 not change the return type of an overridden method. Catch-22. And you can not 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 Abstract

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 to do so. There's one more abstraction to make.EJBObserverProxy

Patterns can be addictive. You can not 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 EjBobserProxy Implements Java.io.Serializable, EJBOBSERVER

{

Private Properties MCONTextValues ​​= NULL;

Private string mjndiname = NULL;

Public EJBOBSERVERPROXY

String InitialContextFactory,

String ProviderURL,

String jndiname)

{

Properties P = new profment ();

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 property GetContextValues ​​()

{

Return MCONTextValues;

}

Public int hashcode ()

{

Return (McontextVales.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 Reference 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 reason to the name Object and narrow it to ejbhome

To make Sure it is a home interface.

* /

Ejbhome obshom = (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. ejbhome doesn't define a create () Method

So We can't Invoke It Via Polymorphism.

Let's use reflection to find and invoke the create () method () Method.

Look for a mathod 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 (Obshod, New Object [0]);

/ * Narrow the resulting reference to the EJBOBSERVER

Interface, Which Each Remote Observer is obliGated to import.

* /

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 EJBSERVERPROXY:

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 ...............................

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 could not 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 can not 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 it does not bother calling the equals method. So there was no way (that I found) to eliminate duplicate .

New Tester Using EJBOBSERVERPROXY

Now I Could Use EJBSERVERPROXY 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 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 Tester does not 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 - Not Just In The Distributed Transaction Scenario That I'VE OUTLINED.

About the Author

.

转载请注明原文地址:https://www.9cbs.com/read-5294.html

New Post(0)