Creating a Logging Interceptor
One of the most powerful features of Gaderian is the ability to create interceptors for services. Interceptors
provide additional behavior to a service, often a cross-cutting concern such as logging or transaction management.
Interceptors can be thought of as "aspect oriented programming lite".
This example shows how easy it can be to create an interceptor; it creates a simplified version of the
standard gaderian.LoggingInterceptor.
The real logging interceptor uses the Javassist
bytecode enhancement framework to create a new class at runtime. This has some minor advantages in terms of runtime performance,
but is much more complicated to implement and test than this example, which uses
JDK Dynamic Proxies.
The Interceptor Factory
Interceptors are created by interceptor factories, which are themselves Gaderian services. Interceptor factories
implement the ServiceInterceptorFactory interface.
Our implementation is very simple:
package org.apache.examples.impl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.List;
import org.apache.commons.logging.Log;
import org.ops4j.gaderian.InterceptorStack;
import org.ops4j.gaderian.ServiceInterceptorFactory;
import org.ops4j.gaderian.internal.Module;
public class ProxyLoggingInterceptorFactory implements ServiceInterceptorFactory
{
public void createInterceptor(InterceptorStack stack, Module invokingModule, List parameters)
{
Log log = stack.getServiceLog();
InvocationHandler handler = new ProxyLoggingInvocationHandler(log, stack.peek());
Object interceptor =
Proxy.newProxyInstance(
invokingModule.getClassResolver().getClassLoader(),
new Class[] { stack.getServiceInterface()},
handler);
stack.push(interceptor);
}
}
The createInterceptor() method is passed the InterceptorStack, the Module of the
invoking module (the module containing the service being created), and any parameters passed to the interceptor
(from inside the <interceptor> element). This example does not make use of parameters, but the real logging
interceptor uses parameters to control which methods are, and are not, logged.
An interceptor's job is to peek() at the top object on the stack and create a new object, wrapped
around the top object, that provides new behavior. The top object on the stack may be the core service
implementation, or it may be another interceptor ... all that's known for sure is that it implements the service
interface defined by the <service-point>.
The interceptor in this case is a dynamic proxy, provided by Proxy.newProxyInstance(). The key here
is the invocation handler, and object (described shortly) that is notified any time a method on the
interceptor proxy is invoked.
Once the interceptor is created, it is pushed onto the stack. More interceptors may build upon it, adding yet
more behavior.
In Gaderian, a single Log instance is used when constructing a service as well as by any
interceptors created for the service. In other words, by enabling logging for a particular service id, you will see
log events for every aspect of the construction of that particular service. If you add a logging interceptor,
you'll also see method invocations. To ensure that logging takes place using the single logging instance, neither
the class nor the interceptor factory is responsible for creating the logging instance ... that's the
responsibility of Gaderian. The logging instance to use is provided by the getServiceLog() method of
the InterceptorStack instance provided to the interceptor factory.
Invocation Handler
The invocation handler is where the intercepting really takes place; it is invoked every time a method of the
proxy object is invoked and has a chance to add behavior before, and after (or even instead of!) invoking a method
on the next object in the stack. What is the "next object"? It's the next object in the interceptor stack, and may be
another interceptor instance, or may be the core service implementation.
package org.apache.examples.impl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.ops4j.gaderian.service.impl.LoggingUtils;
public class ProxyLoggingInvocationHandler implements InvocationHandler
{
private Log _log;
private Object _delegate;
public ProxyLoggingInvocationHandler(Log log, Object delegate)
{
_log = log;
_delegate = delegate;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
boolean debug = _log.isDebugEnabled();
if (debug)
LoggingUtils.entry(_log, method.getName(), args);
try
{
Object result = method.invoke(_delegate, args);
if (debug)
{
if (method.getReturnType() == void.class)
LoggingUtils.voidExit(_log, method.getName());
else
LoggingUtils.exit(_log, method.getName(), result);
}
return result;
}
catch (InvocationTargetException ex)
{
Throwable targetException = ex.getTargetException();
if (debug)
LoggingUtils.exception(_log, method.getName(), targetException);
throw targetException;
}
}
}
The invoke() method is the key. Using the remembered Log instance,
and the remembered delegate object (the next object on the interceptor stack ... the object that
was peek()-ed). The code for actually generating the logging output is inside static
methods of the LoggingUtils utility class -- in this way the output from this interceptor is
identical to the output when using gaderian.LoggingInterceptor.
The invoke() method is invoked for all methods that can be invoked on the proxy ... this includes
the methods of the service interface, but also includes java.lang.Object methods such
as hashCode() or toString().
Note
The
gaderian.LoggingInterceptor
will typically add its own implementation of
toString()
, to assist
in debugging (it clearly identifies itself as an interceptor object, and identifies the service id and
service interface).
This proxy-based implementation does not, so invoking
toString()
on the proxy will end up invoking the method
on the next object.
Declaring the interceptor factory
Like any other service, an service interceptor factory must appear inside a Gaderian module deployment descriptor:
<service-point id="ProxyLoggingInterceptor" interface="org.ops4j.gaderian.ServiceInterceptorFactory">
<create-instance class="org.apache.examples.impl.ProxyLoggingInterceptorFactory"/>
</service-point>
Using the interceptor
Using the interceptor is the same as using any other interceptor; the <interceptor> element simply has to point at the correct service:
<module id="examples" version="1.0.0" package="org.apache.examples">
. . .
<service-point id="ProxyLoggingInterceptor" interface="org.ops4j.gaderian.ServiceInterceptorFactory">
<create-instance class="impl.ProxyLoggingInterceptorFactory"/>
</service-point>
<service-point id="Target" interface="TargetService">
<create-instance class="impl.TargetServiceImpl"/>
<interceptor service-id="ProxyLoggingInterceptor"/>
</service-point>
</module>
The TargetService interface defines three methods used to demonstrate the logging interceptor:
package org.apache.examples;
import java.util.List;
public interface TargetService
{
public void voidMethod(String string);
public List buildList(String string, int count);
public void exceptionThrower();
}
The implementation class is equally inspiring:
package org.apache.examples.impl;
import java.util.ArrayList;
import java.util.List;
import org.ops4j.gaderian.ApplicationRuntimeException;
import org.apache.examples.TargetService;
public class TargetServiceImpl implements TargetService
{
public void voidMethod(String string)
{
}
public List buildList(String string, int count)
{
List result = new ArrayList();
for (int i = 0; i < count; i++)
result.add(string);
return result;
}
public void exceptionThrower()
{
throw new ApplicationRuntimeException("Some application exception.");
}
Running the examples
From the examples directory, run ant compile, then run ant -emacs run-logging:
bash-2.05b$ ant -emacs run-logging
Buildfile: build.xml
run-logging:
Target [DEBUG] Creating SingletonProxy for service examples.Target
*** Void method (no return value):
Target [DEBUG] Constructing core service implementation for service examples.Target
Target [DEBUG] Applying interceptor factory examples.ProxyLoggingInterceptor
ProxyLoggingInterceptor [DEBUG] Creating SingletonProxy for service examples.ProxyLoggingInterceptor
ProxyLoggingInterceptor [DEBUG] Constructing core service implementation for service examples.ProxyLoggingInterceptor
Target [DEBUG] BEGIN voidMethod(Hello)
Target [DEBUG] END voidMethod()
*** Ordinary method (returns a List):
Target [DEBUG] BEGIN buildList(Gaderian, 4)
Target [DEBUG] END buildList() [[Gaderian, Gaderian, Gaderian, Gaderian]]
*** Exception method (throws an exception):
Target [DEBUG] BEGIN exceptionThrower()
Target [DEBUG] EXCEPTION exceptionThrown: org.ops4j.gaderian.ApplicationRuntimeException
org.ops4j.gaderian.ApplicationRuntimeException: Some application exception.
at org.apache.examples.impl.TargetServiceImpl.exceptionThrower(TargetServiceImpl.java:35)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at org.apache.examples.impl.ProxyLoggingInvocationHandler.invoke(ProxyLoggingInvocationHandler.java:38)
at $Proxy0.exceptionThrower(Unknown Source)
at $SingletonProxy_fe67f7e0ae_12.exceptionThrower($SingletonProxy_fe67f7e0ae_12.java)
at org.apache.examples.LoggingMain.main(LoggingMain.java:27)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at org.apache.tools.ant.taskdefs.ExecuteJava.run(ExecuteJava.java:193)
at org.apache.tools.ant.taskdefs.ExecuteJava.execute(ExecuteJava.java:130)
at org.apache.tools.ant.taskdefs.Java.run(Java.java:705)
at org.apache.tools.ant.taskdefs.Java.executeJava(Java.java:177)
at org.apache.tools.ant.taskdefs.Java.execute(Java.java:83)
at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:275)
at org.apache.tools.ant.Task.perform(Task.java:364)
at org.apache.tools.ant.Target.execute(Target.java:341)
at org.apache.tools.ant.Target.performTasks(Target.java:369)
at org.apache.tools.ant.Project.executeTarget(Project.java:1214)
at org.apache.tools.ant.Project.executeTargets(Project.java:1062)
at org.apache.tools.ant.Main.runBuild(Main.java:673)
at org.apache.tools.ant.Main.startAnt(Main.java:188)
at org.apache.tools.ant.launch.Launcher.run(Launcher.java:196)
at org.apache.tools.ant.launch.Launcher.main(Launcher.java:55)
BUILD SUCCESSFUL
Total time: 3 seconds
The log4j.properties file for the examples has enabled debug logging for the entire module; thus we see
some output about the construction of the ProxyLoggingInterceptor service as it is employed to construct the
interceptor for the Target service.
Conclusion
Implementing a basic interceptor using Gaderian is very simple when using JDK Dynamic Proxies. You can easily provide code that slips
right into the calling sequence for the methods of your services with surprisingly little code. In addition, the APIs do not force you
to use any single approach; you can use JDK proxies as here, use Javassist, or use any approach that works for you. And because interceptor
factories are themselves Gaderian services, you have access to the entire Gaderian environment to implement your interceptor factories.