Aspect Oriented Programming with Spring
Introduction
In an ideal Object Oriented System, we would want to design each object to perform one specific task. However apart from performing its main task the objects also performs passive task like logging, transactions, security, caching etc. These passive activities which are necessary but not part of business logic are called “Cross Cutting Concerns“.
(Cross Cutting Concerns == Commonly Used Functionality across the system)
Separating Cross Cutting Concerns from business logic can be a major step towards writing an well designed decoupled code. Let us ponder over ways to deal with separating Cross Cutting Concerns
Inheritance
Inheritance pops up in the mind straight away, we can inherit the common functionality and use it in our objects. But Inheriting the common functionality requires us to design us a base class. If we reuse this class in multiple places then modifying the class could be a tough job later on.
Inheritance == Difficult to modify later(inelastic code)
Delegation
Delegation is a better way of dealing with the cross Cutting Concerns. Remember Composition over Inheritance, (delegation and composition share a common concerns). But Then we would have to make calls to delegate objects at many places thus making it cumbersome.
Delegation == Cumbersome
Aspect Oriented Programming
Does this mean we are in soup. Rather not, this leaves us with a third and the best approach of all, Aspect Oriented Programming. AOP spares us the brittleness of Inheritance and cumbersomeness of delegation. AOP shines in area of separting cross cutting concerns
What is AOP?
AOP allows us to modularize cross cutting concerns into special objects called Aspects, thereby creating a cleaner and decoupled code. With aspects in place objects no longer have to worry about performing the passive cross cutting concerns as AOP takes care of it all.
Terminologies related to AOP
Like any successful technologies AOP comes with its own sets of jargon and terminologies. Let us take a glance at those before moving to more serious business of understanding AOP.
- Concerns – These are the part of system modularized based on their functions. There are two types of concerns. 1. Core concerns 2. Cross Cutting concerns. Core concerns are related to the business logic of the system i.e active tasks that system performs like generating salary slip, getting employee record, doing bank transfer etc. Cross cutting concerns are the passive tasks that are required to perform the active tasks like logging, caching etc.
- Joinpoint – Joinpoint is a point in execution flow where some action takes place and a possibility arises to apply an Aspect(cross cutting concern). A joinpoint can be method being invoked, exception being thrown or change in state of an object.
- Advice – Each Aspect in AOP has a purpose i.e. the job it has to do. This job has to be applied at a Joinpoint. Job or purpose of an Aspect is called Advice. Apart from defining the job of aspect, Advice also defines the time when the Aspect is to perform the job. Should the job be applied before or after or both before and after a core concern finishes its execution.
- Pointcut – There can be many Joinpoints in the system, but not all are chosen to be advised by an Aspect. Aspect takes help from Pointcut to choose the Joinpoint where advise is to be woven.
- Aspect – Advice and Pointcut defines an Aspect. As we saw that Advice defines the job of an Aspect and when to perform it. While Pointcut defines the location where aspect weaves it advice. So what, when and where of a job defines the Aspect.
- Target – Target is the object which is being adviced. (Core Concern). With help of AOP this object is free to perform its primary task without worrying about cross cutting concerns.
- Proxy – When advice is applied to a target object a Proxy object is created. AOP container creates and manages the lifecycle of object and programmers need not worry about them.
- Weaving – Weaving is the process of applying Advice or Aspect to the target object to create the proxy object. Weaving can be done at compile time or classloading time or at runtime. Typically Spring AOP weaves aspect in the target object at runtime.
That’s a long list of terms to digest. Take your time in understanding them before moving on.
Types of Advice
One Final piece before indulging in an example is to learn about type of advice. Mainly there are 4 types of advice.
- Before Advice – Before advice is applied before the Joinpoint starts execution. BeforeAdvice is created by implementing org.springframework.aop.MethodBeforeAdvice interface. The method to be implemented is public void before(Method m, Object args[], Object target) throws Throwable
- After Returning Advice – After advice is applied after the Joinpoint completes executing. AfterReturningAdvice is created by implementing org.springframework.aop.AfterReturningAdvice interface. The method to be implemented is public void afterReturning(Method m, Object args[], Object target) throws Throwable
- Throws Advice – Throws advice is applied when Joinpoint throws an exception during execution.
- Around Advice – This advice surrounds the Joinpoint execution and is executed before and after Joinpoint execution. This can even be use to control the invocation of a Joinpoint.
Example
We will try to develop a simple cache with help of SpringAOP. Caching has three main core concerns.
Core Concerns
- Save object in Cache.
- Return object from Cache.
- Delete object from Cache.
Now apart from these core concerns caching framework has other passive task. These passive tasks forms the cross cutting concern.
Cross Cutting Concerns
- Re-sizing the cache when it reaches its size limit. (LRU) implementation.
- Locking an object to prevent from deletion when it is being read.
- Locking the cache to prevent and read/writes/deletes when it is getting re-sized.
Coding for all these cross cutting concerns can be time taking and tedious so let us simplify the example and we will just implement the re-size logic when the cache is full. So after example is done we will have a cache where we can put, get and delete objects. There is a max size of cache which has been set to 10 in example. Once the cache stores 10 object then any addition to the cache will result in deletion (re-sizing) of cache by deletion of first object. The re-sizing operation is controlled by an Aspect created using Spring AOP. Here are the steps to be followed in the example
Example Code Can be downloaded from SVN here: https://www.assembla.com/code/weblog4j/subversion/nodes/31/SpringDemos/trunk
- Dependencies – AOP is a core functionality of spring so to get Spring AOP going all we need are core spring jar so in your POM add following dependencies.
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency>
- Core Caching object.
package com.aranin.spring.aop; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; public class MyCache { private LinkedHashMap<String, Object> cacheMap = new LinkedHashMap<String, Object>(); private LinkedHashMap<String, Date> timeStampMap = new LinkedHashMap<String, Date>(); /** * defines the max size of hashmap */ private long maxsize = 10; //should come from properties file or some configuration /** * how long the object should be stored before it is evicted from cache */ private long objectLifeTime = 10000; private boolean lock = false; public LinkedHashMap<String, Object> getCacheMap() { return cacheMap; } public void setCacheMap(LinkedHashMap<String, Object> cacheMap) { this.cacheMap = cacheMap; } public LinkedHashMap<String, Date> getTimeStampMap() { return timeStampMap; } public void setTimeStampMap(LinkedHashMap<String, Date> timeStampMap) { this.timeStampMap = timeStampMap; } public long getMaxsize() { return maxsize; } public void setMaxsize(long maxsize) { this.maxsize = maxsize; } public long getObjectLifeTime() { return objectLifeTime; } public void setObjectLifeTime(long objectLifeTime) { this.objectLifeTime = objectLifeTime; } public boolean isLock() { return lock; } public void setLock(boolean lock) { this.lock = lock; } /** * This method is used to retrive the object from cache * @param key * @return */ public Object get(String key){ return this.getCacheMap().get(key); } /** * this method is used for putting an object in cache * @param key * @param object */ public void put(String key, Object object){ //get the curr date Date date = new Date(System.currentTimeMillis()); //set object in cacheMap this.getCacheMap().put(key,object); //put timestamp in cache this.getTimeStampMap().put(key, date); } public void delete(String key){ this.getCacheMap().remove(key); this.getTimeStampMap().remove(key); } public void clearAll(){ this.setCacheMap(new LinkedHashMap<String, Object>()); this.setTimeStampMap(new LinkedHashMap<String, Date>()); } /** * remove last 2 entries * not worried about object life time * this is just an example */ public void resize(){ System.out.println("inside resize"); long size = this.getCacheMap().size(); System.out.println("size + " + size); if(size == this.getMaxsize()){ System.out.println("max size has reached"); Map.Entry<String, Date> firstEntry = this.getTimeStampMap().entrySet().iterator().next(); System.out.println("removing : " + firstEntry.getKey() + " value : " + firstEntry.getValue()); this.timeStampMap.remove(firstEntry.getKey()); Map.Entry<String, Object> firstCEntry = this.getCacheMap().entrySet().iterator().next(); System.out.println("removing : " + firstCEntry.getKey() + " value : " + firstCEntry.getValue()); this.cacheMap.remove(firstCEntry.getKey()); } System.out.println("leaving resize with size : " + this.getCacheMap().size()); } }
There is nothing much to say about this class. There are two LinkedHashMaps one which stores the object and the other stores the timestamp when object was pushed in the cache. The max size is set to 10 and it has get, put and delete methods. Also there is a re-size method which will be called by Aspect as we will check later.
- Resize Advice
package com.aranin.spring.aop; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; public class ResizeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("invoking " + method.getName() + " on " + target.getClass() + " Object"); if(method.getName().equals("put")){ System.out.println("before invoking " + method.getName()); ((MyCache)target).resize(); } } }
As you can see this is a method before advice. Class implements MethodBeforeAdvice interface which contains a single mthod before(). If you examine the method you will check that rezise method is called when ever we call a put method.
- Spring context springaopdemo.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <bean id="resizeAdvice" class="com.aranin.spring.aop.ResizeAdvice" /> <bean id="myCache" class="com.aranin.spring.aop.MyCache" /> <bean id="myAOPCache" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="myCache" /> <property name="interceptorNames"> <list> <value>resizeAdvice</value> </list> </property> </bean> </beans>
If you notice the above xml file, both MyCache and ResizeAdvice have been registered as spring bean. The main bean in the file is myAOPCache. This is the proxy object that spring aop creates after applying the advice on the core class. The proxy object is created by ProxyFactoryBean class. We pass a reference of myCache object to the proxy object and also register all the advice which are to be applied to the proxy classes.
- Finally let check the Client which will help us running this demo.
package com.aranin.spring.aop; import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; public class MyCacheClient { public static void main(String[] args){ ApplicationContext springcontext = new FileSystemXmlApplicationContext("D:/samayik/SpringDemos/src/main/resources/springaopdemo.xml"); MyCache myCache = (MyCache)springcontext.getBean("myAOPCache"); myCache.put("1", "1"); myCache.put("2", "2"); myCache.put("3", "3"); myCache.put("4", "4"); myCache.put("5", "5"); myCache.put("6", "6"); myCache.put("7", "7"); myCache.put("8", "8"); myCache.put("9", "9"); myCache.put("10", "10"); System.out.println((String)myCache.get("1")); System.out.println((String)myCache.get("2")); System.out.println((String)myCache.get("10")); myCache.put("11", "11"); System.out.println((String)myCache.get("1")); System.out.println((String)myCache.get("2")); System.out.println((String)myCache.get("10")); System.out.println((String)myCache.get("11")); } }
In this class we start the spring container and load the beans present in spingaopdemo.xml. We push 10 objects in the cache and when we try to push the 11th object then the first one is deleted and 11th inserted. Output is big so I am not posting the output. Run the class and check the output to your satisfaction.
Summary
In this post we learnt how to better deal with cross cutting concerns using Aspect Oriented Programming. AOP is a powerful concept that allows us write cleaner and decoupled code. AOP does not provide any thing new. All it does is to segregate the business logic from other mundane tasks that system has to perform. It enables reuse of code implementing system wide cross cutting concerns. We also learnt the various terminologies associated with AOP. Last but not the least we saw a simple example where we created a simple before method advice using Spring AOP and applied it to manage our caching system.
Note
You can freely use and distribute the caching system developed in this code. Though using it in production system is not advisable.
As and always I intend this post as a launching platform for collective learning, feel free to drop in a comment or two about what you feel about AOP and how you to plan to use it in your code. Happy reading.
Excellent explanation of AOP concept and its terms. Great JOB !!!