P A R T  2

images

Applying the DLR

C H A P T E R  7

images

DLR and Aspect-Oriented Programming

Dynamic objects in the DLR provide a foundation that has many applications. In previous chapters, we’ve seen some practical examples that leverage this foundation and do interesting things that are either awkward or impossible in static languages. In this chapter, we are going to travel further down the path and see the fantastic application of dynamic objects in aspect-oriented programming (AOP). AOP is a programming paradigm that is very good at solving the problem of cross-cutting concerns. Common cross-cutting concerns in a software system are issues like transaction management, security, auditing, performance monitoring, logging and tracing, and so on. By virtue of addressing the problem of cross-cutting concerns in an elegant manner, AOP provides tremendous value in the design and architecture of software systems. I’ll begin with an introduction of the basic AOP concepts accompanied by some simple examples, then show you how to implement an AOP framework based on dynamic objects. By the end of the chapter, you will have an AOP framework that (a) works across both static and dynamic objects and (b) is integrated with the widely adopted Spring.NET’s AOP framework.

Aspect-Oriented Programming

Let’s go over the important concepts of AOP now to set the stage for the rest of the chapter. After reading this section, you will know what AOP is and the problem it solves. You will also learn the meaning of terms such as pointcut, join point, and advice. If you are already familiar with these topics, you can skip this section and jump ahead.

Cross-Cutting Concerns

AOP solves the issue of cross-cutting concerns very well. This is best illustrated with an example. Listing 7-1 shows some code that logs one message at the beginning and one at the end of the Age property’s get method. The real business logic of the property’s get method is represented by the code comment //some business logic here. The two lines that write to the console belong to the logging concern. The code by itself might not seem to be a problem, but imagine how the code would look if we were to do this kind of logging for all property access in 20 other classes. You would quickly notice that the same code that writes messages to the console output is duplicated and scattered all over the place. That naïve approach violates the DRY (Don’t Repeat Yourself) principle and creates the problem of code scattering. Furthermore, the code in listing 7-1 also illustrates the problem of code tangling because the logging code and the real business logic are enmeshed.

The code scattering and tangling are the kinds of problems AOP solves. Logging is just an example of a cross-cutting concern we commonly encounter in a software system, and it’s the easiest and simplest to demonstrate. From this simple example, you can extrapolate and see that if this were an example of transaction management, the two lines of logging code would be replaced by a line of code that starts a transaction and another line of code that commits or rolls back the transaction.   

Listing 7-1. Logging Messages Before and After a Property Access

public class Employee : IEmployee
{
    private int age;
    private String name;

    public int Age
    {
        get
        {
            Console.WriteLine("Employee Age getter is called.");
            //some business logic here.
            Console.WriteLine("End of Employee Age getter.");
            return age;
        }
        set { age = value; }
    }

    public String Name
    {
        get
        {
            Console.WriteLine("Employee Name getter is called.");
            return name;
        }
        set { name = value; }
    }
}

public interface IEmployee
{
    int Age { get; set; }
    String Name { get; set; }
}

Advice, Join Points, and Pointcuts

In AOP terms, the two lines of logging code we saw in the Age property’s get method should be modularized into something called advice. Advice is the action you’d like to take to address a cross-cutting concern. The first logging statement is at the beginning of the property getter. The second logging statement is at the end of the property getter before the employee’s age is returned. The beginning and the end of the property getter in this case are called join points. Join points are the places in code where advice can be applied. A collection of join points is called a pointcut. When you encapsulate pointcuts and advice into a module, you get what’s called an aspect. Figure 7-1 shows a pictorial view of all those terms to make it easy to learn them.

image

Figure 7-1. AOP concepts and their relationships

An Example

Now that I’ve introduced the concepts of AOP, let’s relate the abstract concepts to concrete code. For this example, I’ll use Spring.NET’s AOP framework and apply it to the code in Listing 7-1. The goal here is to extract the logging statements into a piece of advice and apply that advice to the right join points. To try the example, you will need Spring.NET; I use version 1.3.0 for the examples in this chapter. Here are the steps you need to take to set up Spring.NET.

  1. Go to the Spring.NET website (www.springframework.net) and download the file Spring.NET-1.3.0.zip.
  2. Unzip Spring.NET-1.3.0.zip to a folder of your choice. Throughout this book, I’ll assume that Spring.NET-1.3.0.zip is unzipped to C:Spring.NET-1.3.0. If you choose to unzip the file to a different folder, you need to substitute that path with your own whenever I refer to it in the book.
  3. Spring.NET consists of several components, not all of which are needed to run the code examples in this chapter. Our examples need only two components—Spring.Core.dll and Spring.Aop.dll. You need to copy the following files from C:Spring.NET-1.3.0Spring.NETin et2.0 elease to C:ProDLRlibSpring.NET elease:
    • Common.Logging.dll
    • Spring.Aop.dll, Spring.Aop.pdb and Spring.Aop.xml
    • Spring.Core.dll, Spring.Core.pdb and Spring.Core.xml
The Advice

