Tuesday, October 21, 2014

Spring AOP pointcut explained with example

PointCuts:-

In the my previous example where i have defined the aop related example with all the method implementations. All of them if applied to a class called customerservice which has age, salary and name then  are applied then it is applied to all the three methods(getname, getage and getsalary) in the customerservice class .

If we want to have an interceptor only on a NAME attribute then we can define a pointcut for the getName() method either by name of the method or Regular Expression.

So above mentioned scenario is the general definition for pointcut where you want to define a aspect for each method then you can define a point cut  to just intercept that method call.
  • Pointcut: a predicate that matches join points. Advice is associated with a pointcut expression and runs at any join point matched by the pointcut (for example, the execution of a method with a certain name). The concept of join points as matched by pointcut expressions is central to AOP, and Spring uses the AspectJ pointcut expression language by default. (Source:- spring documentation)
There are two types of Pointcuts that can be defined.
  • Name
  • Regular expression.

Configuration of pointcut by Name :- 


Above diagram is the way the point cut is the configured in the application context.xml or spring configuration file.
CodeSnippet:-

                              ProxyBean from above digram
<beans:bean id="customerServiceProxy" 
                 class="org.springframework.aop.framework.ProxyFactoryBean">
<beans:property name="target" ref="customerService" />  Target
<beans:property name="interceptorNames">
<beans:list>
<beans:value>customerAdvisor</beans:value> List of interceptors
</beans:list>
</beans:property>
</beans:bean>
Interceptor definition
<beans:bean name="hijackerAroundMethod" class="com.test.harsha.HijackerAroundMethod"/>

<beans:bean id="customerAdvisor"
class="org.springframework.aop.support.DefaultPointcutAdvisor">
<beans:property name="pointcut" ref="customerPointcut" /> pointcut reference
<beans:property name="advice" ref="hijackerAroundMethod" /> Interceptor reference
</beans:bean>

<beans:bean id="customerPointcut"    pointcut definition
        class="org.springframework.aop.support.NameMatchMethodPointcut">
<beans:property name="mappedName" value="getName" />
</beans:bean>

Now map the above code snippet with the diagram and you will be able to understand the flow.

Configuration of pointcut by Regular expression:- 


     ProxyBean from above digram

<beans:bean id="customerServiceProxy" 
                 class="org.springframework.aop.framework.ProxyFactoryBean">
<beans:property name="target" ref="customerService" />  Target
<beans:property name="interceptorNames">
<beans:list>
<beans:value>customerAdvisor</beans:value> List of interceptors
</beans:list>
</beans:property>
</beans:bean>
Interceptor definition
<beans:bean name="hijackerAroundMethod" class="com.test.harsha.HijackerAroundMethod"/>
Regular expression definition
<beans:bean id="customerAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<beans:property name="patterns">
<beans:list>
<beans:value>.*getName</beans:value>
</beans:list>
</beans:property>

<beans:property name="advice" ref="hijackerAroundMethod" />

</beans:bean>


Now let us see the sample example for implementing the above pattern.

Step1:-  Create a sample maven project.


Step2:- Maven dependencies for the poject.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.springframework.samples</groupId>
  <artifactId>SpringAOPPointCutExample</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <properties>

<!-- Generic properties -->
<java.version>1.6</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<!-- Spring -->
<spring-framework.version>3.2.3.RELEASE</spring-framework.version>

<!-- Hibernate / JPA -->
<hibernate.version>4.2.1.Final</hibernate.version>

<!-- Logging -->
<logback.version>1.0.13</logback.version>
<slf4j.version>1.7.5</slf4j.version>

<!-- Test -->
<junit.version>4.11</junit.version>

</properties>

<dependencies>
<!-- Spring and Transactions -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-framework.version}</version>
</dependency>

<!-- Logging with SLF4J & LogBack -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>runtime</scope>
</dependency>

<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>


<!-- Test Artifacts -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring-framework.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>

</dependencies>

</project>

Step3:- Define the servlet context xml . Configure your pointcut. Below mentioned shows both reg exp pointcut and pointcut by name.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
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.0.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<beans:bean name="customerService" class="com.test.harsha.CustomerService">
<beans:property name="name" value="harsha"/>
<beans:property name="age" value="28"/>
<beans:property name="state" value="MN"/>
</beans:bean>

<beans:bean name="hijackerAroundMethod" class="com.test.harsha.HijackerAroundMethod"/>

<beans:bean id="customerServiceProxy" 
                 class="org.springframework.aop.framework.ProxyFactoryBean">

