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]