Let’s start with the advice portion of the example. Listing 7-2 shows the code for the logging advice, which is in a class called SpringBasedLoggingAdvice. The class implements the AopAlliance.Intercept.IMethodInterceptor interface. AOP Alliance is a project that defines a standard set of Java interfaces that all Java-based AOP frameworks can choose to implement. The idea is that if all AOP frameworks implement those interfaces, we can code against those interfaces and our code will be vendor-agnostic. In the ideal situation, we can swap out a particular AOP framework and swap in another without any code change because our code depends only on the standard interfaces, not on a particular vendor’s implementation. The interfaces AOP Alliance defines are Java interfaces. The developers of Spring.NET defined corresponding interfaces in .NET. IMethodInterceptor is one of those interfaces.

A class implements IMethodInterceptor if it wants to provide AOP advice by intercepting method calls. That happens to be what our example wants to do. Our example wants to intercept calls to property getters and provide some logging-related advice. So the class SpringBasedLoggingAdvice implements IMethodInterceptor, which has only an Invoke method. The implementation of the Invoke method in SpringBasedLogginAdvice is fairly straightforward. It first calls BeforeInvoke to perform the part of the advice logic that we’d like to take place before the property getter is called. In this simple example, the BeforeInvoke method simply prints a message to the console. After calling BeforeInvoke, the code in the Invoke method calls invocation.Proceed(). This will effectively call the method that is intercepted, the property getter in our case. The result of the method call is stored in the returnValue variable so that it can be returned by the Invoke method later. Before returning returnValue, the Invoke method calls AfterInvoke to perform the part of the advice logic that we’d like to take place after the property getter is called. At this point, the implementation of the Spring.NET-based advice class is done. Next, let’s see how to specify the pointcut of our example.  

Listing 7-2. Logging Advice Based on Spring.NET

public class SpringBasedLoggingAdvice : IMethodInterceptor
{
    public object Invoke(IMethodInvocation invocation)
    {
        BeforeInvoke(invocation.Method, invocation.Arguments, invocation.Target);
        object returnValue = invocation.Proceed();
        AfterInvoke(returnValue, invocation.Method,
                invocation.Arguments, invocation.Target);
        return returnValue;
    }

    private void BeforeInvoke(MethodInfo method, object[] args, object target)
    {
        Console.Out.WriteLine("Advice BeforeInvoke is called. Intercepted method is {0}.",
                method.Name);
    }

    private void AfterInvoke(object returnValue, MethodInfo method,
                object[] args, object target)
    {
        Console.Out.WriteLine("Advice AfterInvoke is called. Intercepted method is {0}.",
                method.Name);
    }
}
The Pointcut

Listing 7-3 shows the XML that specifies the pointcut of the example. Since this is not a chapter about Spring.NET, I won’t get into too much detail about the XML file. If you want to learn more, Spring.NET has excellent online documentation you can refer to. Here, I’ll only explain the XML in Listing 7-3 at a high level. A typical Spring.NET XML file consists mainly of a collection of <object> elements. In Listing 7-3, the object element whose id is getAgeCalls specifies the pointcut of our example. The pointcut is specified by the pattern expression .*Age, which matches any property or method whose name ends with Age. The object element whose id is loggingAdvice represents an instance of the SpringBasedLoggingAdvice class we saw in the previous section. The <aop:advisor> element represents something similar to an aspect. As mentioned earlier, an aspect encapsulates pointcuts and advice. That’s why the <aop:advisor> element references the getAgeCalls pointcut and the loggingAdvice advice in order to indicate which pointcuts and advice it encapsulates. Finally, in order to apply the advisor to a target object, the XML defines the object element whose id is employeeBob. The object element employeeBob represents an instance of the Employee class we saw in Listing 7-1.

Listing 7-3. Application-config.xml

<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                mxlns:aop="http://www.springframework.net/aop">

  <aop:config>

    <aop:advisor id="getAgeAdvisor" pointcut-ref="getAgeCalls"
                 advice-ref="loggingAdvice"/>

  </aop:config>

  <object id="getAgeCalls"
        type="Spring.Aop.Support.SdkRegularExpressionMethodPointcut, Spring.Aop">
    <property name="patterns">
      <list>
        <value>.*Age</value>
      </list>
    </property>
  </object>

  <object id="loggingAdvice" type="Aop1.SpringBasedLoggingAdvice, Aop1"/>

  <object id="employeeBob" type="Aop1.Employee, Aop1">
    <property name="Name" value="Bob"/>
    <property name="Age" value="30"/>
  </object>

</objects>

A Test Run

The example so far has defined a pointcut, an advice class, and an advisor. Listing 7-4 shows the client code that demonstrates how all the pieces work together. The code first creates a Spring.NET application context from the application-config.xml file. A Spring.NET application context is basically a container of the objects defined by the <object> elements in files like application-config.xml. The objects usually have dependencies on one another. When Spring.NET creates an application context, it will make sure that the dependencies among the objects are properly wired up.

From the application context, the code in RunSimpleStaticObjectExample retrieves the object employeeBob by its object id and assigns the object to the employee variable. The last line of code calls the getter method of the Age property on the employee variable and prints the employee’s age to the screen. Because the Employee class’s Age property matches the pattern expression of the pointcut, the call to the Age property’s getter method will be intercepted and the advice logic implemented in SpringBasedLoggingAdvice will be applied.    