<beans:property name="target" ref="customerService" />

<beans:property name="interceptorNames">
<beans:list>
<beans:value>customerAdvisor</beans:value>
</beans:list>
</beans:property>

</beans:bean>

<!-- <beans:bean id="customerAdvisor" -->
<!-- class="org.springframework.aop.support.DefaultPointcutAdvisor"> -->
<!-- <beans:property name="pointcut" ref="customerPointcut" /> -->
<!-- <beans:property name="advice" ref="hijackerAroundMethod" /> -->
<!-- </beans:bean> -->

<beans:bean id="customerAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<beans:property name="patterns">
<beans:list>
<beans:value>.*getName</beans:value>
</beans:list>
</beans:property>

<beans:property name="advice" ref="hijackerAroundMethod" />
</beans:bean>

<beans:bean id="customerPointcut"
        class="org.springframework.aop.support.NameMatchMethodPointcut">
<beans:property name="mappedName" value="getName" />
</beans:bean>


</beans:beans>


Step4:- Configure the hijacker around method (Actual definition of what the pointcut should do).

package com.test.harsha;

import java.util.Arrays;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class HijackerAroundMethod implements MethodInterceptor  {

@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("Method name : "
+ methodInvocation.getMethod().getName());
System.out.println("Method arguments : "
+ Arrays.toString(methodInvocation.getArguments()));
 
// same with MethodBeforeAdvice
System.out.println("HijackAroundMethod : Before method hijacked!");
 
try {
// proceed to original method call
Object result = methodInvocation.proceed();
 
// same with AfterReturningAdvice
System.out.println("HijackAroundMethod : Before after hijacked!");
 
return result;
 
} catch (IllegalArgumentException e) {
// same with ThrowsAdvice
System.out.println("HijackAroundMethod : Throw exception hijacked!");
throw e;
}
}


}

Now define the actual implementation on which the pointcut has to be performed.
CustomerService.
package com.test.harsha;

public class CustomerService {

private String name;

private String age;

private String state;

public String getName() {
System.out.println("in the getter method of name----"+name  );
return name;
}

public void setName(String name) {
System.out.println("inside setter of name");
this.name = name;
}

public String getAge() {
System.out.println("in the getter method of age----"+age  );
return age;
}

public void setAge(String age) {
System.out.println("inside setter of age");
this.age = age;
}

public String getState() {
System.out.println("in the getter method of state----"+state  );
return state;
}

public void setState(String state) {
System.out.println("inside setter of state");
this.state = state;
}

public void printThrowException() {
throw new IllegalArgumentException();
}


}

Step5:- Write the standalone class which calls the actual service (customer service) and debug to see how the pointcut works.

package com.test.harsha;

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

public class App {

/**
* @param args
*/
public static void main(String[] args) {
ApplicationContext appContext = new ClassPathXmlApplicationContext(
new String[] { "servlet-context.xml" });

CustomerService cust = (CustomerService) appContext.getBean("customerServiceProxy");

System.out.println("*************************");
cust.getAge();
System.out.println("*************************");
cust.getName();
System.out.println("*************************");
cust.getState();
System.out.println("*************************");
try {
cust.printThrowException();
} catch (Exception e) {

}

}


}

OUTPUT:-  you will see the first sysout
System.out.println("*************************");

Then when the call getName() is made then it goes to the proxy which is created by AOP and the call gets intercepted by the POINTCUT.

System.out.println("Method name : "
+ methodInvocation.getMethod().getName());
System.out.println("Method arguments : "
+ Arrays.toString(methodInvocation.getArguments()));
 
// same with MethodBeforeAdvice
System.out.println("HijackAroundMethod : Before method hijacked!");

Then the method call happens and prints the sysout if any present in the getName method . 
System.out.println("in the getter method of age----"+age  );

After that System.out.println("HijackAroundMethod : Before after hijacked!");  This is printed.
 
If any exception occurs then you will see System.out.println("HijackAroundMethod : Throw exception hijacked!");

The only difference between AOP where you define before , after , around and throws and this Pointcut is :-
In the first case you are applying your interceptor to all the calls in the service. Example is in the customer service if you call any method getAge getName and so on the interceptor will be activated and the aop is applied to all the calls. Drawback is that even if you donot want some of the methods should not be applied with the interceptors this allows the aop to apply it to all the methods.

In the later case where the pointcut is defined we have the flexibility to intercept only a specific method calls like getName and you will only activate the interceptor for this method. This is more modularize  the AOP functionality.

No comments:

Post a Comment