Spring AOP Pointcut Annotation Example

0

A pointcut expression determine join points of interest, we can directly embed it in an advice annotation and thus control when an advice executes. If the same pointcut expression is used in multiple advices, we can declare the pointcut as a simple method with the @Pointcut annotation and a pointcut expression that determines the method executions we are interested in. The @Pointcut annotation defines method name and parameters. Other advices can refer to this pointcut by the method name instead of defining the pointcut expression again.
In this article we will see some examples of @Pointcut annotation.

Pointcut Declaration Example

The pointcut declaration has two parts: a signature comprising a name and any parameters, and a pointcut expression. args() limits matching to join points where the arguments are instances of the given types.

    @Pointcut(value = "args(a)", argNames = "a")
    public void auditFields(Object a) {
    }

Another example:

    @Pointcut("execution(* *.*SecuredInfo())")
    public void securedInfo() {
    }

Here we have used execution for matching method execution join points.
As you can see from above examples, a pointcut signature is provided by a regular method definition, and the pointcut expression is indicated using the @Pointcut annotation.
auditFields(Object a) and securedInfo() are examples of the method serving as the pointcut signature. These methods must have a void return type.
Other advices can refer to this pointcut by the method name.

    @Around(value = "auditFields(value)", argNames = "value")
    public void doAudit(ProceedingJoinPoint pjp, Object value)
            throws Throwable {
    ....
    }

Pointcut securedInfo() is referred to in the below @Before advice.

    @Before("securedInfo()")
    public void dontAllowAccessToSecuredInfo() {
    ...
    }

Reusing Pointcut Definitions

We now know how to define a pointcut declaration. Let’s see a complete example of it from declaration to reusing the definition.
To demonstrate the example, we will use Employee bean. It has couple of fields age and name. Whenever we change one these attributes, we want to update the changed attribute.

Employee:

package com.javarticles;

import java.util.Date;

public class Employee implements AuditAware {
    private String name;
    private int age;
    private Date changed;
    
    public void setAge(int a) {
        System.out.println("setAge(" + a + ") called");
        this.age = a;
    }

    public void setName(String name) {
        System.out.println("setName(" + name + ") called");
        this.name = name;
    }

    public String getName() {
        return name;
    }       

    public int getAge() {
        return age;
    }

    public void setChanged(Date changed) {
        this.changed = changed;
    }

    public Date getChanged() {
        return changed;
    }
    
    public Object someSecuredInfo() {
        return new Object();
    }        
}

Employee bean implements AuditAware which contains the method to update the changed.

AuditAware:

package com.javarticles;

import java.util.Date;

public interface AuditAware {
    void setChanged(Date changed);
}

We will now declare @Pointcut and then use it to do auditing.
We will define the pointcut first. Any method that has an argument is selected for joinpoint.

    @Pointcut(value = "args(a)", argNames = "a")
    public void auditFields(Object a) {
    }

We will use it in @Around advice to do auditing. We will audit only if the method is one of the setters.

    @Around(value = "auditFields(value)", argNames = "value")
    public void doAudit(ProceedingJoinPoint pjp, Object value) throws Throwable {
        if (pjp.getSignature().getName().startsWith("set")) {
        ....
        }
     }

If you are new to Around advice see an example here.
We have to explicitly call the ProceedingJoinPoint.proceed() so that the actual method gets called.

 
pjp.proceed(new Object[] { value });

Next, we update the changed property.

Object target = pjp.getTarget();
if (target instanceof AuditAware) {
    AuditAware audit = (AuditAware) target;
    audit.setChanged(new Date());
}

AuditAopBean:

package com.javarticles;

import java.util.Date;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AuditAopBean {

    @Pointcut(value = "args(a)", argNames = "a")
    public void auditFields(Object a) {
    }

    @Around(value = "auditFields(value)", argNames = "value")
    public void doAudit(ProceedingJoinPoint pjp, Object value) throws Throwable {
        if (pjp.getSignature().getName().startsWith("set")) {
            pjp.proceed(new Object[] { value });
            Object target = pjp.getTarget();
            if (target instanceof AuditAware) {
                AuditAware audit = (AuditAware) target;
                audit.setChanged(new Date());
            }
        }
    }
}

Spring AOP only supports method execution join points for Spring beans, so you need make sure you define the beans in spring container.
To force CGLIB proxying when using the @AspectJ autoproxy support, set the proxy-target-class to true.

<aop:aspectj-autoproxy proxy-target-class="true"/>

If you don’t do it, below code will throw a java.lang.ClassCastException.

Employee employee = (Employee) context.getBean("emp");
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy7 cannot be cast to com.javarticles.Employee
	at com.javarticles.SpringAopPointcutExample.main(SpringAopPointcutExample.java:10)

applicationContext.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:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

	<bean id="emp" class="com.javarticles.Employee"/>	
        <aop:aspectj-autoproxy proxy-target-class="true"/>
	<bean id="auditBean" class="com.javarticles.AuditAopBean"/>
	
