hlship 2003/09/11 13:52:05
Modified: hivemind/xdocs navigation.xml
Added: hivemind/xdocs interceptors.xml
Log:
Add documentation on creating new interceptors.
Revision Changes Path
1.19 +2 -1 jakarta-commons-sandbox/hivemind/xdocs/navigation.xml
Index: navigation.xml
===================================================================
RCS file: /home/cvs/jakarta-commons-sandbox/hivemind/xdocs/navigation.xml,v
retrieving revision 1.18
retrieving revision 1.19
diff -u -r1.18 -r1.19
--- navigation.xml 10 Sep 2003 16:54:52 -0000 1.18
+++ navigation.xml 11 Sep 2003 20:52:05 -0000 1.19
@@ -23,6 +23,7 @@
<item name="Localization" href="/localization.html"/>
<item name="Inversion of Control" href="/ioc.html"/>
<item name="Multi-Threading" href="/multithreading.html"/>
+ <item name="Creating New Interceptors"
href="/interceptors.html"/>
<item name="Overriding Services" href="/override.html"/>
<item name="Case Study #1: Application Startup/Shutdown"
href="case1.html"/>
1.1 jakarta-commons-sandbox/hivemind/xdocs/interceptors.xml
Index: interceptors.xml
===================================================================
<?xml version="1.0"?>
<!-- $Id: interceptors.xml,v 1.1 2003/09/11 20:52:05 hlship Exp $ -->
<!DOCTYPE document [
<!ENTITY % common-links SYSTEM "../common/links.xml">
%common-links;
]>
<document>
<properties>
<title>Creating New Interceptors</title>
<author email="[EMAIL PROTECTED]">Howard M. Lewis Ship</author>
</properties>
<body>
<section name="Introduction">
<p>
Interceptors are used to add behavior to a HiveMind service after the fact. An
interceptor sits between
the client code and the core service implementation; it implements the service
interface. For each method
in the service interface, the interceptor will re-invoke the method on the
next object in the chain ... either another interceptor, or the core service
implementation.
</p>
<p>
That's not useful ... but when the interceptor does something before and/or after
re-invoking the
method, it can easily add quite a bit of useful, robust functionality.
</p>
<p>
In fact, if you've heard about "Aspect Oriented Programming", interceptors are
simply one kind of aspect, a
method introduction, based on service interface.
</p>
<p>
Be warned; interceptors are an example of programs writing other programs; it's a
whole new level of abstraction and
requires a bit of getting used to.
</p>
</section>
<section name="Interceptor Factories">
<p>
Interceptors are created, at runtime, by interceptor factories. An interceptor
factory builds a custom class at runtime
using the Javassist library. The class is then instantiated.
</p>
<p>
Interceptor factories are HiveMind services which implement the
<a
href="apidocs/org/apache/commons/hivemind/ServiceInterceptorFactory.html">ServiceInterceptorFactory</a>
interface. This interface has a single method, <code>createInterceptor()</code>,
which is passed:
<ul>
<li>The <a
href="apidocs/org/apache/commons/hivemind/InterceptorStack.html">InterceptorStack</a>
(an object used to manage the process of creating interceptors for a service)</li>
<li>The <a href="apidocs/org/apache/commons/hivemind/Module.html">Module</a> which
invoked the interceptor factory</li>
<li>A list of parameters</li>
</ul>
</p>
<p>
Like service implementation factories, interceptor factories may take parameters;
they may define a ¶meters-schema;
which is used to convert any XML enclosed by the &interceptor; element into Java
objects. Many interesting
interceptors can be created without needing parameters to guide the fabrication of
the interceptor class.
</p>
</section>
<section name="Implementing the NullInterceptor">
<p>
To demonstrate how easy it is to create an interceptor, we'll start with a
NullInterceptor. NullInterceptor
does not add any functionality, it simply re-invokes each method on its
<em>inner</em>. The <em>inner</em> is
the next interceptor, or the core service implementation ... an interceptor doesn't
know or care which.
</p>
<p>
Simple interceptors, those which do not take any parameters, are implemented by
subclassing
<a
href="apidocs/org/apache/commons/hivemind/service/impl/AbstractServiceInterceptorFactory.html">AbstractServiceInterceptorFactory</a>.
It does most of the work, organizing the process of creating the class and methods
... even adding a
<code>toString()</code> method implementation automatically.
</p>
<subsection name="NullInterceptor Class">
<p>
Most of the work for creating a standard service interceptor factory is taken care
of by the AbstractServiceInterceptorFactory
base class. All that's left is to define what happens for each method in the
service interface.
<source><![CDATA[
package com.example.impl;
import java.lang.reflect.Modifier;
import org.apache.commons.hivemind.service.BodyBuilder;
import org.apache.commons.hivemind.service.ClassFab;
import org.apache.commons.hivemind.service.impl.AbstractServiceInterceptorFactory;
public class NullInterceptor extends AbstractServiceInterceptorFactory
{
protected void addServiceMethodImplementation(
ClassFab classFab,
String methodName,
Class returnType,
Class[] parameterTypes,
Class[] exceptionTypes)
{
BodyBuilder builder = new BodyBuilder();
builder.begin();
builder.add("return ($r) _inner.");
builder.add(methodName);
builder.add("($$);");
builder.end();
classFab.addMethod(
Modifier.PUBLIC,
methodName,
returnType,
parameterTypes,
exceptionTypes,
builder.toString());
}
}
]]></source>
</p>
<p>
The <code>addServiceMethodImplementation()</code> method is invoked for each
service method.
It is passed the
<a href="apidocs/org/apache/commons/hivemind/service/ClassFab.html">ClassFab</a>, an
object which represents a class being fabricated, which allows new fields, methods and
constructors
to be added.
</p>
<p>
ClassFab and friends are just a wrapper around the Javassist framework, a library
used for runtime bytecode enhancement and
other aspect oriented programming tasks. HiveMind uses only a small fraction of the
capabilities of Javassist.
Javassist's greatest feature is how new code is specified ... as enhanced Java code!
</p>
<p>
The
<a
href="apidocs/org/apache/commons/hivemind/service/BodyBuilder.html">BodyBuilder</a>
class helps
with assembling method bodies in bits and pieces. The <code>_inner</code> variable
is a private instance variable,
the inner for this interceptor. The <code>($r)</code> reference means "cast to the
return type for this method", and properly handles
void methods. The <code>$$</code> is a placeholder for a comma-seperated list of
all the parameters to the method.
</p>
<p>AbstractServiceInterceptorFactory is responsible for creating the
<code>_inner</code> variable and
building the constructor which sets it, as well as invoking the constructor on
the
completed interceptor class.
</p>
</subsection>
<subsection name="Declaring the Service">
<p>
To use a service, it is necessary to declare the service in a module deployment
descriptor. The
AbstractServiceInterceptorFactory expects two properties to be set when the service
is constructed,
<code>extensionId</code> and <code>factory</code>:
<source><![CDATA[
<service id="NullInterceptor"
interface="org.apache.commons.hivemind.ServiceInterceptorFactory">
<invoke-factory service-id="hivemind.BuilderFactory">
<construct class="com.example.impl.NullInterceptor"
point-id-property="extensionId">
<set-service property="factory"
service-id="hivemind.ClassFactory"/>
</construct>
</invoke-factory>
</service>
]]></source>
</p>
</subsection>
</section>
<section name="Implementing the hivemind.LoggingInterceptor service">
<p>
A more involved example is the LoggingInterceptor service, which adds logging
capabilities to
services. It's a bit more involved than NullInterceptor, and so overrides more
methods of
AbstractServiceInterceptorFactory.
</p>
<subsection name="AbstractLoggingInterceptor base class">
<p>
In most cases, an abstract base class for the interceptor is provided; in this case,
it is
<a
href="apidocs/org/apache/commons/hivemind/service/impl/AbstractLoggingInterceptor.html">AbstractLoggingInterceptor</a>.
This class provides several protected methods used by fabricated interceptors. To
help ensure
that there are no conflicts between the method of the service interface and the
methods
provided by the super-class, the provided methods are named with a leading
underscore. These methods are:
<ul>
<li><code>_logEntry()</code> to log entry to a method</li>
<li><code>_logExit()</code> to log exit from a method, with return value</li>
<li><code>_logVoidExit()</code> to log exit from a void method (no return value)</li>
<li><code>_logException()</code> to log an exception thrown when the method is
executed</li>
<li><code>_isDebugEnabled()</code> to determine if debugging is enabled or
disabled</li>
</ul>
</p>
<p>
In addition, there's a protected constructor, which takes an instance of
<code>org.apache.commons.logging.Log</code>
that must be invoked from the fabricated subclass.
</p>
<p>
Method <code>getInterceptorSuperclass()</code> is used to tell
AbstractServiceInterceptorFactory which class to use as the base:
<source>
protected Class getInterceptorSuperclass()
{
return AbstractLoggingInterceptor.class;
}
</source>
</p>
</subsection>
<subsection name="Creating the infrastructure">
<p>
The method <code>createInfrastructure()</code> is used to add fields and
constructors to the
interceptor class.
<source>
protected void createInfrastructure(InterceptorStack stack, ClassFab classFab)
{
Class topClass = stack.peek().getClass();
classFab.addField("_inner", topClass);
classFab.addConstructor(
new Class[] { Log.class, topClass },
null,
"{ super($1); _inner = $2; }");
}
</source>
</p>
<p>
Since, when a interceptor is created, the inner object has already been created, we
can
use its <em>actual type</em> for the <code>_inner</code> field. This results in a
much more
efficient method invocation than if <code>_inner</code>'s type was the service
interface.
</p>
</subsection>
<subsection name="Instantiating the Instance">
<p>
The method <code>instantiateInterceptor()</code> is used to create a new instance
from the fully fabricated class.
<source>
protected Object instantiateInterceptor(InterceptorStack stack, Class
interceptorClass)
throws Exception
{
Object stackTop = stack.peek();
Class topClass = stackTop.getClass();
Log log =
LogFactory.getLog(stack.getServiceExtensionPoint().getExtensionPointId());
Constructor c = interceptorClass.getConstructor(new Class[] { Log.class,
topClass });
return c.newInstance(new Object[] { log, stackTop });
}
</source>
</p>
<p>
This implementation gets the top object from the stack (the inner object for this
interceptor) and the correct <code>Log</code> instance (based on the service
extension point id ...
for the service being extended with the interceptor). The constructor, created
by <code>createInfrastructure()</code> is accessed and invoked to create the
interceptor.
</p>
</subsection>
<subsection name="Adding a Service Methods">
<p>
The last, and most complex, part of this is the method which actually creates
each
service method.
<source><![CDATA[
protected void addServiceMethodImplementation(
ClassFab classFab,
String methodName,
Class returnType,
Class[] parameterTypes,
Class[] exceptions)
{
boolean isVoid = (returnType == void.class);
BodyBuilder builder = new BodyBuilder();
builder.begin();
builder.addln("boolean debug = _isDebugEnabled();");
builder.addln("if (debug)");
builder.add(" _logEntry(");
builder.addQuoted(methodName);
builder.addln(", $args);");
if (!isVoid)
{
builder.add(ClassFabUtils.getJavaClassName(returnType));
builder.add(" result = ");
}
builder.add("_inner.");
builder.add(methodName);
builder.addln("($$);");
if (isVoid)
{
builder.addln("if (debug)");
builder.add(" _logVoidExit(");
builder.addQuoted(methodName);
builder.addln(");");
}
else
{
builder.addln("if (debug)");
builder.add(" _logExit(");
builder.addQuoted(methodName);
builder.addln(", ($w)result);");
builder.addln("return result;");
}
builder.end();
MethodFab methodFab =
classFab.addMethod(
Modifier.PUBLIC,
methodName,
returnType,
parameterTypes,
exceptions,
builder.toString());
builder.clear();
builder.begin();
builder.add("_logException(");
builder.addQuoted(methodName);
builder.addln(", $e);");
builder.addln("throw $e;");
builder.end();
String body = builder.toString();
int count = exceptions == null ? 0 : exceptions.length;
for (int i = 0; i < count; i++)
{
methodFab.addCatch(exceptions[i], body);
}
// Catch and log any runtime exceptions, in addition to the
// checked exceptions.
methodFab.addCatch(RuntimeException.class, body);
}
]]></source>
</p>
<p>
When you implement logging in your own classes, you often invoke the
method <code>Log.isDebugEnabled()</code> multiple times ... but in the fabricated
class,
the method is only invoked once and cached for the duration of the call ... a little
efficiency gained back.
</p>
<p>
Likewise, if a method can throw an exception or return from the middle, its hard to
be assured that you've logged
every exit, or overy thrown exception; taking this code out into an interceptor
class ensures that
its done consistently and properly.
</p>
</subsection>
</section>
<section name="Implementing Interceptors with Parameters">
<p>
Interceptor factories may take parameters ... but then their implementation can't be
based
on AbstractServiceInterceptorFactory. The unit tests for HiveMind includes an
<a
href="xref-test/hivemind/test/services/impl/FilterLoggingInterceptor.html">example</a>
of
such a factory. The basic approach is the same ... you just need a little extra work
to
validate, interpret and use the parameters.
</p>
<p>
When would such as thing be useful? One example is declarative security; you could
specify, on a
method-by-method basis, which methods were restricted to which roles.
</p>
</section>
<section name="Conclusion">
<p>
Interceptors, and interceptor factories, are a powerful concept that allow you to
add consistent, efficient,
robust behavior to your services. It takes a little while to wrap your brain around
the idea of
classes writing the code for other classes ... but once you do, a whole world of
advanced techniques
opens up to you!
</p>
</section>
</body>
</document>
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]