2010-01-04

Spring. NET AOP - behind the scenes (2)

This is the second installment of my series about the internals of Spring.NET AOP. In my first post of this series I described the first step in separating concerns and how we can use the decorator pattern to make our code much better maintainable. Nevertheless we were still left with two major problems:

  • Changing an interface to our service requires all decorators to be changed
  • Lack of reusing decorator logic for other services

A more generic approach: Interceptors

As mentioned, we can do better. Assume we have other services in our application. We do not want to implement the caching or retry code each time and for every method. Code duplication is the root of all evil. Thus we need to improve our solution. First, let's refactor our retry code into a separate class, that can be used to wrap any arbitrary method call. We will introduce a special interface for these added behaviors and call them interceptors:

public interface IMethodInterceptor
{
 object InvokeMethod(Func<object> invokeNext);
}

An interceptor implementation gets passed in a delegate. This delegate can either be the method we are wrapping or even another interceptor. The method we are wrapping is usually called the target method. When the delegate is another interceptor, we refer to this as an interceptor chain, with each interceptor adding its own behavior before calling the target method. Our Retry-interceptor then looks like this:

public class RetryInterceptor : IMethodInterceptor
{
 public object InvokeMethod(Func<object> invokeNext)
 {
   int retries = 0;
   while (true)
   {
     try
     {
       return invokeNext();
     }
     catch (Exception ex)
     {
       retries++;
       if (retries >= 3)
       {
         throw; // retry threshold exceeded, giving up
       }
       Thread.Sleep(1000); // wait a second
     }
   }
 }
}

Notice, that our RetryInterceptor now is absolutely unaware of the target method or its arguments. It just gets passed in a delegate to invoke, which hands over control to either the next interceptor in the chain or the actual target method.

Now we can implement our interceptor chain in a rather generic way, we will call it our CalculatorProxy and hand it our actual target instance and a list of interceptors. The actual method invocation is a bit tricky, but doable:

public class CalculatorProxy : ICalculator
{
 private ICalculator target;
 private IMethodInterceptor[] interceptors;

 public CalculatorProxy(ICalculator target, IMethodInterceptor[] interceptors)
 {
   this.target = target;
   this.interceptors = interceptors;
 }

 public int Add(int x, int y)
 {
   return (int) InvokeInterceptorAtIndex(0, ()=>target.Add(x, y));
 }

 private object InvokeInterceptorAtIndex(int interceptorIndex, Func<object> targetMethod)
 {
   if (interceptorIndex >= interceptors.Length)
   {
     return targetMethod();
   }
   return interceptors[interceptorIndex].InvokeMethod(
           () => InvokeInterceptorAtIndex(interceptorIndex + 1, targetMethod)
       );
 }
}

This proxy implementation accepts calls coming in over the ICalculator.Add() interface method and passes control to the first interceptor in the chain. Initialize our proxy like this:

ICalculator calc = new CalculatorProxy(
                       new CalculatorWebService(),
                       new IMethodInterceptor[] { new RetryInterceptor() });
int sum = calc.Add(2, 5);

Each interceptor will invoke the next interceptor in the list if available or the final target:

calculatorproxy_callgraph

We have just implemented a mechanism that allows us to add any arbitrary number of interceptors to our proxy!

Generating Proxies - Spring's ProxyFactory

Of course we implemented our interception mechanism for one method only yet. Assume we have to extend the capabilities of our remote calculator and add a new Divide() method. How would the proxy implementation of this method look like? Look at this:

public double Divide(double dividend, double divisor)
{
 return (int)InvokeNext(0, () => target.Divide(dividend, divisor));
}

Comparing this implementation to our previous Add() implementation, the pattern becomes obvious. Both methods hand control to the first interceptor and pass in a delegate to the actual method invocation. No matter how many methods we add, this code will always look the same. Let's refactor the common code for invoking the interceptor chain into a base class AbstractProxy:

public abstract class AbstractProxy
{
 private IMethodInterceptor[] interceptors;

 public AbstractProxy(IMethodInterceptor[] interceptors)
 {
   this.interceptors = interceptors;
 }

 protected object InvokeInterceptorAtIndex(int interceptorIndex, Func<object> targetMethod)
 {
   if (interceptorIndex >= interceptors.Length)
   {
     return targetMethod();
   }
   return interceptors[interceptorIndex].InvokeMethod(
       () => InvokeInterceptorAtIndex(interceptorIndex + 1, targetMethod)
     );
 }
}

Our CalculatorProxy then will be:

public class CalculatorProxy : AbstractProxy, ICalculator
{
 private readonly ICalculator target;

