| S2Container.NET TOP page ## S2Container.NET AOP Introduction Object Oriented Problems of Object Oriented Design Main ideas behind AOP Merits of AOP Merits of S2AOP.NET Warnings S2AOP.NET Reference Files that Needs to be Created Configuration Files Interceptor used by S2AOP.NET TraceInterceptor MockInterceptor Custom Implementation of Interceptor Weaving Aspect without a dicon File Examples TraceInterceptor Custom Implementation of Interceptor ## Introduction 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. ### Problems of Object Oriented Design 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.
    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
    "http://www.seasar.org/dtd/components.dtd">
    <components>
        <component name="traceInterceptor"
                   class="S2.NET.Framework.Aop.Interceptors.TraceInterceptor"/>
        <component class="Foo">
            <aspect pointcut="Foo">traceInterceptor</aspect>
        </component>
        <component class="Bar">
            <aspect pointcut="DoSomething">traceInterceptor</aspect>
        </component>
    </components>
Refer to Configuration File for further details.
    class Foo
    {
        privaye Bar bar = new Bar();
        public void Foo()
        {
            bar.DoSomething();
        }
    }
    class Bar
    {
        private Baz baz = new Baz();
        public void DoSomething()
        {
            baz.DoSomething();
        }
    }
### Main Idea Behind AOP #### Advice(MethodInterceptor) Represents source code inserted into a program. Usually called "Interceptor". #### Joinpoint(MethodInvocation) Represents joint point between a class and an advice. Advice get arguments and method information from a Joinpoint. #### Pointcut Define where to set a Joinpoint. #### Aspect Associate Advice to a Pointcut. ### Merits of AOP Maintainability is increased by separating core concerns from crosscutting concerns System functionalities (crosscutting concern) may be taken out of application logic (core concern) making the application logic clearer. Functionalities such as transaction processing that required knowledge of enterprise application may be treated as an object in .NET. ### Merits of S2AOP.NET Configuration is simple Only one interface is required to be implemented Aspect applied to a component is clear Commonly used object patterns of aspect implementations are readily available ### Warnings To use an aspect, component is received as an interface type. To apply an aspect to a component that is directly received from an S2Container, a component must be a class that extends System.MarshalByRefObject. If pointcut attribute is not specified or if pointcut attribute is set to ".*", all methods in implemented interface is assumed. An aspect is not applied when "this" pointer is used (if it is not called via an interface) ## S2AOP.NET Reference ### Files Required to be Created 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. ### Configuration Files #### aspect tag (required when using AOP) 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).
    <component class="System.Collections.Hashtable">
        <aspect pointcut="Add,Clear">
            <component class="S2.NET.Framework.Aop.Interceptors.TraceInterceptor"/>
        </aspect>
    </component>
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).
    <component class="System.Collections.Hashtable">
        <aspect pointcut=".*">
            <component class="S2.NET.Framework.Aop.Interceptors.TraceInterceptor"/>
        </aspect>
    </component>
### Interceptor Made Available by S2AOP.NET S2AOP.NET has the following Interceptors. Custom Interceptors may also be created easily. #### (1) TraceInterceptor ##### Class name S2.NET.Framework.Aop.Interceptors.TraceInterceptor ##### Description 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.
    <component class="System.Collections.Hashtable">
        <aspect pointcut="Add">
            <component class="S2.NET.Framework.Aop.Interceptors.TraceInterceptor"/>
        </aspect>
    </component>
Refer to TraceInterceptor for detailed information. #### (2) MockInterceptor ##### Class name S2.NET.Framework.Aop.Interceptors.MockInterceptor ##### Description Interceptor to allow test using Mock. #### (3) Custom implemented Interceptor ##### Description To create a custom Interceptor, following interface or abstract class must be implemented. S2.NET.Framework.Aop.IMethodInterceptor S2.NET.Framework.Aop.Interceptors.AbstractInterceptor 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. public object CreateProxy(Type proxyType) protected IComponentDef GetComponentDef(IMethodInvocation invocation) 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 method

            object 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 Refer to Custom implementatin of an Interceptor for further information. ### Way to weave an aspect without using a dicon file 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");
## Examples Setup is necessary before executing the following example. ### TraceInterceptor 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. Trace.dicon
    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
    "http://www.seasar.org/dtd/components.dtd">
    <components>
        <component name="traceInterceptor"
            class="S2.NET.Framework.Aop.Interceptors.TraceInterceptor"/>

        <component class="System.Collections.ArrayList>
            <aspect>traceInterceptor</aspect>
        </component>

        <component class="System.Collections.Hashtable">
            <aspect pointcut="Add, GetHashCode">
                traceInterceptor
            </aspect>
        </component>

        <component name="AopTraceInterceptor"
            class="S2.NET.Examples.Reference.Aop.AopTraceClient" />
    </components>