Listing 7-4.  Example Code That Demonstrates How Advice is Applied to a Target Object.

private static void RunSimpleStaticObjectExample()
{
    IApplicationContext context = new XmlApplicationContext("application-config.xml");
    IEmployee employee = (IEmployee)context["employeeBob"];
    Console.WriteLine("Employee is {0} years old.", employee.Age);
}

The output you’ll see when running the code looks like the following.

Advice BeforeInvoke is called. Intercepted method is get_Age.
Employee Age getter is called.
End of Employee Age getter.
Advice AfterInvoke is called. Intercepted method is get_Age.
Employee is 30 years old.

What happens behind the scene is that at runtime, Spring.NET creates a proxy object that wraps the employeeBob object when it sees that calls to employeeBob need to be intercepted. The employee variable actually references the proxy object, not the employeeBob object. The code employee.Age in our example therefore calls the Age property’s getter method on the proxy object, and that’s how method interception works. The proxy object Spring.NET creates is not just any proxy object; it is an instance of a dynamically generated class that implements the IEmployee interface. The generated class overrides the Age property’s getter method. The overridden method will call the Invoke method on the advice object, and thus the logging logic is woven into the Employee class. This is called runtime weaving because it weaves the logic of advice into an object at runtime. There are techniques other than runtime weaving for implementing an AOP framework. For example, compile-time weaving, as its name suggests, weaves one piece of code with another at compile time. Load-time weaving does the code weaving at the time classes are loaded into a runtime such as CLR or JVM.

Of all the different techniques for implementing an AOP framework, the one of most interest to us is runtime weaving. This is because the runtime-weaving AOP and its method interception mechanism smells and tastes a lot like what dynamic objects do. Late-binding actions on dynamic objects are “intercepted” and handled by the Bind[Action] methods in DynamicMetaObject. A proxy object to a target object in AOP is like a meta-object to a base object in DLR. With this analogy between runtime-weaving AOP and DLR dynamic objects in mind, let’s take the natural next step and see how to implement a DLR-based AOP framework for dynamic objects.  

AOP for Dynamic Objects

The Spring.NET aspect-oriented programming you saw in the previous section works for static .NET objects only. Now let’s implement a DLR-based AOP framework that works for dynamic objects. If you open the C:ProDLRsrcExamplesChapter7Chapter7.sln file in Visual Studio 2010, you can find the source code for this section in the Aop1 project. The last part of this chapter will integrate our AOP framework with the Spring.NET AOP so that the same advice objects and pointcuts can work across both static and dynamic objects.

Understanding the Framework

Before looking at how the DLR-based AOP framework is implemented, let’s see how to use it and what functionalities it provides. Listing 7-5 shows the familiar Customer class I’ve been using in many of the examples in earlier chapters. There is not much new to say about the Customer class. What’s important about this class is that (a) it implements IDynamicMetaObjectProvider and (b) it defines an Age property getter method. The meta-object class that contains the late-binding logic for Customer instances is the AopMetaObject class.

Listing 7-5. Customer.cs

public class Customer : IDynamicMetaObjectProvider
{
    public int Age
    {
        get
        {
            Console.WriteLine("Customer Age getter is called.");
            return 3;
        }
    }

    public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression parameter)
    {
        return new AopMetaObject(parameter, this);
    }
}

As you’ll see shortly, the AopMetaObject class is where aspect weaving happens. For the moment, let’s write some code that uses the Customer class and see the effects. Listing 7-6 shows the client code that creates an instance of Customer and accesses its Age property.

Listing 7-6. Client Code That Demonstrates Effects of Using the Customer Class.

private static void RunDynamicObjectExample()
{
    dynamic customer = new Customer();
    Console.WriteLine("Customer is {0} years old.", customer.Age);
}

The result of running the code in Listing 7-6 will look like the following:

Advice BeforeInvoke is called.
Customer Age getter is called.
Advice AfterInvoke is called.
Customer is 3 years old.

The two lines in bold are spit out by the advice logic in a class called SimpleLoggingAdvice. Listing 7-7 in the next section will show the code for SimpleLoggingAdvice. The point to stress here is that the advice logic you’ll see in Listing 7-7 and the code in Customer’s Age property are “woven” together by the AopMetaObject class. That’s why when the code in Listing 7-6 accesses the Age property of the customer variable, the program first prints the result of calling SimpleLoggingAdvice’s BeforeInvoke method, followed by the result of calling the Age property getter method, and finally the result of calling SimpleLoggingAdvice’s AfterInvoke method.

Implementing the Framework

