Desperately trying to get the code into CVS:
cvs add readme.txt (in directory
C:\Home\leo\private\Apache\jakarta-avalon\src\proposal\microcontainer\)
cvs server: cannot add file on non-branch tag HEAD
----------------------------------------------------
Anyway: here's the readme.txt...
AVALON MICROCONTAINER
- --------------------- -
PURPOSE
One of the barriers of entry to Avalon is the fact that you are buying
into an entire architecture. Instead of just getting a set of classes
that you can use as you were used to, you get a set of Avalon Components
that must be put in a container and managed. Often this involves XML
config
files and other niceties that, while providing amazing flexibility,
makes
it difficult to lure unsuspecting developers into Avalon. We lose the
"first
component is for free" / "I can see that you're special, so I got
something
special for you" opportunity.
Stefano mentioned that several potential new users of Avalon had tried
to
start a component by doing:
ComponentInterface comp = new ComponentImpl ();
and then nothing more, because that was what they were familiar with.
The purpose of Avalon Microcontainer is to make something that is as
close as possible to the above possible. Specifically:
+ Components should be able to be used with the Microcontainer without
any
XML and any config files.
+ All configuration is handled programmatically.
+ As far as possible, usage of components should follow standard Java
idioms.
BASIC IDEA
Define a container that holds *one* component. Yep, that's right, one
single
component for each container. Solve composition by linking these
together.
Solve usage of components by letting the container expose the
component's
interface. This may seem difficult to understand, but see the case study
below
to see how I reached the currect way of doing it.
CASE STUDY
The case I'm looking at has two components, the Supplier and the
Composer.
Their interfaces are defined in Supplier.java and Composer.java,
respectively,
and their implementations are in SupplierImpl.java and
ComposerImpl.java.
Let me list some issues:
+ ComposerImpl accesses the Supplier via a ComponentManager interface.
It
uses lookup("supplier") to get the Supplier.
+ ComposerImpl may require more than one Supplier during each
transaction.
+ SupplierImpl may or may not be threadsafe.
+ The user may want to access Supplier directly.
+ The user definitely wants to access Supplier directly.
So we have a component, ComposerImpl, that needs a Supplier. Had this
been
straight Java, we would expect to do something like this:
...
SupplierImpl supplier = new SupplierImpl();
ComposerImpl composer = new ComposerImpl(supplier);
supplier.someMethod();
composer.someMethod();
...
So let's introduce an ObjectFactory class for construction:
...
Supplier supplier = (Supplier) ObjectFactory.newInstance(
SupplierImpl.class
);
// Pass the supplier in with role "supplier"
Composer composer = (Composer) ObjectFactory.newInstance(
ComposerImpl.class,
new Role[]{ new Role("supplier",
supplier) }
);
supplier.someMethod();
composer.someMethod();
...
But now we have a problem. Even though we do give ComposerImpl a
Supplier
with the correct role name, we have forgotten that ComposerImpl may do
*several* lookups on "supplier", and that SupplierImpl may not be
reusable. As it looks now, ComposerImpl will get the same instance of
SupplierImpl all the time, and that may not work.
So let's introduce a ObjectFactoryFactory. Give it a component
implementation
class and it will produce a factory that you can use to create new
components.
Then we pass *that* one to ComposerImpl (hidden behind a
ComponentManager
interface) and we're done, right?
...
ObjectFactory supplierFactory = ObjectFactoryFactory.newInstance(
SupplierImpl.class
);
// Pass the supplier in with role "supplier"
Composer composer = ObjectFactoryFactory.newInstance(
ComposerImpl.class,
new Role[]{ new Role("supplier",
supplierFactory) }
);
supplier.someMethod(); // !!!!!!!!!!! This won't work anymore!
composer.someMethod(); // !!!!!!!!!!! This won't work anymore!
...
Guess not.
The problem is that sometimes, we want to use the returned object as a
factory
(when it is used with Composable components) and sometimes we just want
the
"raw" component. The ideal would be something that exposes the interface
of the
component, but also exposed a factory interface. We would use the
factory
interface of a component when giving it to another component that is a
composer, but use the ordinary interface otherwise.
Solution: Dynamic proxies.
When you do:
Supplier supplier = (Supplier) ObjectFactory.newInstance(
SupplierImpl.class
);
we should give you back an object that implements all of SupplierImpl's
interfaces, but also an ObjectFactory interface that we can use to
create
new instances of SupplierImpl.
This ObjectFactory interface will be used when combining the supplier
with
the composer, and the user who wants to use the returned SupplierImpl
via the
Supplier interface can do so as well.
The end result of this is in the code, and below I will do a short
walkthrough.
WALKTHROUGH
Component Usage
Below is the current demonstration class. It sets up a Supplier and a
Composer using that Supplier, and then invokes methods on the Composer.
public class MC
{
public static void main (String[] args) throws Exception
{
Supplier supplier = SupplierImpl.getInstance ();
Composer comp = ComposerImpl.getInstance (supplier);
comp.method();
MicroContainer.release (comp);
MicroContainer.release (supplier);
}
}
>From top to bottom:
+ You notice that the Supplier is retrieved via a static method in the
implementation class, and that the same goes for the Composer.
These are just convenience methods, and I'll show them in just a
moment.
+ The returned Composer instance is used just as you would expect to
use it.
+ Note that you are required to release components. This can be done
either by casting, or you can use a static method in MicroContainer
that will do the job for you.
Now, let's look at the SupplierImpl.getInstance() method:
public static Supplier getInstance()
{
return (Supplier) new MicroContainer( SupplierImpl.class )
.sharedInstance( true )
.create();
}
This command creates a new MicroContainer for SupplierImpl. It also
specifies that SupplierImpl can be shared among several clients -
this is, for example, when Composer does multiple lookups on it. In
terms
of the usage described in the case study above, the "factory" aspect of
the returned object will return the same singleton instance all the
time.
Here is the corresponding instance for ComposerImpl:
public static Composer getInstance( Supplier supplier )
{
return (Composer) new MicroContainer( ComposerImpl.class )
.addComponent( Supplier.ROLE, supplier
)
.sharedInstance( true )
.create();
}
Similar, but note the addComponent() call - this sets up supplier as the
component for the Supplier role.
Also note that the getInstance method is type-safe: You must pass it a
supplier. If MicroContainer had used generic ObjectFactories this would
not have been possible.
Impact on Component Developers
Developers can, if they feel like it, add a getInstance method for use
with MicroContainer. However, it is not needed.
Impact on Component Users
MicroContainer has no pooling of components, and provides no
pluggability
in itself. That is, when creating a container, you must specify the
implementation class.
These two tradeoffs were made since MicroContainer would otherwise grow
and become something like Fortress or ECM - a bit more than originally
envisioned.
--
To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>