Tuesday, April 17, 2012

Aspect Oriented Programming (AOP) with Spring 3

What does it mean?

AOP is a programming paradigm as well as Object Oriented Programming (OOP). In OOP we work with "objects" in a procedural world, meanwhile in AOP we work with "aspects" in a declarative world. You can use AOP concepts while working mainly with OOP, like the example presented below.


What's the basic concept behind?

The basic concept is to declare aspects. An aspect is a method of a class that will run automatically whenever certain conditions occur. Really simple but powerful.

It is similar to database triggers, you need to do something if some event occurs. The simplest example can be when you want to automatically log something before any method of your service layer runs.

Enough, let's see an example!


Hello World Spring 3 AOP example:

First of all we will create a service with a method that just does nothing (DummyService). Then, we will create another service with a method that writes "hello" in the console (SayHelloWorldService). Then we will create a test that instantiates the DummyService bean and runs the dummy method that does nothing, the result will be say hello written on console instead of nothing. This will be achieved by some AOP configuration that will always execute the say hello method before the dummy method is executed.

Versions we will use:
Spring 3.1.1-RELEASE

pom.xml
<repositories>
 <repository>
  <id>springsource-repo</id>
  <name>SpringSource Repository</name>
  <url>http://repo.springsource.org/release</url>
 </repository>
</repositories>

<dependencies>

 <dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.8.2</version>
  <scope>test</scope>
 </dependency>
 
 <!-- These are for Spring beans and to enable the AOP features  -->

 <dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>3.1.1.RELEASE</version>
  <scope>test</scope>
 </dependency>

 <dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.6.12</version>
  <scope>test</scope>
 </dependency>
  
 <dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.6.12</version>
  <scope>test</scope>
 </dependency>

 <dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>2.2.2</version>
  <scope>test</scope>
 </dependency>

</dependencies>

DummyService
package ar.com.pabloExample.services;

public class DummyService {

 public void dummyMethod() {
  
 }
}
This service has a method that does nothing. The final result is that we will always see a Hello World message in the console whenever we run this method. You may be asking how that's possible... well, thanks to AOP whenever we run dummyMethod(), the sayHelloWorld() method will run before...

SayHelloWorldService
package ar.com.pabloExample.services;

public class SayHelloWorldService {
 
 public void sayHelloWorld() {
  System.out.println("--- Hello World!!! ---");
 }
}
This services just prints the Hello World message on the console.

DummyServiceTest
public class DummyServiceTest {
 
 private ApplicationContext applicationContext;
 private DummyService dummyService;

 @Before
 public void init() {
  applicationContext = new ClassPathXmlApplicationContext("test-beans.xml");
  dummyService = (DummyService) applicationContext.getBean("dummyService");
 }

 @Test
 public void dummyMethodTest() {
  
  dummyService.dummyMethod();
 }
}
This is the test that will simply instantiate a DummyService bean and run the dummyMethod() to see the result.


Creating the test-beans.xml with AOP configuration:

For this test to run we need to define these beans:
<bean class="ar.com.pabloExample.services.SayHelloWorldService" id="sayHelloWorldService">
 
<bean class="ar.com.pabloExample.services.DummyService" id="dummyService">

The AOP configuration is:

<aop:config>
 <aop:aspect id="myAspect" ref="sayHelloWorldService">
  <aop:pointcut expression="execution(public void ar.com.pabloExample.services.*.*(..))" id="pointcut1" />
  <aop:before method="sayHelloWorld" pointcut-ref="pointcut1" />
 </aop:aspect>
</aop:config>

<aop:config>
</aop:config>
This tag defines an AOP configuration section in the xml.

<aop:config>
 <aop:aspect id="myAspect" ref="sayHelloWorldService" />
</aop:config>
We declare an aspect referencing the bean with the service we want to execute.

<aop:pointcut expression="execution(public void ar.com.pabloExample.services.*.*(..))" id="pointcut1" />
The pointcut is the condition that must occur so that this aspect runs. In this case the condition is "This aspect will run for any method of any class with any parameter that is under the package ar.com.pabloExample.services and returns a void and is public"

<aop:before method="sayHelloWorld" pointcut-ref="pointcut1" />
This AOP will run the sayHelloWorld() before the dummyMethod() in this case.

So, the xml with the beans ends like this:

test-beans.xml
<beans xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">

 <aop:config>
  <aop:aspect id="myAspect" ref="sayHelloWorldService">
   <aop:pointcut expression="execution(public void ar.com.pabloExample.services.*.*(..))" id="pointcut1" />
   <aop:before method="sayHelloWorld" pointcut-ref="pointcut1" />
  </aop:aspect>
 </aop:config>

 <bean class="ar.com.pabloExample.services.SayHelloWorldService" id="sayHelloWorldService" />
 
 <bean class="ar.com.pabloExample.services.DummyService" id="dummyService" /> 
</beans>

Now you can run the test and see the results.

The same AOP configuration can be done with annotations instead of xml configuration, but I prefer the xml way because AOP configuration is decoupled from the java classes, and a change of AOP configuration doesn't require a new compilation. A really nice example on how to do profiling method execution using annotations can be found here.

The main disadvantage of AOP is that the flow of the program structure may change completely (it depends on how much you depend on aspects...) and it becomes more complex because it is more dificult now to track issues looking at the code.

More information about Spring 3 AOP can be found here

No comments:

Post a Comment