AOP framework is one of the key components of the Spring framework. AOP provides a facility to enable middleware services with the Spring application in a loosely coupled manner. Middleware services are the additional services that makes our application to be executed in a stable manner in all the situations. Middleware services can be like transaction management, security, mailing, logging etc.
By using Spring AOP, we can write the actual application and middleware services codes separately and then we can link these both codes through any of the AOP’s implementation approaches (programmatic, declarative XML, annotations). The main aim of AOP is to separating services from the actual business code. In simple words, you can add some more functionality to your business code without writing anything extra in your actual code.
AOP Terminologies :
These are some central AOP concepts and terminologies. These terms are not spring specific but they are related to AOP.
– Aspect : It is the service class which contains advices, join points etc. For example, a logging module would be known as AOP aspect for logging. An application may have any number of aspects as per the requirement.
– Join point : It is the possible position in your business method where the advice can be applied. This point can be execution of a method or handling of an exception.
– Advice : This is the actual code which is invoked during the method execution either before or after by Spring AOP framework.
– Pointcut : It is a point or a condition to execute aspects for business methods by linking the advice with the join points.
– Introduction : It allows you to introduce new additional methods or attributes to the existing classes.
– Target Object : It is the object on which we will apply the services. It will always be a proxied object.
– AOP Proxy : If you add business along with your service, it will combine these both and finally delivers a proxy object which will always be a child object of business class.
– Weaving : Weaving means combining, it combines both your service and business at runtime in Spring AOP.
– Advisor : It is the combination of pointcut and advice.
Types of Advice :
These are different types of advices :
– Before advice : It executes before the method execution.
– After returning advice : It executes after the method execution only if method completes successfully.
– After throwing advice : It executes after the method execution only if method exits by throwing an exception.
– After (finally) advice : It executes after the method execution irrespective of it’s termination either normally or abnormally.
– Around advice : It executes before and after of the advised method execution.
Now let’s try to understand Spring AOP by a simple example of logging. For better understanding, we will use Spring dtd based AOP implementation which will be a programmatic approach with XML configuration.
Required JARs :
– AOP libraries
~ spring-aop
~ spring-aspects
~ aspectj
~ aspectjrt
~ aspectjweaver
~ aopalliance
~ cglib-nodep
– Other libraries :
~ spring-core
~ spring-context
~ spring-beans
~ commons-logging
~ spring-expression
Create a Bank class under business package in which there will be two simple methods defined.
package business; public class Bank { private int amount = 10000; private String accountNo = "bank001163"; //1st method public int balanceDeposite(String accountNumber, int amountDeposite) { System.out.println("we are in balanceDeposite() method !!"); if (accountNumber.equalsIgnoreCase(this.accountNo)) { this.amount = this.amount + amountDeposite; return this.amount; } else { throw new InvalidAccountException(); } } //2nd method public int getBalance(String accountNumber) { System.out.println("we are in getBalance() method !!"); if (accountNumber.equalsIgnoreCase(this.accountNo)) { return this.amount; } else { throw new InvalidAccountException(); } } }
Define a customized exception for the Bank class within the same package, InvalidAccountException class.
package business; public class InvalidAccountException extends RuntimeException { @Override public String toString() { return "Please check your a/c number !!"; } }
Now, create advice & pointcut classes under services package.
BeforeService.java >> This will log an information before the method executes.
package services; import java.lang.reflect.Method; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.MethodBeforeAdvice; import business.Bank; public class BeforeService implements MethodBeforeAdvice { @Override public void before(Method m, Object[] params, Object o) throws Throwable { Log l = LogFactory.getLog(Bank.class); l.info("balanceDeposite() method : start || (BeforeService)"); } }
AfterService.java >> This will log an information after the method executes successfully.
package services; import java.lang.reflect.Method; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.AfterReturningAdvice; import business.Bank; public class AfterService implements AfterReturningAdvice { @Override public void afterReturning(Object returnValue, Method m, Object[] params, Object o) throws Throwable { Log l = LogFactory.getLog(Bank.class); l.info("balanceDeposite() method : end || (balance = " + returnValue.toString() + ") || (AfterService)"); //'returnValue' is the value returned by 'balanceDeposite()' method. } }
AroundService.java >> This will log informations before and after the method executes.
package services; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import business.Bank; public class AroundService implements MethodInterceptor { @Override public Object invoke(MethodInvocation mi) throws Throwable { Log l = LogFactory.getLog(Bank.class); l.info("balanceDeposite() method : start || (AroundService)"); //line 16 Object returnObject = mi.proceed(); //line 18 /* line 18 will execute first line 16 then 'balanceDeposite()' method and at the end it will execute line 25. 'returnObject' is the value in the form of an object returned by 'balanceDeposite()' method which was an integer. */ l.info("balanceDeposite() method : end || (AroundService)"); //line 25 return returnObject; } }
ThrowsService.java >> This will log an information along with the exception message if any exception occur in method execution.
package services; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.ThrowsAdvice; import business.Bank; public class ThrowsService implements ThrowsAdvice { /* 'ThrowsAdvice' interface contains five methods. And it doesn't forces you to implement any of it's method. at runtime our proxy will add all these five methods. from these five methods, if you are providing any of it's implementation, then our proxy will not provide that particular method's implementation. */ //this method will execute iff any exception raised in our 'balanceDeposite()' method. public void afterThrowing(Exception e) throws Throwable { Log l = LogFactory.getLog(Bank.class); l.error("exception raised in balanceDeposite() method || (" + e.toString() + ")"); } }
If we will apply these services to our business without any condition, in such case these all services will execute for each method of the Bank class. But we want to execute services only for some methods. For doing this we have to use pointcut classes along with the advice in advisors.
PointcutForDeposite.java >> This will put a condition to the services to execute only for ‘balanceDeposite()’ method.
package services; import java.lang.reflect.Method; import org.springframework.aop.support.StaticMethodMatcherPointcut; public class PointcutForDeposite extends StaticMethodMatcherPointcut { @Override public boolean matches(Method m, Class c) { /* this 'matches()' method will check here the required method's name for which we want to execute a service. if the method name is correct then it returns TRUE else returns FALSE. */ String methodName = m.getName(); if (methodName.equalsIgnoreCase("balanceDeposite")) { return true; } else { return false; } } }
Now, come to the main XML configuration file (spring.xml in resources package).
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> <beans> <bean id = "bank" class = "business.Bank" /> <!-- target --> <bean id = "bfs" class = "services.BeforeService" /> <!-- before advice --> <bean id = "afs" class = "services.AfterService" /> <!-- after advice --> <bean id = "ars" class = "services.AroundService" /> <!-- around advice --> <bean id = "ts" class = "services.ThrowsService" /> <!-- throws advice --> <bean id = "pfd" class = "services.PointcutForDeposite" /> <!-- pointcut (condition) --> <!-- in lines 17-21, we are allowing around advice to execute only for 'balanceDeposite()' method --> <bean id = "dpa" class = "org.springframework.aop.support.DefaultPointcutAdvisor"> <!-- default advisor class --> <!-- in this class we have setter methods for service & pointcut --> <property name = "advice" ref = "ars" /> <!-- set advice --> <property name = "pointcut" ref = "pfd" /> <!-- set pointcut --> </bean> <bean id = "pfb" class = "org.springframework.aop.framework.ProxyFactoryBean"> <!-- proxy --> <property name = "target" ref = "bank" /> <!-- set target --> <property name = "interceptorNames"> <!-- adding advices/advisors --> <list> <!-- passing string type array of advices/advisors --> <value>bfs</value> <value>afs</value> <!-- <value>ars</value> --> <!-- line 28 || in this case, around advice will execute for all methods --> <value>dpa</value> <!-- line 29 || around advice will execute only for 'balanceDeposite()' method --> <value>ts</value> </list> </property> </bean> </beans>
Finally create a test class Client.java under client package.
package client; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import business.Bank; public class Client { public static void main(String[] args) { //load proxy into container ConfigurableApplicationContext cap = new ClassPathXmlApplicationContext("resources/spring.xml"); //get generated proxy object Bank bProxy = (Bank) cap.getBean("pfb"); /* 'pfb' is a factory bean, and factory bean always needs to return other class's object. here it returns Bank class's object. */ int accountBalance = bProxy.balanceDeposite("bank001163", 7000); //line 22 System.out.println("your account balance is : " + accountBalance); //line 23 // int accountBalance = bProxy.getBalance("bank001163"); //line 25 // System.out.println("your account balance is : " + accountBalance); //line 26 cap.close(); //closing container. } }
The outputs for this example will be like :
// 1. Before, After & Around services executes for 'balanceDeposite()' method. INFO: balanceDeposite() method : start || (BeforeService) INFO: balanceDeposite() method : start || (AroundService) we are in balanceDeposite() method !! INFO: balanceDeposite() method : end || (AroundService) INFO: balanceDeposite() method : end || (balance = 17000) || (AfterService) your account balance is : 17000 // 2. If we pass a wrong a/c no. from Client.java to 'balanceDeposite()' method, Throws advice will execute for 'balanceDeposite()' method. INFO: balanceDeposite() method : start || (BeforeService) INFO: balanceDeposite() method : start || (AroundService) we are in balanceDeposite() method !! SEVERE: exception raised in balanceDeposite() method || (Please check your a/c number !!) // 3. If we comment line 22, 23 and uncomment line 25, 26; Before & After services executes for 'getBalance()' method, Around service will not execute because we put a pointcut in our example for this advice. INFO: balanceDeposite() method : start || (BeforeService) we are in getBalance() method !! INFO: balanceDeposite() method : end || (balance = 10000) || (AfterService) your account balance is : 10000