Monday, January 17, 2011

Aspect Oriented Programming using SpringAOP

While developing software applications for a business we do recieve the requirements either from requirements gathering team or from business analysts. In general those requirements are functional requirements which represents the activities that the business is doing. while developing software applications, apart from the functional requirements we should also consider some other points like performance, transaction management, security, logging etc. These are called non-functional requirements.

Suppose let us consider a BookStore application which is providing web access the store. User can browse the various categories of books, add the interested books to cart and finally checkout, do payment and get the books.

For this app we might receive the requirements from business analyst as follows:
1. A login/registration screen to enter into BookStore.
2. Users should be able to browse through various categories of books
3. Users should be able to search books by name, author name, publisher
4. Users should be able to add/remove the books to/from his cart
5. Users should be able to see what are all the items currently in his cart
6. Users should be able to checkout and provide facility to pay the amount through some payment gateway
7. A successful message will be shown to users with all the details of his purchases.
8. A failure message will be shown to users with the cause of failure.
9. BookStore administrator/manager should be able to provide the access to add/remove/update book details.


All the above requirements comes under Functional Requirements. While implementing the above we should also take care of the following things even though they are not explicitly mentioned:

1. Role based access to UI. Here only Administrators/Managers only can access have access to add/remove/update book details.[Role based Authorization]
2. Atomicity in Purchasing. Suppose a user logged into the BookStore and add 5 books to his cart, checked out and did payment. In the back-end implementation we may need to enter this purchase details in 3 tables. If after inserting the data into 2 tables and system crashed the whole operation should be rolled-back.[Transaction Management].
3. No one is perfect and no system is flawless. So if something went wrong and the development team has to figure it out what went wrong logging will be most useful. So logging should be implemented in such a way that developer should be able to figured it out where exactly it got failed and fix it.[Logging]

The above implicit requirements are called non-functional requirements.  In addition to the above Performance will obviously be a crucial non-functional requirement for many public facing websites.

So with all the above functional requirements we can build the system decomposing the whole system into various components by taking care of non-functional requirements through out the components.

public class OrderService
{
    private OrderDAO orderDAO;
    
    public boolean placeOrder(Order order)
    {
        boolean flag =false;
        logger.info("Entered into OrderService.placeOrder(order) method");
        try
        {
            flag = orderDAO.saveOrder(order);
        }
        catch(Exception e)
        {
            logger.error("Error occured in OrderService.placeOrder(order) method");
        }
        logger.info("Exiting from OrderService.placeOrder(order) method");
        return flag;
    }
}

public class OrderDAO
{
    public boolean saveOrder(Order order)
    {
        boolean flag =false;
        logger.info("Entered into OrderDAO.saveOrder(order) method");
        Connectoin conn = null;
        try
        {
            conn = getConnection();//get database connection
            conn.setAutoCommit(false);
            // insert data into orders_master table which generates an order_id
            // insert order details into order_details table with the generated order_id
            // insert shipment details into order_shipment table
            conn.commit();
            conn.setAutoCommit(true);
            flag = true;
        }
        catch(Exception e)
        {
            logger.error("Error occured in OrderDAO.saveOrder(order) method");
            conn.rollback();
        }
        logger.info("Exiting from OrderDAO.saveOrder(order) method");
        return flag;
    }
}
Here in the above code, the functional requirement implementation and non-functional requirement implementation is mingled in the same place.
Logging is placed accross OrderService and OrderDAO classes. Transaction Management is spanned across DAOs.
With this we will have several issues:
1. The classes needs to be changed either to change functional or non-functional requirements.
    For Ex: At some point later in the development if the Team decides to log the Method Entry/Exit information along with TimeStamp we need to change almost all the classes.
   
2. The Transaction Management code setting the auto-commit to false in the beginning, doing the DB operations, committing/rollbacking the operation logic will be duplicated across all the DAOs.

Here if we see Method Entry/Exit logging is spanned across all the modules. Transaction Management is spanned across all the DAO's.
These kind of requirements which span across the modules/components is called Cross Cutting Concerns.


To better design the system we should separate out these cross cutting concerns from actual business logic so that it will be easier to change or enhance or maintain later point of time.

Aspect Oriented Programming is a methodology which says separate the cross cutting concerns from actual business logic.

So let us follow AOP methodology and redesign the above two classes separating the cross cutting concerns.

public interface IOrderService
{
    public boolean placeOrder(Order order);
}
public class OrderService implements IOrderService
{
    private OrderDAO orderDAO;
    
    public boolean placeOrder(Order order)
    {
        return orderDAO.saveOrder(order);
    }
}
public class OrderDAO
{
    public boolean saveOrder(Order order)
    {
        boolean flag =false;
        
        Connectoin conn = null;
        try
        {
            conn = getConnection();//get database connection
            // insert data into orders_master table which generates an order_id
            // insert order details into order_details table with the generated order_id
            // insert shipment details into order_shipment table
            flag = true;
        }
        catch(Exception e)
        {
            logger.error(e);            
        }        
        return flag;
    }
}
Now lets create a LoggingInterceptor implementing how logging should be done and create a Proxy for OrderService which takes the call from caller, log the entry/exit entries using LoggingInterceptor and delegates to actual OrderService.