</beans>

To test this, we change employee’s age first and then verify the changed attribute. Next, sleep 1 sec and then change employee. Verify the changed again, it should have a different timestamp.

SpringAopPointcutExample:

package com.javarticles;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringAopPointcutExample {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "applicationContext.xml");
        try {
            Employee employee = (Employee) context.getBean("emp");
            employee.setAge(32);
            System.out.println("Employee age changed at " + employee.getChanged());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("Interrupted exception: " + e);
            }
            employee.setName("Joe"); 
            System.out.println("Employee name " + employee.getName());
            System.out.println("Employee name changed at " + employee.getChanged());
        } finally {
            context.close();
        }
    }
}

Output:

setAge(32) called
Employee age changed at Mon Aug 31 18:40:25 IST 2015
setName(Joe) called
Employee name Joe
Employee name changed at Mon Aug 31 18:40:26 IST 2015

Prevent Accessing a Method

In this example, we will use a pointcut expression using execution as the pointcode designator. It will match any method that ends with SecuredInfo().
In a before advice, we will throw exception if the secured info is tried to be accessed.

AuditAopBean:

package com.javarticles;

import java.util.Date;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AuditAopBean {

    @Pointcut(value = "args(a)", argNames = "a")
    public void auditFields(Object a) {
    }

    @Around(value = "auditFields(value)", argNames = "value")
    public void doAudit(ProceedingJoinPoint pjp, Object value) throws Throwable {
        if (pjp.getSignature().getName().startsWith("set")) {
            pjp.proceed(new Object[] { value });
            Object target = pjp.getTarget();
            if (target instanceof AuditAware) {
                AuditAware audit = (AuditAware) target;
                audit.setChanged(new Date());
            }
        }
    }

    @Pointcut("execution(* *.*SecuredInfo())")
    public void securedInfo() {
    }

    @Before("securedInfo()")
    public void dontAllowAccessToSecuredInfo() {
        System.out.println("dontAllowAccessToSecuredInfo called");
        throw new IllegalStateException();
    }
}

If one calls employee.someSecuredInfo() an IllegalStateException will be thrown. One can add some more logic around it, in the before advice, so that IllegalStateException is thrown conditionally.

SpringAopPointcutExample:

package com.javarticles;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringAopPointcutExample {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "applicationContext.xml");
        try {
            Employee employee = (Employee) context.getBean("emp");
            employee.setAge(32);
            System.out.println("Employee age changed at " + employee.getChanged());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("Interrupted exception: " + e);
            }
            employee.setName("Joe"); 
            System.out.println("Employee name " + employee.getName());
            System.out.println("Employee name changed at " + employee.getChanged());
            
            try {
                employee.someSecuredInfo();
                System.out.println("shouldn't be able to access secured info");
            } catch (IllegalStateException ise) {
                System.out.println("tried to access secured info");
            }            
        } finally {
            context.close();
        }
    }
}

Output:

setAge(32) called
Employee age changed at Mon Aug 31 22:05:22 IST 2015
setName(Joe) called
stringReturnValue: Joe
Employee name Joe
Employee name changed at Mon Aug 31 22:05:23 IST 2015
dontAllowAccessToSecuredInfo called
tried to access secured info

Track Method Calls

We can also track method calls using the pointcut expression designator. In this example, we track if one of the employee’s getter is called or PersistenceApi.save() is called.

PersistenceApi:

package com.javarticles;

public interface PersistenceApi<T> {
    void save(T bean);
}

PersistenceHelper:

package com.javarticles;

public class PersistenceHelper implements PersistenceApi{
    public void save(T bean) {
        System.out.println("save " + bean);
    }
}

AuditAopBean:

package com.javarticles;

import java.util.Date;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AuditAopBean {

    @Pointcut(value = "args(a)", argNames = "a")
    public void auditFields(Object a) {
    }

    @Around(value = "auditFields(value)", argNames = "value")
    public void doAudit(ProceedingJoinPoint pjp, Object value) throws Throwable {
        if (pjp.getSignature().getName().startsWith("set")) {
            pjp.proceed(new Object[] { value });
            Object target = pjp.getTarget();
            if (target instanceof AuditAware) {
                AuditAware audit = (AuditAware) target;
                audit.setChanged(new Date());
            }
        }
    }

    @Pointcut("execution(* *.*SecuredInfo())")
    public void securedInfo() {
    }

    @Before("securedInfo()")
    public void dontAllowAccessToSecuredInfo() {
        System.out.println("dontAllowAccessToSecuredInfo called");
        throw new IllegalStateException();
    }

    @Pointcut("execution(!void get*())")
    public void propertAccessOccurrence() {
    }

    @AfterReturning("propertAccessOccurrence()")
    public void incrementPropertAccess() {
        propertyAccessCount++;
    }

    @Pointcut("execution(* com.javarticles.PersistenceApi.save(..))")
    public void saveAnInterfaceMethodCall() {
    }

    @Before("saveAnInterfaceMethodCall()")
    public void incrementSave() {
        System.out.println("increment save");
        saveCount++;
    }

    public int getPropertyAccessCount() {
        return propertyAccessCount;
    }

    public int getSaveCount() {
        return saveCount;
    }

    private int propertyAccessCount;
    private int saveCount;
}