Now that I’ve shown you what using the AOP framework is like, let’s see how that framework is implemented. The implementation of the AOP framework consists of two classes, SimpleLoggingAdvice and AopMetaObject. Listing 7-7 shows the SimpleLoggingAdvice class. The code in SimpleLoggingAdvice.cs is fairly simple. The only thing of importance here is the method signatures of BeforeInvoke and AfterInvoke. Both methods take no input parameters and have System.Object as the return type. To be honest, the method signatures are not practical. Usually methods like BeforeInvoke and AfterInvoke have input parameters that give them some information about the target method—i.e., the method being intercepted. If you look back at the code in Listing 7-2, you’ll see that the Invoke method of IMethodInterceptor takes an instance of IMethodInvocation as an input parameter. The IMethodInvocation instance contains information about the intercepted method. Practically, the BeforeInvoke and AfterInvoke methods should take some input parameters like what IMethodInterceptor’s Invoke method does. However, for this example I simplify things a bit because, as you’ll see, the method signatures of BeforeInvoke and AfterInvoke will influence the implementation complexity of the AopMetaObject class. I’ll tackle that complexity later and show you a more practical example when we integrate our AOP framework with Spring.NET AOP.

Listing 7-7. SimpleLoggingAdvice.cs

public class SimpleLoggingAdvice
{
    //For simplicity, this method does not have any input parameter.
    public void BeforeInvoke()
    {
        Console.Out.WriteLine("Advice BeforeInvoke is called.");
    }
    //For simplicity, this method does not have any input parameter.
    public void AfterInvoke()
    {
        Console.Out.WriteLine("Advice AfterInvoke is called.");
    }
}

The only piece of the AOP framework we haven’t looked at is the AopMetaObject class. AopMetaObject is the meta-object class for the Customer class. It is also the class that weaves together the advice logic and the code in Customer’s Age property. Listing 7-8 shows the implementation of AopMetaObject. To qualify as a meta-object class, AopMetaObject inherits from DynamicMetaObject. Since our example only triggers the GetMember action on instances of the Customer class, AopMetaObject overrides only the BindGetMember method. A more complete implementation of the AOP framework should also override the other Bind[Action] of DynamicMetaObject. The overridden BindGetMember method in Listing 7-8 delegates the late binding to the host language’s binder by calling the BindGetMember method of DynamicMetaObject. The binder in this case is C#’s binder. When the code customer.Age in Listing 7-6 is executed, C#’s binder will ask the customer object for its meta-object. Then the C# binder will call the BindGetMember on the meta-object. The meta-object in this case is an instance of AopMetaObject and it will delegate the late binding back to the C# binder. The C# binder’s binding logic will check if the Customer class has a property called Age with a getter method. If the Customer class has such a property, the C# binder will bind the GetMember action to that property. Otherwise it will return an error.

When the C# binder returns the binding result, we want to wrap the result with expressions that represent calls to both the BeforeInvoke method and the AfterInvoke method of SimpleLoggingAdvice. That’s why the BindGetMember method in Listing 7-8 passes the result from the C# binder to the WeaveAspect method in line 12. The WeaveAspect method pretty much does the same thing as the Invoke method in Listing 7-2. The difference is that WeaveAspect needs to turn the code in the Invoke method we saw in Listing 7-2 into DLR expressions. Lines 25 to 28 create a DLR expression that represents a call to the BeforeInvoke method on the member variable advice declared in line 4. Line 30 assigns the late-binding result returned by the C# binder to the returnValue parameter expression. In our example, when this expression is compiled into IL, it has the effect of executing customer.Age and assigning the result to a variable. Lines 32 to 35 create a DLR expression that represents a call to the AfterInvoke method on the member variable advice. Line 37 makes returnValue the return value of the whole block expression that spans from lines 22 to 39. If you compare the block expression to the Invoke method in Listing 7-2, the similarity should be obvious. When the code in lines 25 to 28 creates the method call expression, it needs to pass in expressions that represent the input parameters to the method call. Because the BeforeInvoke method of SimpleLoggingAdvice takes no input parameters, the code in lines 25 to 28 simply passes in an empty array, i.e., the args variable, to the Expression.Call method. Similarly, because AfterInvoke takes no input parameters, the code in lines 32 to 35 also passes in the args variable to the Expression.Call method. The example in the next section will improve this and make it more practical.

Notice that in this example, for simplicity, the AOP advice is hard-coded to an instance of SimpleLoggingAdvice in line 4. In a practical situation, which advice to use should be configurable, not hard-coded. Another issue with the example is that it does not apply advice based on pointcuts. Every GetMember action handled by AopMetaObject will have the SimpleLoggingAdvice applied to it. The next section will fix those issues.

Listing 7-8. AopMetaObject.cs

1)  public class AopMetaObject : DynamicMetaObject
2)  {
3)      //For simplicity, which advice we use is not dependent on configuration.
4)      private SimpleLoggingAdvice advice = new SimpleLoggingAdvice();
5)      public AopMetaObject(Expression expression, object obj)
6)
7)          : base(expression, BindingRestrictions.Empty, obj)
8)      { }
9)
10)      public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
11)      {
12)          return WeaveAspect(base.BindGetMember(binder));
13)      }
14)
15)      private DynamicMetaObject WeaveAspect(DynamicMetaObject originalObject)
16)      {
17)          Expression originalExpression = originalObject.Expression;
18)          var args = new Expression[0] {};
19)          ParameterExpression returnValue = Expression.Parameter(originalExpression.Type);
20)
21)          var advisedObject = new DynamicMetaObject(
22)              Expression.Block(
23)                  new[] { returnValue },
24)                  new Expression[] {
25)                      Expression.Call(
26)                          Expression.Constant(this.advice),
27)                          typeof(SimpleLoggingAdvice).GetMethod("BeforeInvoke"),
28)                          args),
29)
30)                      Expression.Assign(returnValue, originalExpression),
31)
32)                      Expression.Call(
33)                          Expression.Constant(this.advice),
34)                          typeof(SimpleLoggingAdvice).GetMethod("AfterInvoke"),
35)                          args),
36)
37)                      returnValue
38)                  }
39)              ),
40)              originalObject.Restrictions
41)          );
42)
43)          return advisedObject;
44)      }
45)  }