By using Dynamic Proxies we can separate out implementation of cross cutting concerns(Logging) from actual business logic as follows.
public class LoggingInterceptor
{
    public void logEntry(Method m)
    {
        logger.info("Entered into "+m.getName()+" method");
    }
    public void logExit(Method m)
    {
        logger.info("Exiting from "+m.getName()+" method");
    }
}
public class OrderServiceProxy implements IOrderService extends LoggingInterceptor
{
    private OrderService orderService;
    
    public boolean placeOrder(Order order)
    {
        boolean flag =false;
        Method m = getThisMethod();//get OrderService.placeOrder() Method object
        logEntry(m);
        flag = orderService.placeOrder(order);
        logExit(m);
        return flag;
    }
}
Now the OrderService caller(OrderController) can get the OrderServiceProxy and place the order as:
public class OrderController
{
    public void checkout()
    {
        Order order = new Order();
        //set the order details
        IOrderService orderService = getOrderServiceProxy();
        orderService.placeOrder(order);
    }
}
We have several AOP frameworks to seperate out implementation of cross cutting concerns.
a)Spring AOP
b)AspectJ
b)JBoss AOP

Now lets see how we can separate out Logging from actual business logic using Spring AOP.

Before going to use Spring AOP, first we need to understand the following:

JoinPoint:: A joinpoint is a point in the execution of the application where an aspect can be plugged in. This point could be a method being called, an exception being thrown, or even a field being modified.

Pointcut: A pointcut definition matches one or more joinpoints at which advice should be woven. Often you specify these pointcuts using explicit class and method names or through regular expressions that define matching class and method name patterns.

Aspect: An aspect is the merger of advice and pointcuts.

Advice:The job of an aspect is called advice.

SpringAOP supports several types of advices:
1. Before: This advice weaves the aspect before method call.
2. AfterReturning: This advice weaves the aspect after method call.
3. AfterThrowing: This advice weaves the aspect when method throws an Exception.
4. Around: This advice weaves the aspect before and after method call.

Suppose we have the following ArithmeticCalculator interface and implementation classes.
package com.springapp.aop;
public interface ArithmeticCalculator
{
    public double add(double a, double b);
    public double sub(double a, double b);
    public double mul(double a, double b);
    public double div(double a, double b);
}
package com.springapp.aop;
import org.springframework.stereotype.Component;

@Component("arithmeticCalculator")
public class ArithmeticCalculatorImpl implements ArithmeticCalculator
{
    public double add(double a, double b)
    {
        double result = a + b;
        System.out.println(a + " + " + b + " = " + result);
        return result;
    }

    public double sub(double a, double b)
    {
        double result = a - b;
        System.out.println(a + " - " + b + " = " + result);
        return result;
    }

    public double mul(double a, double b)
    {
        double result = a * b;
        System.out.println(a + " * " + b + " = " + result);
        return result;
    }

    public double div(double a, double b)
    {
        if(b == 0)
        {
            throw new IllegalArgumentException("b value must not be zero.");
        }
        double result = a / b;
        System.out.println(a + " / " + b + " = " + result);
        return result;
    }
}
The following LoggingAspect class shows various bit and pieces of applying Logging Advice using SpringAOP.
package com.springapp.aop;

import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect
{
    private Log log = LogFactory.getLog(this.getClass());
    
    @Pointcut("execution(* *.*(..))")
    protected void loggingOperation() {}
    
    @Before("loggingOperation()")
    @Order(1)
    public void logJoinPoint(JoinPoint joinPoint)
    {
        log.info("Join point kind : " + joinPoint.getKind());
        log.info("Signature declaring type : "+ joinPoint.getSignature().getDeclaringTypeName());
        log.info("Signature name : " + joinPoint.getSignature().getName());
        log.info("Arguments : " + Arrays.toString(joinPoint.getArgs()));
        log.info("Target class : "+ joinPoint.getTarget().getClass().getName());
        log.info("This class : " + joinPoint.getThis().getClass().getName());
    }
        
    @AfterReturning(pointcut="loggingOperation()", returning = "result")
    @Order(2)
    public void logAfter(JoinPoint joinPoint, Object result)
    {
        log.info("Exiting from Method :"+joinPoint.getSignature().getName());
        log.info("Return value :"+result);
    }
    
    @AfterThrowing(pointcut="execution(* *.*(..))", throwing = "e")
    @Order(3)
    public void logAfterThrowing(JoinPoint joinPoint, Throwable e)
    {
        log.error("An exception has been thrown in "+ joinPoint.getSignature().getName() + "()");
        log.error("Cause :"+e.getCause());
    }
    
    @Around("execution(* *.*(..))")
    @Order(4)
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable
    {
        log.info("The method " + joinPoint.getSignature().getName()+ "() begins with " + Arrays.toString(joinPoint.getArgs()));
        try
        {
            Object result = joinPoint.proceed();
            log.info("The method " + joinPoint.getSignature().getName()+ "() ends with " + result);
            return result;
        } catch (IllegalArgumentException e)
        {
            log.error("Illegal argument "+ Arrays.toString(joinPoint.getArgs()) + " in "+ joinPoint.getSignature().getName() + "()");
            throw e;
        }        
    }
    
}
applicationContext.xml
    
    
    

