You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@avalon.apache.org by le...@apache.org on 2002/06/23 15:17:29 UTC
cvs commit: jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer Composer.java ComposerImpl.java Handler.java MC.java MicroContainer.java MicroContainerComponentManager.java MicroContainerProxy.java Supplier.java SupplierImpl.java
leosutic 2002/06/23 06:17:29
Added: src/proposal/microcontainer build.xml readme.txt
src/proposal/microcontainer/src/org/apache/avalon/microcontainer
Composer.java ComposerImpl.java Handler.java
MC.java MicroContainer.java
MicroContainerComponentManager.java
MicroContainerProxy.java Supplier.java
SupplierImpl.java
Log:
First attempt.
Revision Changes Path
1.1 jakarta-avalon/src/proposal/microcontainer/build.xml
Index: build.xml
===================================================================
<?xml version="1.0"?>
<project name="Avalon MicroContainer" default="main" basedir=".">
<!--
Give user a chance to override without editing this file
(and without typing -D each time he compiles it)
-->
<property file="ant.properties"/>
<property file="${user.home}/.ant.properties"/>
<property name="debug" value="on"/>
<property name="optimize" value="off"/>
<property name="deprecation" value="off"/>
<property name="compress.jars" value="true"/>
<!-- Set the properties for intermediate directory -->
<property name="build.dir" value="build"/>
<property name="build.lib" value="${build.dir}/lib"/>
<property name="build.classes" value="${build.dir}/classes"/>
<!-- Set the properties for source directories -->
<property name="src.dir" value="src"/>
<path id="project.class.path">
<pathelement location="${framework.jar}"/>
<pathelement path="${java.class.path}" />
<pathelement path="${build.classes}" />
</path>
<!-- Main target -->
<target name="main" depends="test" description="Builds the MicroContainer sample"/>
<!-- Prepares the build directory -->
<target name="prepare">
<tstamp/>
<mkdir dir="${build.dir}"/>
</target>
<!-- Compiles the source code -->
<target name="compile" depends="prepare" description="compiles the source code">
<mkdir dir="${build.classes}"/>
<javac srcdir="${src.dir}"
destdir="${build.classes}"
debug="${debug}"
optimize="${optimize}"
deprecation="${deprecation}"
target="1.3">
<classpath refid="project.class.path" />
</javac>
</target>
<target name="test" depends="compile">
<java classname="org.apache.avalon.microcontainer.MC"
fork="true">
<classpath refid="project.class.path" />
</java>
</target>
</project>
1.1 jakarta-avalon/src/proposal/microcontainer/readme.txt
Index: readme.txt
===================================================================
Leo Sutic <le...@inspireinfrastructure.com>:
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.
1.1 jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/Composer.java
Index: Composer.java
===================================================================
package org.apache.avalon.microcontainer;
import org.apache.avalon.framework.component.Component;
interface Composer extends Component {
public static final String ROLE = Composer.class.getName();
public void method ();
}
1.1 jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/ComposerImpl.java
Index: ComposerImpl.java
===================================================================
package org.apache.avalon.microcontainer;
import java.util.HashMap;
import java.util.Map;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.component.ComponentException;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.component.Composable;
public class ComposerImpl implements Composer, Composable, Disposable {
private ComponentManager manager;
public void compose( ComponentManager manager )
{
System.out.println( "In " + ComposerImpl.class.getName() + ".compose()" );
this.manager = manager;
}
public void dispose()
{
System.out.println( "In " + ComposerImpl.class.getName() + ".dispose()" );
}
public void method()
{
System.out.println( "In " + ComposerImpl.class.getName() + ".method()" );
Supplier supplier = null;
try
{
supplier = (Supplier) manager.lookup( Supplier.ROLE );
supplier.supplierMethod ();
}
catch (ComponentException ce)
{
// Ignore for clarity's sake.
}
finally
{
manager.release(supplier);
}
}
/**
* Helper method to compose MicroContainers.
*/
public static Composer getInstance( Supplier supplier )
{
return (Composer) new MicroContainer( ComposerImpl.class )
.addComponent( Supplier.ROLE, supplier )
.sharedInstance( true )
.create();
}
}
1.1 jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/Handler.java
Index: Handler.java
===================================================================
package org.apache.avalon.microcontainer;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.component.Composable;
/**
* InvocationHandler for MicroContainerProxies.
*/
class Handler implements InvocationHandler {
private Object instance;
private final ComponentManager manager;
private final Class componentClass;
private final boolean shared;
public Handler (Class componentClass, Map components, boolean shared) {
this.componentClass = componentClass;
this.manager = new MicroContainerComponentManager( components );
this.shared = shared;
this.instance = newInstance ();
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName ().equals( "MicroContainerProxy__release" ))
{
release();
return null;
}
else if (method.getName().equals( "MicroContainerProxy__releaseInstance" ))
{
if ( !shared )
{
release(args[0]);
}
return null;
}
else if (method.getName().equals ( "MicroContainerProxy__newInstance" ))
{
if ( shared )
{
return instance;
}
else
{
return newInstance();
}
}
return method.invoke(instance, args);
}
public Object newInstance () {
try
{
Object inst = componentClass.newInstance();
if (inst instanceof Composable)
{
((Composable) inst).compose( manager );
}
return inst;
}
catch( Exception e )
{
throw new RuntimeException( e.toString () );
}
}
public void release(Object o) {
if (o instanceof Disposable)
{
((Disposable) o).dispose ();
}
}
public void release() {
if (instance instanceof Disposable)
{
((Disposable) instance).dispose ();
}
}
}
1.1 jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/MC.java
Index: MC.java
===================================================================
package org.apache.avalon.microcontainer;
/**
* Demo.
*/
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);
}
}
1.1 jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/MicroContainer.java
Index: MicroContainer.java
===================================================================
package org.apache.avalon.microcontainer;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
/**
* Factory class for MicroContainers. Usage follows the named parameter idiom:
* <code>
* Component myComponent = (Component) new MicroContainer( ComponentImpl.class )
* .sharedInstance( true )
* .components( componentMap )
* .create();
* </code>
*/
public class MicroContainer {
private boolean sharedInstance = true;
private Map components = new HashMap ();
private final Class implementation;
/**
* Begin creation of a new MicroContainerProxy for the given component class.
* Note that you are not interested in this class, but the object you get when
* calling <code>create()</code>.
*/
public MicroContainer( Class implementation )
{
this.implementation = implementation;
}
/**
* Specifies whether the container should serve up the same instance to
* all composers requesting an instance. If the component is thread safe,
* you can set this to true. The default is <code>true</code>.
*/
public MicroContainer sharedInstance( boolean shared )
{
this.sharedInstance = shared;
return this;
}
/**
* Populates the ComponentManager for the component class.
* Each entry in the <code>components</code> map is assumed to be a
* mapping from String to MicroContainerProxies, obtained via this class.
*/
public MicroContainer components( Map components )
{
this.components = components;
return this;
}
public MicroContainer addComponent( String role, Object component )
{
components.put( role, component );
return this;
}
/**
* Creates a new MicroContainer proxy. The returned object implements all interfaces
* of the implementation class.
*/
public MicroContainerProxy create() throws RuntimeException
{
try
{
Class[] implementationInterfaces = implementation.getInterfaces ();
Class[] proxyInterfaces = new Class[implementationInterfaces.length + 1];
for (int i = 0; i < implementationInterfaces.length; i++)
{
proxyInterfaces[i + 1] = implementationInterfaces[i];
}
proxyInterfaces[0] = MicroContainerProxy.class;
return (MicroContainerProxy) Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
proxyInterfaces,
new Handler (implementation, components, sharedInstance)
);
}
catch (Exception e)
{
throw new RuntimeException( e.toString() );
}
}
/**
* Disposes of a MicroContainer proxy.
*/
public static void release(Object o)
{
if (o instanceof MicroContainerProxy)
{
((MicroContainerProxy) o).MicroContainerProxy__release();
}
}
}
1.1 jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/MicroContainerComponentManager.java
Index: MicroContainerComponentManager.java
===================================================================
package org.apache.avalon.microcontainer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.avalon.framework.component.Component;
import org.apache.avalon.framework.component.ComponentManager;
/**
* An implementation of the ComponentManager interface for
* MicroContainers.
*/
public class MicroContainerComponentManager implements ComponentManager {
private final HashMap handlers = new HashMap ();
private final HashMap used = new HashMap ();
public MicroContainerComponentManager( Map components ) {
Iterator iter = components.keySet().iterator();
while (iter.hasNext ())
{
String role = (String) iter.next ();
Object component = components.get (role);
// Verify that component is a MicroContainerProxy...
handlers.put (role, component);
}
}
public boolean hasComponent( String role ) {
return handlers.containsKey( role );
}
public Component lookup( String role ) {
MicroContainerProxy handler = (MicroContainerProxy) handlers.get( role );
Component component = (Component) handler.MicroContainerProxy__newInstance();
used.put( component, handler );
return component;
}
public void release( Component instance ) {
MicroContainerProxy handler = (MicroContainerProxy) used.remove( instance );
handler.MicroContainerProxy__releaseInstance( instance );
}
}
1.1 jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/MicroContainerProxy.java
Index: MicroContainerProxy.java
===================================================================
package org.apache.avalon.microcontainer;
/**
* The "factory" interface for proxies. The method names have been
* chose so as to not conflict with any known interface.
*/
interface MicroContainerProxy {
/**
* Create a new instance of the managed component.
*/
public Object MicroContainerProxy__newInstance();
/**
* Release the active instance of the component.
*/
public void MicroContainerProxy__release();
/**
* Release an instance created via MicroContainerProxy__newInstance().
*/
public void MicroContainerProxy__releaseInstance(Object instance);
}
1.1 jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/Supplier.java
Index: Supplier.java
===================================================================
package org.apache.avalon.microcontainer;
import org.apache.avalon.framework.component.Component;
interface Supplier extends Component {
public static final String ROLE = Supplier.class.getName();
public void supplierMethod ();
}
1.1 jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/SupplierImpl.java
Index: SupplierImpl.java
===================================================================
package org.apache.avalon.microcontainer;
import java.util.HashMap;
import java.util.Map;
import org.apache.avalon.framework.activity.Disposable;
public class SupplierImpl implements Supplier, Disposable {
public void supplierMethod()
{
System.out.println( "In " + SupplierImpl.class.getName() + ".supplierMethod()" );
}
public void dispose()
{
System.out.println( "In " + SupplierImpl.class.getName() + ".dispose()" );
}
public static Supplier getInstance()
{
return (Supplier) new MicroContainer( SupplierImpl.class ).sharedInstance( true ).create();
}
}
--
To unsubscribe, e-mail: <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>