Integration with Spring.NET AOP

The example in the previous section cuts some corners and shows a primitive implementation of an AOP framework for dynamic objects. Let’s fix those shortcuts and make the AOP framework more practical. As the first step, let’s modify the example so that it reads from the application-config.xml file to find out the advice to apply, and to find out the corresponding pointcuts. If you open the C:ProDLRsrcExamplesChapter7Chapter7.sln file in Visual Studio 2010, you can find the source code for this section in the Aop project.

Getting the AOP Advisors

Listing 7-9 shows the code of the helper class that reads advice and pointcuts from a configuration file. The class is called AdvisorChainFactory. It has a property called Context. Client code that uses AdvisorChainFactory is supposed to read the application-config.xml file and create a Spring.NET application context. Once that context is created, the client code should assign the application code to AdvisorChainFactory’s Context property. When we get to the client code later in this section, you’ll see the part of the client code that sets AdvisorChainFactory’s Context property.

The GetInterceptors method of AdvisorChainFactory returns a list of AOP advice. The method takes two input parameters—method and targetType. It uses those parameters to find the matching pointcuts (line 20). For each matched pointcut, GetInterceptors puts the advice associated with the pointcut into the adviceList variable (line 21). The method parameter has information about the method that you want GetInterceptors to match against pointcuts. Similarly, the targetType parameter represents the class that you want GetInterceptors to match against pointcuts. In this case, there is only one pointcut defined in the application context, and that pointcut matches any method of any class as long as the method’s name ends with  “Age”.

Listing 7-9. AdvisorChainFactory.cs

1)  class AdvisorChainFactory
2)  {
3)      private static IApplicationContext context;
4)
5)      public static IApplicationContext Context
6)      {
7)          set { context = value; }
8)      }
9)
10)      public static IList<IAdvice> GetInterceptors(MethodInfo method, Type targetType)
11)      {
12)          IList<IAdvice> adviceList = new List<IAdvice>();
13)          IDictionary advisors = context.GetObjectsOfType(typeof(IPointcutAdvisor));
14)
15)          ArrayList advisorList = new ArrayList(advisors.Values);
16)          advisorList.Sort(new OrderComparator());
17)
18)          foreach (IPointcutAdvisor advisor in advisorList)
19)          {
20)              if (advisor.Pointcut.MethodMatcher.Matches(method, targetType))
21)                  adviceList.Add(advisor.Advice);
22)          }
23)
24)          return adviceList;
25)      }    
26)  }

Notice that GetInterceptors returns a list of IAdvice objects. Every Spring.NET advice class must implement the IAdvice interface. The fact that GetInterceptors returns a list of advice objects indicates that (a) we can have more than one advice object applied to a late-binding action and (b) the advice objects have an order (because a list contains ordered elements). As a matter of fact, Spring.NET provides a way for us to specify the order in which to apply multiple advice objects in XML configuration files. That’s why we have line 16 in Listing 7-9 to sort the advisor objects according to their order. By doing the sorting, the code honors the order of advisor objects specified in a configuration file such as application-config.xml. I’ll show you some examples of applying multiple advice objects in different orders using our AOP framework in a moment.

Before we move on to the rest of the AOP framework’s implementation, let’s take a look at the code in Listing 7-10 that tests whether the AdvisorChainFactory class works correctly. Listing 7-10 has a method called RunAdvisorChainFactoryExample that demonstrates how the GetInterceptors method of the AdvisorChainFactory class works. In RunAdvisorChainFactoryExample, the target type is the Customer class (line 20) and the method is the Age property’s getter method (line 21). Given that target type and method, GetInterceptors will look for pointcuts that match them (lines 22 and 23). GetInterceptors will return the advice objects associated with the matched pointcuts. Finally, RunAdvisorChainFactoryExample prints out the class names of those advice objects. As you can see, with just a few lines of code, we can now easily find out from an application context which advice should apply to which method of which class. Notice that the init method in Listing 7-10 sets AdvisorChainFactory’s Context property (line 15). This ensures that the Context property is properly set before the AdvisorChainFactory.GetInterceptors method is called.

Listing 7-10. An Example in Program.cs That Shows How RunAdvisorChainFactory Works

1)  class Program
2)  {
3)      private static IApplicationContext context;
4)      
5)      static void Main(string[] args)
6)      {
7)          init();
8)          RunAdvisorChainFactoryExample();
9)          Console.ReadLine();
10)      }
11)
12)      private static void init()
13)      {
14)          context = new XmlApplicationContext("application-config.xml");
15)          AdvisorChainFactory.Context = context;
16)      }
17)
18)      private static void RunAdvisorChainFactoryExample()
19)      {
20)          Type targetType = typeof(Customer);
21)          MethodInfo method = targetType.GetProperty("Age").GetGetMethod();
22)          IList<IAdvice> interceptors =
23)               AdvisorChainFactory.GetInterceptors(method, targetType);
24)
25)          foreach (var interceptor in interceptors)
26)              Console.WriteLine("type of matching interceptor is {0}",
27)                   interceptor.GetType().Name);
28)      }
29)  }