And  standalone test client to est the functionality.
package com.springapp.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringAOPClient
{

    public static void main(String[] args)
    {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        ArithmeticCalculator calculator = (ArithmeticCalculator) context.getBean("arithmeticCalculator");
        double sum = calculator.add(12, 23);
        System.out.println(sum);
        double div = calculator.div(1, 10);
        System.out.println(div);
    }

}
Required jars:

Spring.jar(2.5.6 or above)
commons-logging.jar
aopalliance.jar

aspectjrt.jar
aspectjweaver.jar
cglib-nodep-2.1_3.jar


We can define the type of advice using @Before, @AfterReturning, @Around etc. We can define pointcuts in different ways.
@Around("execution(* *.*(..))") means it is an Around advice which will be applied to all classes in all packages and all methods.
Suppose if we want to apply only for all the services resides in com.myproj.services package, the pointcut would be
@Around("execution(* com.myproj.services.*.*(..))"). "(..)" mean with any type of arguments.

If we want to apply same pointcuts for many advices we can define a pointcut on a method and can refer that later as follows.
    @Pointcut("execution(* *.*(..))")
    protected void loggingOperation() {}
    
    @Before("loggingOperation()")
    public void logJoinPoint(JoinPoint joinPoint)
    {
    }
    
If multiple Advices has to be applied on same pointcut we can specify the order using @Order on which advices will be applied.
In the above example @Before will be applied first then @Around will be applied when add() method is called.

With AOP approach the code will be more cleaner and maintainable. SpringAOP is one way of implementing AOP and only supports Method invokation join point. AspectJ is even more powerful and can be applied on several joinputs in addition to Method invokation join point. Spring is also supporting AspectJ integration.

13 comments:

  1. Your explanation is fantastic.
    I had read AOP but never implemented but reading your excellent article would implement it very soon in an application I am working on.

    ReplyDelete
  2. I tried as above but getting these exceptions

    "Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/aop]"
    Do you have an idea?

    ReplyDelete
  3. The above problem is explained here.
    http://www.mysticcoders.com/blog/2009/03/02/maven-assembly-plugin-and-spring/

    ReplyDelete
  4. Hi Rishi,
    The above error is coming because you might not have org.springframework.aop-3.0.5.RELEASE.jar in your classpath.

    Put the following jars in your classpath and try:

    commons-logging.jar
    aspectjrt.jar
    aspectjweaver.jar
    aopalliance.jar
    cglib-nodep-2.1_3.jar
    org.springframework.beans-3.0.5.RELEASE.jar
    org.springframework.context-3.0.5.RELEASE.jar
    org.springframework.core-3.0.5.RELEASE.jar
    org.springframework.expression-3.0.5.RELEASE.jar
    org.springframework.aop-3.0.5.RELEASE.jar
    org.springframework.asm-3.0.5.RELEASE.jar

    ReplyDelete
  5. Siva,
    I was adding various spring files in my classpath but as per the link posted by me above. If you include just one spring file (containing everything) then this works fine.

    Really wonderful to see you reply to the query.

    Thanks.

    ReplyDelete
  6. Siva,
    yes problem is resolved .Thanks very much.

    ReplyDelete
  7. hi siva, i tried implementing this code but the logging is not displayed on the console.In my context i defined as below







    where all my packages start with com.cc. I defined my controller with @component and was expecting my console to be filled up with logs.Am i missing something?

    ReplyDelete
  8. Hi Siva,

    is it possible to apply this as a webservices example?

    ReplyDelete
  9. Hi Siva,


    Thanks for writing this wonderful post. I am not able to see the logs. Where to see the logs?













    ReplyDelete
  10. Good introduction to AOP for starters!

    PS
    For logging it is probably good idea not to re-invent the wheel
    http://aspect4log.sf.net - this one is very easy to use, i was able to make it running with my project within 5 min.
    http://loggifier.unkrig.de - this one is complex and not that well document but claims that it can instrument already compiled jar/war/ear files!

    ReplyDelete
  11. Good introduction to AOP for starters!

    PS
    For logging it is probably good idea not to re-invent the wheel
    http://aspect4log.sf.net - this one is very easy to use, i was able to make it running with my project within 5 min.
    http://loggifier.unkrig.de - this one is complex and not that well document but claims that it can instrument already compiled jar/war/ear files!

    ReplyDelete