SpringAopPointcutExample:

package com.javarticles;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringAopPointcutExample {
    public static void main(String[] args) throws NoSuchMethodException, SecurityException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "applicationContext.xml");
        try {
            Employee employee = (Employee) context.getBean("emp");
            employee.setAge(32);
            System.out.println("Employee age changed at " + employee.getChanged());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("Interrupted exception: " + e);
            }
            employee.setName("Joe"); 
            System.out.println("Employee name " + employee.getName());
            System.out.println("Employee name changed at " + employee.getChanged());
            
            try {
                employee.someSecuredInfo();
                System.out.println("shouldn't be able to access secured info");
            } catch (IllegalStateException ise) {
                System.out.println("tried to access secured info");
            }
            
            AuditAopBean auditAopBean = (AuditAopBean) context.getBean("auditBean");
            System.out.println("property access count: " + auditAopBean.getPropertyAccessCount());   
            
            PersistenceHelper persistenceHelper = (PersistenceHelper) context.getBean("persistenceApi");
            persistenceHelper.save(employee);
            System.out.println("save count: " + auditAopBean.getSaveCount());
        } finally {
            context.close();
        }
    }
}

Output:

setAge(32) called
Employee age changed at Tue Sep 01 15:45:36 IST 2015
setName(Joe) called
Employee name Joe
Employee name changed at Tue Sep 01 15:45:37 IST 2015
dontAllowAccessToSecuredInfo called
tried to access secured info
property access count: 3
save count: 0

Track any method that returns some value

We want to track any method from Employee bean that returns some value.
Your pointcut declaration would be:

    @Pointcut("execution(* com.javarticles.Employee.*(..))")
    public void anyStringReturningMethod() {
    }

anyStringReturningMethod() is the pointcut method name. We will use it in an after returning advice. The return value cane be accessed as an argument using the attribute returning.

    @AfterReturning(pointcut = "anyStringReturningMethod()", returning = "returnValue")
    public void stringReturnValue(String returnValue) {
        if (returnValue != null) {
            System.out.println("stringReturnValue: " + returnValue);
        }
    }

SpringAopPointcutExample:

package com.javarticles;

import java.util.Date;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AuditAopBean {

    @Pointcut(value = "args(a)", argNames = "a")
    public void auditFields(Object a) {
    }

    @Around(value = "auditFields(value)", argNames = "value")
    public void doAudit(ProceedingJoinPoint pjp, Object value) throws Throwable {
        if (pjp.getSignature().getName().startsWith("set")) {
            pjp.proceed(new Object[] { value });
            Object target = pjp.getTarget();
            if (target instanceof AuditAware) {
                AuditAware audit = (AuditAware) target;
                audit.setChanged(new Date());
            }
        }
    }

    @Pointcut("execution(* *.*SecuredInfo())")
    public void securedInfo() {
    }

    @Before("securedInfo()")
    public void dontAllowAccessToSecuredInfo() {
        System.out.println("dontAllowAccessToSecuredInfo called");
        throw new IllegalStateException();
    }

    @Pointcut("execution(!void get*())")
    public void propertAccessOccurrence() {
    }

    @AfterReturning("propertAccessOccurrence()")
    public void incrementPropertAccess() {
        propertyAccessCount++;
    }

    @Pointcut("execution(* com.javarticles.PersistenceApi.save(..))")
    public void saveAnInterfaceMethodCall() {
    }

    @Before("saveAnInterfaceMethodCall()")
    public void incrementSave() {
        System.out.println("increment save");
        saveCount++;
    }

    @Pointcut("execution(* com.javarticles.Employee.*(..))")
    public void anyStringReturningMethod() {
    }

    @AfterReturning(pointcut = "anyStringReturningMethod()", returning = "returnValue")
    public void stringReturnValue(String returnValue) {
        if (returnValue != null) {
            System.out.println("stringReturnValue: " + returnValue);
        }
    }

    public int getPropertyAccessCount() {
        return propertyAccessCount;
    }

    public int getSaveCount() {
        return saveCount;
    }

    private int propertyAccessCount;
    private int saveCount;
}

Output:

setAge(32) called
Employee age changed at Tue Sep 01 15:45:36 IST 2015
setName(Joe) called
stringReturnValue: Joe
Employee name Joe
Employee name changed at Tue Sep 01 15:45:37 IST 2015
dontAllowAccessToSecuredInfo called
tried to access secured info
property access count: 3
save count: 0

Download the source code

This was an example about Spring AOP Pointcut Annotation.

You can download the source code here: springAopPointcutExample.zip
Share.

Comments are closed.