Implementing Advice

Because we are not going to hard-code the advice to use in the AopMetaObject class, we need an interface to serve as the baseline of all the advice classes our AOP framework understands. Listing 7-11 shows that baseline interface’s definition. The interface is called IDynamicAdvice. Any advice class that would like to “advise” dynamic objects needs to implement IDynamicAdvice. Notice that the BeforeInvoke and AfterInvoke methods defined in IDynamicAdvice now take input parameters. BeforeInvoke takes three parameters—method, args, and target. The method parameter has information about the method being late-bound. The args parameter represents the arguments to the method being late-bound. The target parameter is the object on which the late-bound method is invoked. The AfterInvoke method takes all the three parameters that BeforeInvoke takes plus the returnValue parameter that represents the return value of the late-bound method.

Listing 7-11 also shows the modified advice class. The class is called LoggingAdvice and it implements both IDynamicAdvice and IMethodInterceptor. By implementing both of those interfaces, LoggingAdvice instances can advise both dynamic and static objects. The code in LoggingAdvice is by and large the same as the code in SpringBasedLoggingAdvice that we saw earlier.

Listing 7-11. IDynamicAdvice and LoggingAdvice.

interface IDynamicAdvice : IAdvice
{
    void BeforeInvoke(MethodInfo method, object[] args, object target);
    void AfterInvoke(object returnValue, MethodInfo method, object[] args, object target);
}

public class LoggingAdvice : IDynamicAdvice, IMethodInterceptor
{
    public object Invoke(IMethodInvocation invocation)
    {
        BeforeInvoke(invocation.Method, invocation.Arguments, invocation.Target);
        object returnValue = invocation.Proceed();
        AfterInvoke(returnValue, invocation.Method,
                        invocation.Arguments, invocation.Target);
        return returnValue;
    }

    #region IDynamicAdvice Members

    public void BeforeInvoke(MethodInfo method, object[] args, object target)
    {
        Console.Out.WriteLine("Advice BeforeInvoke is called. Intercepted method is {0}.",
                method.Name);
    }

    public void AfterInvoke(object returnValue, MethodInfo method,
        object[] args, object target)
    {
        Console.Out.WriteLine("Advice AfterInvoke is called. Intercepted method is {0}.",
                method.Name);
    }
    #endregion
}

Applying Advice

The last file we need to modify in order to complete the whole example is AopMetaObject.cs. Listing 7-12 shows the modified code.

Listing 7-12. AopMetaObject.cs

1)  public class AopMetaObject : DynamicMetaObject
2)  {
3)      public AopMetaObject(Expression expression, object obj)
4)          : base(expression, BindingRestrictions.Empty, obj)
5)      { }
6)
7)      public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
8)      {
9)          return WeaveAspect(binder.Name, base.BindGetMember(binder));
10)      }
11)
12)      private DynamicMetaObject WeaveAspect(String name, DynamicMetaObject
13)          originalObject)
14)      {
15)          Expression originalExpression = originalObject.Expression;
16)          Type targetType = this.Value.GetType();
17)          PropertyInfo property = targetType.GetProperty(name);
18)          MethodInfo method = property.GetGetMethod();
19)
20)          Expression nullExp = Expression.Constant(null);
21)          Expression targetExp = Expression.Constant(this.Value);
22)          Expression arrayExp = Expression.Constant(new Object[] { });
23)          Expression methodExp = Expression.Constant(method);
24)
25)          IList<IAdvice> adviceList = AdvisorChainFactory
26)                            .GetInterceptors(method, targetType);
27)
28)          List<Expression> calls = new List<Expression>();
29)          List<Expression> afterCalls = new List<Expression>();
30)          foreach (var advice in adviceList)
31)          {
32)              If (!(advice is IDynamicAdvice)) continue;
33)
34)              calls.Add(Expression.Call(
35)                      Expression.Constant(advice),
36)                      typeof(IDynamicAdvice).GetMethod("BeforeInvoke"),
37)                      new Expression[] { methodExp, arrayExp, targetExp }));
38)
39)              afterCalls.Add(Expression.Call(
40)                      Expression.Constant(advice),
41)                      typeof(IDynamicAdvice).GetMethod("AfterInvoke"),
42)                      new Expression[] { nullExp, methodExp, arrayExp, targetExp }));
43)          }
44)
45)          ParameterExpression returnValue = Expression
46)                           .Parameter(originalExpression.Type);
47)          calls.Add(Expression.Assign(returnValue, originalExpression));
48)          afterCalls.Reverse();
49)          calls.AddRange(afterCalls);
50)          calls.Add(returnValue);
51)
52)          var advisedObject = new DynamicMetaObject(
53)              Expression.Block(
54)                  new[] { returnValue },
55)                  calls.ToArray()
56)              ),
57)              originalObject.Restrictions
58)          );
59)
60)          return advisedObject;
61)      }
62)  }

