Introduction
In my previous article, I discussed about Spring.NET's Dependency Injection technique and how it can be used in a real time scenario. This article will focus on another feature called Aspect Oriented Programming and how Spring.NET supports this. This will be the first part of a series of AOP articles that I have planned.
Aspect Oriented Programming (AOP)
AOP is a design principle that helps to separate concerns while developing a multi-layered system. AOP is built using Object Oriented Programming concepts. AOP is implemented using Reflection in modern frameworks/runtimes like CLR and JRE.
Since I mentioned about separation of concerns, it's now time to talk about that. In the AOP world, a concern could be defined as fulfilling a business requirement. For example, fetching a list of customers from a database could be a concern. There are two types of concerns - core and cross-cutting concerns.
- Core concern: This defines the main business requirement. For example, fetching a list of customers, updating customer information etc.
- Cross-cutting concern: This defines the other activities that should happen while performing the core concern. This could span across multiple layers and classes. For example, checking the time taken to retrieve a list of customers for monitoring performance, logging the customer update activity for auditing purposes etc.
Seperation of concerns
Why do we need separation of concerns?
Well, at the high level, we need to have a clean way of doing things. In order to fulfill a business requirement, we are creating software (class/component/method), and we are also creating additional software like logging, exception handling, performance monitoring etc., which is not the actual business requirement. The additional software is mandatory in some areas (like Development, QA environment) and optional in some areas (Production). So, how do we implement the software that is mandatory in some stage and optional in some other stage? The answer is - Separation of concerns.
We separate the actual logic (core concern) and the optional logic (cross-cutting concern) through AOP concepts.
At the low level, it boils down to the separation of the actual logic and the infrastructural logic. We create the class/method that performs the actual logic, and we create a separate class/method that performs the infrastructural logic, and glue them together at runtime based on configuration. In a QA environment, this configuration can be enabled so they are added, and in Production, they can be disabled. The actual logic will not have any infrastructural code, and it can concentrate on what it is supposed to do. All infrastructure code (cross-cutting concern logic) is developed separately.
Following are some of the benefits:
- The core logic will not care about the infrastructural logic
- The infrastructural logic can be plugged in at runtime, enabled/disabled, based on the environment
- Any changes, bug fixes, new versions to the infrastructural logic will not affect the core logic
- Loosely coupled system
AOP terminology
We need to be aware of some AOP terms before jumping further deep.
- Advice: The infrastructure code or extra code that we write to accomplish a cross-cutting concern is created as an Advice and applied to the core concern.
- Point-cut: This is the point at which the Advice needs to be applied to the code. If we wish to write a trace message before the execution of a data access layer method call, then the Point-cut is defined as before executing the data access layer method call.
- Aspect: The combination of an Advice and a Point-cut is termed as an Aspect.
AOP using Spring.NET
AOP is one of the key features offered by Spring.NET. This section explains the steps involved in doing the same.
Scenario
The scenario is very similar to my previous article. I have three layers - Presentation, Business Logic Layer (BLL), and Data Access Layer (DAL). The UI layer implements the MVP pattern in which I created views (WinForms), Presenter classes, and DataTable
s as Models.
The UI calls the Presenter, which interacts with the BLL, the BLL interacts with the DAL and retrieves data from the Northwind database.
Logical architecture
The following diagram shows the layers of the application:
The Data Access Layer has an Around Advice applied to trace the method calls. Basically, we want to write "begin" and "end" trace messages for the Data Access Layer. Without AOP, this is how I would have done to accomplish the tracing requirement:
public DataSet GetAll()
{
TracingComponent.Write("Begin of GetAll()");
DataSet dataSet = null;
TracingComponent.Write("End of GetAll()");
return dataSet;
}
In the above piece of code snippet, we are using the TracingComponent
(a fictitious component) in the code directly. Any changes to the TracingComponent
will affect our Data Access Layer code. Using AOP, things have changed for better, which is what we will be focusing on now.
Steps involved in AOP using Spring.NET
- Create an Advice, and implement the infrastructural code in the Advice
- Configure the Advice, and target objects
- Use the Spring application context to get the object instance
- Call the target method
Having seen the high level steps, it's now time to dive deep into the code to understand how to put things in place.
Creating the Advice
Step 1: Create an Advice. Spring.NET offers many types of Advices. They are the Before Advice, After Advice, Around Advice, and Throws Advice. The names are self-explanatory, and I would leave that learning to you.
In our case, we will create an Around advice. This will help us in intercepting before and after the target method call. We need to implement the interface IMethodInterceptor
that is available in the "AopAlliance.Intercept
" namespace. We need to provide an implementation for the method Invoke()
of IMethodInterceptor
. The signature is given below:
public interface IMethodInterceptor
{
object Invoke(IMethodInvocation invocation);
}
In this, the IMethodInvocation
will hold the reference to the actual object to which the method is called. We will create a class that implements this interface.
using System.IO;
using System.Diagnostics;
using AopAlliance.Intercept;
using AOP.Argument;
public class TraceAroundAdvice : IMethodInterceptor
{
private FileStream _stream = null;
private TextWriterTraceListener _listener = null;
public TraceAroundAdvice()
{
_stream = File.Open(@"C:\temp\Logs.txt", FileMode.Append);
_listener = new TextWriterTraceListener(_stream);
Trace.Listeners.Add(_listener);
}
public object Invoke(IMethodInvocation invocation)
{
string message = string.Format("[{0}] Begin method call {1}",
DateTime.Now.ToString("M/dd/yy hh:m:ss"),
invocation.Method.Name);
Trace.WriteLine(message);
Trace.Flush();
object returnValue = invocation.Proceed();
message = string.Format("[{0}] Completed method call {1}",
DateTime.Now.ToString("M/dd/yy hh:m:ss"),
invocation.Method.Name);
Trace.WriteLine(message);
Trace.Flush();
return returnValue;
}
}
The TraceAroundAdvice
class constructor initializes the trace listener. The Invoke()
method writes messages before and after the invocation.Proceed()
call.
Spring configuration
Spring configuration is very similar to what we learnt in our DI article. We need to include a Spring configuration section, and define a context and object graph. We also need to define the Advices in the object graph.
The following figure shows the configuration for an Advice:
In the above figure, the CustomerDAL
class is defined twice (for demo purpose) - one without the Advice "CustomerDAL
" (which is similar to the DI article), and another with the Advice "CustomerDALWithAdvice
". The object configuration "CustomerDALWithAdvice
" has the Advice "TraceAroundAdvice
" added to it. This Advice is defined in the "InterceptorNames
" property. Note the type of the "CustomerDALWithAdvice
" object property - it shows the "ProxyFactoryObject
" class of the Spring framework. The Target
property points to the actual object, which is the "DAL.Customer.CustomerDAL
" class.
The TraceAroundAdvice
itself has its own object configuration, which is given in the bottom of the figure. The configuration with the Advice can be walked through like this. When "CustomerDALWithAdvice
" is used, the Spring factory (Spring.Aop.Framework.ProxyFactoryObject
) class creates an object defined in the Target
property. In our case, it is "DAL.Customer.CustomerDAL
" which resides in the "DAL" assembly. While creating this instance, it adds the Advices defined in the InterceptorNames
property to the execution pipeline. In our case, TraceAroundAdvice
will be added.
Initialize Spring configuration and call the target method
The following code snippet shows the steps involved in initializing the Spring configuration and getting the IApplicationContext
instance:
using Spring.Context;
using Spring.Context.Support;
IApplicationContext applicationContext = ContextRegistry.GetContext();
IDAL _customerDAL = applicationContext["customerDALWithAdvice"];
_customerBLL.GetAll();
Sample application
The sample application has three layers - UI, BLL, and DAL. Every layer is developed in a separate assembly. The UI layer is a Windows Forms application. Apart from these assemblies, the Advice is maintained in a different assembly called "AOP". The following figure shows the solution structure:
In the UI layer, the form creates a presenter using the DI. The presenter creates the BLL using the DI. The BLL creates the DAL using the DI (when "CustomerDAL
" is used) or AOP (when "CustomerDALWithAdvice
" is used). The configuration for the application is given below:
When the application is run, while using the Customer DAL methods, trace messages before and after the method call will be written to the "C:\Temp\Logs.txt" file.
Conclusion
This article gives an introduction to AOP concepts, the separation of concerns and terminology. We discussed about Spring.NET's AOP support, and the steps involved in creating an Advice and adding it to a target object. We also looked at a sample multi-layered application that uses Spring's AOP and DI techniques.
Happy reading! Happy coding!