Using the concept of the current object oriented programming, objects may be separated as in the following diagram.
In a strict object oriented design, common functionalities (Core Concerns) are implemented in the same class as Crosscutting Concerns such as logging, declarative transaction, database connection, exception handling, security, and distributed transaction.
Problem encountered with including both core concerns and crosscutting concerns in a single class is shown below using an example: Example: A program was developed but it does not run as expected. To find where the defect is, logging was inserted into the code as below. Code to output a log is in bold.
class Foo {
private Bar bar = new Bar();
public void Foo()
{
logger.Log("BEGIN Foo#Foo");
bar.DoSomething();
logger.Log("END Foo#Foo");
}
}
class Bar {
private Baz baz = new Baz();
public void DoSomething(){
logger.Log("BEGIN Bar#DoSomething");
baz.DoSomething();
logger.Log("END Bar#DoSomething");
}
}
As can be seen from the example, code to output logging is usually inserted in several places. Even if an editor is used to find places to insert the logging code, the actual insertion is usually done manually. This may cause the following problems:
Logging codes are inserted in several places. If items to output is changed, all locations must be updated.
A programmer may accidently insert a logging procedure where it might cause an error.
A programmer may have missed inserted the code where logging is necessary.
A programmer may make a typing error in the logging code.
A programmer may accidently modify the application code.
Crossing concern is like logging in the example above where a common function is used in several classes. In s2dotnet, crosscutting concerns are taken out of application codes and the byte codes are weaved in during compilation or during runtime.
Following is an example of logging using S2AOP.NET. Logging is specified in XML configuration file - it is not necessary to code logging into the program in several places.
S2AOP.NET is configured in a S2Container configuration file (dicon file). Configuration file may be placed in an arbitrary folder but it is a convention to place it in the same folder as crosscutting concern files or in the same folder as the component.
Weave an aspect into a component. Interceptor is specified as a JScript.NET expression or as a value in teh child component tag.
Warnings:
Component specified in an aspect tag is acquired when a container is initialized. For that reason, even if the instance attribute of component specified the aspect tag is prototype, instance will not be created everytime method in an Interceptor is called.
pointcut attribute (optional)
Method names may be specified delimited by a comma. If pointcut is not specified, all methods in an interface implemented by a component is assumed. Method name may be specified by a regular expression (System.Text.RegularExpressions.Regex).
Example
Following is an example to apply an aspect specified in a pointcut attribute to Add method and Clear method in System.Collections.Hashtable. If pointcut attribute is not specified, an aspect will be applied to method in an interface implemented by System.Collections.Hashtable (System.Collections.IDictionary in this example).
Following is an example of using regular expression to specify all methods in interfaces implemented by System.Collections.Hashtable (System.Collections.IDictionary in this example).
Interceptor to use tracing as a crosscutting concern. Following is an example of dicon file with TraceInterceptor applied to a Hashtable class. Applied method name is Add.
To implement an interface, following Invoke method is implemented.
public object Invoke(IMethodInvocation invocation)
To extend an abstract class, over the following Invoke method.
public override object Invoke(IMethodInvocation invocation)
AbstractInterceptor is an abstract class implementing IMethodInterceptor. AbstractInterceptor has a CreateProxy method to get Proxy object and a GetComponentDef method, which gets component definition of aspect to apply. To create an Interceptor that requires the class name of a class that had an aspect applied (e.g. Interceptor that output logs), use AbstractInterceptor to get the class name.
Get object, method, arguments that is set to properties Target, Method, and Arguments of IMethodInvocation. Actual result may be obtained by invoking proceed() to invoke the actual method.
Example:
public class TestInterceptor : IMethodInterceptor
{
public object Invoke(IMethodInvocation invocation)
{
Console.WriteLine("Before"); // before invoking a methodobject ret = invocation.Proceed();
Console.WriteLine("After"); // after invoking a method
return ret;
}
}
TraceInterceptor is invoked before and after IMethodInvocation#Proceed() is invoked. If several aspectes are defined in a component, they are applied as follows:
Aspect before IMethodInterceptor is invoked are called in order Aspect are defined
After the last aspect before IMethodInterceptor is called, method in the IMethodInterceptor itself is invoked.
After the method in the component is called, aspects are called in the reverse order they are defined
An aspect may be weaved into a program without configuring a dicon file as follows:
Method names are specified in arguments of constructor to S2.NET.Framework.Aop.Impl.PointcutImpl. If interface is implemented as in System.Collections.Hashtable, specify Type class as in new PointcutImpl(typeof(Hashtable)) to automatically apply to all methods in an implemented interface of that class.
Specify an Interceptor in the the first argument of a constructor for S2.NET.Framework.Aop.Impl.AspectImpl, and specify pointcut created by PointcutImpl in the second argument
As the arguments to the constructor of S2.NET.Framework.Aop.Proxy.AopProxy, specify target class (classes extending System.MarshalByRefObject in this example) or interfaces implemented by the target class and array of aspect created from AspectImpl.
Get object that has an aspect applied by invoking S2.NET.Framework.Aop.Proxy.AopProxy#Create()
Following is an example to applying TraceInterceptor to the System.Collections.Hashtable class.
IPointcut pointcut = new PointcutImpl(new string[]{"Add"});
IAspect aspect = new AspectImpl(new TraceInterceptor(), pointcut);
AopProxy aopProxy = new AopProxy(typeof(IDictionary),
new IAspect[]{aspect}, null, new Hashtable());
IDictionary proxy = (IDictionary) aopProxy.Create();
proxy.Add("aaa", "bbb");
Use TraceInterceptor to output a trace when Add method and Clear method in System.Collections.ArrayList class and System.Collections.Hashtable class is invoked. Following files are created in this example:
dicon file defining components (Trace.dicon)
dicon file to verify is the settings are correct (AopTraceClient.cs)
Create dicon file
Define TraceInterceptor as a component. Set name attribute to "traceInterceptor"
Define System.Collections.ArrayList class as a component. Specify Interceptor as the value of an aspect tag
Define System.Collections.Hashtable class as a component. Specify Add method and Clear method in pointcut attribute. Specify Interceptor as a value in aspect tag.
Specify path of the created dicon file (Trace.dicon) in the first argument in S2.NET.Framework.Container.S2Container#Create() method
Get a component by specifying type class of an interface (typeof(IList), typeof(IDictionary) implemented by the defined class as the first argument of S2.NET.Framework.Container.S2Container#GetComponent() method
Invoke Count property of acquired component (IList) to test if the aspect was properly applied.
In a similar manner, invoke Add method and Clear method of acquired component (IDictionary)
AopTraceClient.cs
using System;
using System.Collections;
using S2.NET.Framework.Container;
using S2.NET.Framework.Container.Factory;
namespace S2.NET.Examples.Reference.Aop
{
public class AopTraceClient
{
private const string PATH = "S2/NET/Examples/Reference/Aop/Trace.dicon";
public void Main()
{
IS2Container container = S2ContainerFactory.Create(PATH);
IList list = (IList) container.GetComponent(typeof(IList));
int count = list.Count;
IDictionary dictionary = (IDictionary)
container.GetComponent(typeof(IDictionary));
dictionary.Add("aaa", "bbb");
dictionary.GetHashCode();
}
}
}
Execution Result
Trace information is outputted before and after the method is invoked:
DEBUG 2005-09-26 23:12:16,138 [2564] BEGIN System.Collections.ICollection#get_Count()
DEBUG 2005-09-26 23:12:16,138 [2564] END System.Collections.ICollection#get_Count() : 0
DEBUG 2005-09-26 23:12:16,138 [2564] BEGIN System.Collections.IDictionary#Add(aaa, bbb)
DEBUG 2005-09-26 23:12:16,138 [2564] END System.Collections.IDictionary#Add(aaa, bbb) :
DEBUG 2005-09-26 23:12:16,138 [2564] BEGIN System.Object#GetHashCode()
DEBUG 2005-09-26 23:12:16,138 [2564] END System.Object#GetHashCode() : 23
Files in this example is available in the S2/NET/Examples/Reference/Aop folder in the S2.NET.Examples project.
Create an interceptor to trace class name, method name, and arguments and measure processing time. Use this interceptor to measure time and trace resource intensive processes. Following files are created in this example:
Interceptor to trace class name, method name, and arugments and measure processing time (MeasurementInterceptor.cs)
Class with a resource intensive process (HeavyProcess.cs)