First, notice that, unlike line 4 in Listing 7-8, the code in Listing 7-12 no longer calls the constructor of an advice class. Instead, it calls the GetInterceptors method of AdvisorChainFactory in line 69 to get a list of advice objects based on pointcut matches. With this change, our example no longer hard-codes the advice objects it applies to dynamic objects. The WeaveAspect method is still the workhorse here. It’s more complex than earlier because it now needs to prepare the proper expressions that satisfy the method signatures of IDynamicAdvice’s BeforeInvoke and AfterInvoke. It also needs to handle not just one advice object but a list of them. Lines 16 to 23 shows the code that prepares the expressions that represent the input parameters needed for calling BeforeInvoke and AfterInvoke. Lines 30 to 43 iterate through the advice objects returned by GetInterceptors. For each advice object, the code creates a method call expression that represents a call to the advice object’s BeforeInvoke method and a method call expression that represents a call to the advice object’s AfterInvoke method. The expressions created in lines 20 to 23 are passed to the Expression.Call method to create the method call expressions.

After the method call expressions are created, we need to put them into a block expression like we did in Listing 7-8. The block expression that holds all the method call expressions is referenced by the calls variable in Listing 7-12. In Listing 7-8, there is only one advice object and therefore only one call expression for calling BeforeInvoke and only one call expression for calling AfterInvoke. Here we have a list of ordered advice objects. Because the advice objects have order, the call expressions we put into the block expression also need to be in order. Figure 7-2 shows how the call expressions should be ordered. In Spring.NET, we can assign a number to an advice object to denote the advice object’s precedence. The lower the number is, the higher the precedence. In Figure 7-2, there are two advice objects; advice 1 has higher precedence than advice 2 and therefore it’s put at the front of the advice chain in the figure. When program execution enters the advice chain, the first method that’s executed is the BeforeInvoke method of advice 1. Then the BeforeInvoke method of advice 2 is executed. Then the intercepted method of the target object will be executed. In this case, the execution of the intercepted method on the target method is represented by the assignment expression created in line 47 of Listing 7-12. The execution of the program will then exit out the advice chain. On exit, the first method that’s executed is the AfterInvoke method of advice 2. Then the AfterInvoke method of advice 1 is executed. That’s why in line 48, all the expressions that represent calls to AfterInvoke methods are reversed before they are added to the block expression in line 49.

image

Figure 7-2. Order of advice objects

For the purpose of this chapter, the AOP framework implementation at this point is finished. Obviously, there are many things the framework does not support. The AopMetaObject class only overrides the BindGetMember method. A more complete implementation will override other Bind[Operation] methods and weave in the advice logic in those overridden methods. The AOP framework supports the order of advisor objects. Spring.NET provides a way for specifying the order of advice objects and our AOP framework does not account for that. Nonetheless, the AOP framework lays the groundwork for further enhancements. It also demonstrates one important scenario (i.e., the AOP programming paradigm) in which the DLR dynamic object infrastructure can be leveraged. To celebrate, let’s take a break and have some fun with the AOP framework.

Cutting Across Dynamic and Static Objects

In this section, you will harvest the results of the AOP framework built in the previous sections. To use the AOP framework, I wrote a method called RunAopExample and put it in Program.cs. Listing 7-13 shows its code.   

Listing 7-13. An Example in Program.cs That Uses the AOP Framework

1)  private static void RunAopExample()
2)  {
3)      dynamic customer = new Customer("John", 5);
4)      Console.WriteLine("Customer {0} is {1} years old. ",
5)          customer.Name, customer.Age);
6)
7)      IEmployee employee = (IEmployee)context["employeeBob"];
8)      Console.WriteLine("Employee {0} is {1} years old.",
9)          employee.Name, employee.Age);
10)  }

The RunAopExample method creates a Customer instance in line 3 and fetches an IEmployee instance from the Spring.NET application context in line 7. Method calls to those two instances’ property getters will be intercepted, matched against the pointcuts specified in application-config.xml, and then advised as appropriate. You’ll see the following output when you run the example:

Customer Name getter is called.
Advice BeforeInvoke is called. Intercepted method is get_Age.
Customer Age getter is called.
Advice AfterInvoke is called. Intercepted method is get_Age.
Customer John is 5 years old.

Employee Name getter is called.
Advice BeforeInvoke is called. Intercepted method is get_Age.
Employee Age getter is called.
Advice AfterInvoke is called. Intercepted method is get_Age.
Employee Bob is 30 years old.

Next, let’s turn it up a notch by throwing in a second advice object. By having a second advice object, we’ll be able to order the two advice objects in different ways and see the effects. To make the demonstration clear, I created a new advice class by copying the LoggingAdvice class and renaming it to LoggingAdvice2. Listing 7-14 shows the code of LoggingAdvice2 with differences from LoggingAdvice in bold. As you can see, LoggingAdvice2 differs from LoggingAdvice only in the messages it prints to the console. Because the messages are different, it will be easy to see which advice object is in front of which in the advice chain later when we run the example.

Listing 7-14. LoggingAdvice2.cs