 public CalculatorProxy(ICalculator target, IMethodInterceptor[] interceptors)
   : base(interceptors)
 {
   this.target = target;
 }

 public int Add(int x, int y)
 {
   return (int)InvokeInterceptorAtIndex(0, () => target.Add(x, y));
 }

 public double Divide(double dividend, double divisor)
 {
   return (int)InvokeInterceptorAtIndex(0, () => target.Divide(dividend, divisor));
 }
}

Even if we implemented other interfaces, these lines would be the same. Luckily, Castle.DynamicProxy, LinFu and of course Spring.NET come with mechanisms that allow this code to be generated at runtime. Below I will show you, how you can use Spring.NET's ProxyFactory to automatically generate this proxy code.

First, to make our interceptor compatible to Spring.NET, we need to implement Spring.NET's IMethodInterceptor interface for our interceptors (instead of our own IMethodInterceptor interface as described above):

public class RetryInterceptor : IMethodInterceptor // Spring.NET's interceptor interface!
{
 public object Invoke(IMethodInvocation invocation)
 {
   int retries = 0;
   while (true)
   {
     try
     {
       return invocation.Proceed();
     }
     catch (Exception ex)
     {
       retries++;
       if (retries >= 3)
       {
         throw; // retry threshold exceeded, giving up
       }
       Thread.Sleep(1000); // wait a second
     }
   }
 }
}

Notice, that instead of getting passed in a method delegate, Spring.NET's IMethodInterceptor.Invoke() receives an instance of IMethodInvocation and calls Proceed() to hand control to the next interceptor or target method in the chain. In contrast to the simple delegate that we used in our manually coded proxy, the IMethodInvocation interface allows you to access additional information about the current method call:

public interface IMethodInvocation
{
 /// <summary>
 /// Get the proxy instance for the current method invocation
 /// </summary>
 object Proxy { get; }
 /// <summary>
 /// Get the target instance for the current method invocation
 /// </summary>
 object Target { get; }
 /// <summary>
 /// Get the method info for the current method invocation
 /// </summary>
 MethodInfo Method { get; }
 /// <summary>
 /// Get the arguments for the current method invocation
 /// </summary>
 object[] Arguments { get; }
 /// <summary>
 /// Hand control to next interceptor or target
 /// </summary>
 object Proceed();
}

Now that we have our interceptors ready, we can use Spring.NET's ProxyFactory to have the proxy class automatically generated for us. Note, that in the example below the method used to add an interceptor is called "AddAdvice". Advice is the common term to refer to an action to be taken before or after calling the actual target. For a more detailled description of common AOP terminology, please refer to the Spring.NET AOP reference. Here's the code to let Spring.NET generate such a proxy instance for us:

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.Target = new CalculatorWebService(); // set target
proxyFactory.AddAdvice( new RetryInterceptor() ); // add interceptor

ICalculator calc = (ICalculator) proxyFactory.GetProxy(); // create proxy instance

The call to GetProxy() will generate a proxy instance, set the target and add the list of interceptors to the proxy. The result is the same structure we showed above and also the call graph of an interceptor chain looks very familiar:

calculatorproxyfactory_callgraph

The difference is, that we didn't have to write a single line of code for our proxy. This means that we now easily can reuse our RetryInterceptor across all kinds of classes in our application!

We want more!

Wow, what a journey. We started by separating concerns into their own classes by applying the decorator pattern, implemented our own proxy with a flexible method interception mechanism and finally saw, how Spring.NET's ProxyFactory can be used to do all of this for us automatically.

Is there anything left on our wishlist? Imagine we have a whole bunch of Service classes in our application that we would like to retry their operations. We'd have to execute the lines to create the proxy, set the target and add the interceptors for each service individually. What about a feature that allows you to just point to a list of objects and tell Spring.NET to wrap those objects in a proxy with a given list of interceptors?

Indeed the AOP framework of Spring.NET comes with a great feature called "AutoProxying" that makes it easy to automatically apply interceptors to a set of objects. Stay tuned to read how this feature works in the next post of this series!

As usual you can download the example code.

5 comments:

Ariel Serlin said...

Sorry, the code sample is not available, can you upload it again?
Thanks!
Ariel

Salman said...

Thank you !! Very usefull !!
dot net training in chennai

Manoj said...

Thank you so much for this good information!
IEEE project centre is a fast growing company has maintained it is leadership in imparting quality services. We provide the mini projects in chennai and cse mini projects in chennai for all college students. As per their own interest and own requirements we deliver all the projects at right time.

Jackie Co Kad said...

Great Article
Internet of Things Final Year Project
Final Year Project Centers in Chennai

Learn Bench India said...

very nice article.keep sharing like this.project center in chennai|best final year ieee project center in chennai