Create execution file 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. ### Custom implementation of an Interceptor 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) Component configuration dicon file (Measurement.dicon) Execution file to test the settings (AopMeasurementClient.cs) Create custom implementation of an Intercepter Implement S2.NET.Framework.Aop.Interceptors.AbstractInterceptor class Implement Invoke(IMethodInvocation invocation) method Get full class name using GetComponentDef(invocation).ComponentType.FullName Get method name by using invocation.Method.Name Get arguments using invocation.Arguments Get the time before the actual method is invoked by invocation.Proceed() Get the time after the actual method is invoked by invocation.Proceed() MeasurementInterceptor.cs
    using System;
    using System.Text;
    using S2.NET.Framework.Aop.Interceptors;

    namespace S2.NET.Examples.Reference.Aop
    {
        public class MeasurementInterceptor : AbstractInterceptor
        {
            public override object Invoke(S2.NET.Framework.Aop.IMethodInvocation invocation)
            {
                long start = 0;
                long end = 0;
                StringBuilder buf = new StringBuilder(100);

                buf.Append(GetComponentDef(invocation).ComponentType.FullName);
                buf.Append("#");
                buf.Append(invocation.Method.Name);
                buf.Append("(");
                object[] args = invocation.Arguments;
                if(args != null && args.Length > 0)
                {
                    foreach(object arg in args)
                    {
                        buf.Append(arg);
                        buf.Append(", ");
                    }
                    buf.Length = buf.Length - 2;
                }
                buf.Append(")");
                try
                {
                    start = DateTime.Now.Ticks;
                    object ret = invocation.Proceed();
                    end = DateTime.Now.Ticks;
                    buf.Append(" : ");
                    return ret;
                }
                catch(Exception ex)
                {
                    buf.Append(" Exception:");
                    buf.Append(ex);
                    throw ex;
                }
                finally
                {
                    Console.WriteLine(buf.ToString() + ((end - start) / 10000));
                }
            }
        }
    }
Create resource intensive class Sleep 5 seconds to mimic resource intensive process HeavyProcess.cs
    using System;

    namespace S2.NET.Examples.Reference.Aop
    {
        public class HeavyProcess : MarshalByRefObject
        {
            public void Heavy()
            {
                try
                {
                    System.Threading.Thread.Sleep(5000);
                }
                catch(Exception ex)
                {
                    Console.WriteLine(ex.StackTrace);
                }
            }
        }
    }
Create dicon file Define MeasurementInterceptor as a component. Set name attribute to "measurement". Apply MeasurementInterceptor as an aspect to Heavy() method in HeavyProcess class Measurement.dicon
    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
    "http://www.seasar.org/dtd/components.dtd">
    <components>
    	<component name="measurement"
    		class="S2.NET.Examples.Reference.Aop.MeasurementInterceptor" />

    	<component class="S2.NET.Examples.Reference.Aop.HeavyProcess">
    		<aspect pointcut="Heavy">measurement</aspect>
    	</component>

    	<component name="AopMeasurementInterceptor"
    		class="S2.NET.Examples.Reference.Aop.AopMeasurementClient" />
    </components>
Create execution file Create a container by specifying the path to the created dicon file (Measurement.dicon) as the first argument to S2Container#create() method Get component by specifying class name (HeavyProcess) as the first argument to S2Container#getComponent() method Invoke HeavyProcess#heavy() method gotten from the container AopMeasurementClient.cs
    using System;
    using S2.NET.Framework.Container;
    using S2.NET.Framework.Container.Factory;

    namespace S2.NET.Examples.Reference.Aop
    {
        public class AopMeasurementClient
        {
            private const string PATH = "S2/NET/Examples/Reference/Aop/Measurement.dicon";

            public void Main()
            {
                IS2Container container = S2ContainerFactory.Create(PATH);
                HeavyProcess heavyProcess = (HeavyProcess)
                    container.GetComponent(typeof(HeavyProcess));
                heavyProcess.Heavy();
            }
        }
    }
Execution Result Class name, method name, and arguments are outputted in a trace.
    S2.NET.Examples.Reference.Aop.HeavyProcess#Heavy() : 5007
Files in this example are available in S2/NET/Examples/Reference/Aop folder in S2.NET.Examples project. |
Trans. by H.Ozawa | Copyright© 2004-2007, The Seasar Foundation and the others. All rights reserved.