public class LoggingAdvice2 : IDynamicAdvice, IMethodInterceptor
{
    //Invoke method is omitted.
    public void BeforeInvoke(MethodInfo method, object[] args, object target)
    {
        Console.Out.WriteLine("Advice2 BeforeInvoke is called. Intercepted method is {0}.",
                method.Name);
    }

    public void AfterInvoke(object returnValue, MethodInfo method, object[] args,
                object target)
    {
        Console.Out.WriteLine("Advice2 AfterInvoke is called. Intercepted method is {0}.",
                method.Name);
    }
}

With LoggingAdvice2 in place, all that remains is to configure new advice and advisor objects in application-config.xml. Listing 7-15 shows the contents of application-config.xml after the modifications. The differences from the earlier version of application-config.xml are highlighted in bold. As you can see, a new advice object with id loggingAdvice2 and a new advisor object with id getAgeAdvisor2 are added. The object loggingAdvice2 is an instance of LoggingAdvice2. As for the getAgeAdvisor2 object, notice that its order attribute is assigned the number 2. In comparison, the order attribute of the getAgeAdvisor object is set to 5. That means in the advice chain, loggingAdvice2 will be in front of loggingAdvice. So we should expect the BeforeInvoke method of loggingAdvice2 to be called before the BeforeInvoke method of loggingAdvice when program execution enters the advice chain. When program execution exits the advice chain, we should expect the AfterInvoke method of loggingAdvice to be called first and the AfterInvoke method of loggingAdvice2 to be called second. Notice also that I changed the pointcut pattern to match not only all methods that end with Age but also the ones that end with Name.

Listing 7-15. Adding a Second Advice Object in Application-config.xml.

<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns:aop="http://www.springframework.net/aop">

  <aop:config>
    <aop:advisor id="getAgeAdvisor" pointcut-ref="getAgeCalls"
                 advice-ref="loggingAdvice" order="5" />

    <aop:advisor id="getAgeAdvisor2" pointcut-ref="getAgeCalls"
                 advice-ref="loggingAdvice2" order="2" />
  </aop:config>

  <object id="getAgeCalls"
        type="Spring.Aop.Support.SdkRegularExpressionMethodPointcut, Spring.Aop">
    <property name="patterns">
      <list>
        <value>.*Age</value>
        <value>.*Name</value>
      </list>
    </property>
  </object>

  <object id="loggingAdvice" type="Aop.LoggingAdvice, Aop"/>
  <object id="loggingAdvice2" type="Aop.LoggingAdvice2, Aop"/>

<!-- The object employeeBob is the same as before and omitted -->
</objects>

If you run the example with the configuration in Listing 7-15, you’ll see the following output:

Advice2 BeforeInvoke is called. Intercepted method is get_Name.
Advice BeforeInvoke is called. Intercepted method is get_Name.
Customer Name getter is called.
Advice AfterInvoke is called. Intercepted method is get_Name.
Advice2 AfterInvoke is called. Intercepted method is get_Name.
Advice2 BeforeInvoke is called. Intercepted method is get_Age.
Advice BeforeInvoke is called. Intercepted method is get_Age.
Customer Age getter is called.
Advice AfterInvoke is called. Intercepted method is get_Age.
Advice2 AfterInvoke is called. Intercepted method is get_Age.
Customer John is 5 years old.

Advice2 BeforeInvoke is called. Intercepted method is get_Name.
Advice BeforeInvoke is called. Intercepted method is get_Name.
Employee Name getter is called.
Advice AfterInvoke is called. Intercepted method is get_Name.
Advice2 AfterInvoke is called. Intercepted method is get_Name.
Advice2 BeforeInvoke is called. Intercepted method is get_Age.
Advice BeforeInvoke is called. Intercepted method is get_Age.
Employee Age getter is called.
Advice AfterInvoke is called. Intercepted method is get_Age.
Advice2 AfterInvoke is called. Intercepted method is get_Age.
Employee Bob is 30 years old.

Try changing the order attribute of getAgeAdvisor in application-config.xml from 5 to 1. Then run the example again and see what the output is.

Summary

This chapter begins with an introduction to aspect-oriented programming, explaining the basic concepts of AOP as well as terms such as join point, pointcut, advice, and aspect. We briefly touched upon three approaches for implementing an AOP framework—runtime, load-time and compile-time weaving. We talked about how Spring.NET’s runtime weaving is achieved by using proxy objects. Our AOP framework and Spring.NET’s AOP framework both take the runtime weaving approach. Unlike Spring.NET’s AOP framework, ours leverages DLR’s dynamic object infrastructure instead of using proxy objects. We went through the first iteration of our AOP framework’s implementation, which works only for dynamic objects. We continued, integrating the AOP framework with Spring.NET AOP. The result is an AOP system that reads aspect-related settings from one single configuration file and works across static as well as dynamic objects.

This chapter scratches only the surface of many topics it covers. The implementation of the AOP framework leaves many things to be desired. To more fully realize the potential of the underlying iceberg whose tip we saw in this chapter, I created a project up on Google Code at http://code.google.com/p/dpier/. You are welcome to visit the project web site for updates on the development of the AOP framework.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.187.223