In this tutorial, you will learn to combine and reuse multiple Pointcut expressions in Spring AOP. We have only discussed a glimpse of it in the previous article. Combining means making use of two or more expressions separated by Logical operator – &&, || and !.

I have included high level sample as well as a complete example to give you good understanding on this topic.

1. Combining Pointcut Expressions

You can combine multiple pointcut expressions by using AND – &&, OR – || and NOT – !. You can also refer to pointcut expressions by name. The following example shows the same:

Java
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} 

@Pointcut("within(com.jsbd.trading..*)")
private void inTrading() {} 

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} 
Remember:

  • Only the method signature is needed to declare a pointcut expressions using the @Pointcut, no method body is allowed.
  • Method name becomes the pointcut expression name.
  • If you keep the expressions in a separate class, you will need the fully qualified name to access them in another pointcut expression.

2. Reusing (sharing) pointcut expressions

You can also keep the Pointcut expressions in a separate class (Pointcut_class) annotated with @Aspect and reuse them in your Aspect classes with the full qualified name (e.g. Pointcut_class.expression_name()). This way you can reuse the same expressions inside multiple aspect methods. We will see this with the help of a simple code shown below.

Java
package com.jbd.someapp.aspect;

@Aspect //Only aspect annotation
public class SystemArchitecture {

    @Pointcut("within(com.jbd.someapp.web..*)")
    public void inWebLayer() {}
    
    @Pointcut("within(com.jbd.someapp.service..*)")
    public void inServiceLayer() {}
}
Java
@Aspect
@Configuration
public class ExampleAspect {

  @Before("com.jbd.someapp.aspect.SystemArchitecture.inWebLayer()")
  public void logWebLayer(){
    System.out.print("Log in web layer");
  }
}

3. A complete working example

This is a complete elaborated step by step example to learn combining and reusing the Joinpoints. We have only explored the @Before advice till now, so we will use the @Pointcut and @Before in the below example.

Problem statement – The example below keeps track of all the DAO operations by reporting the analytics api. However, all the update() methods has to invoke special analytics api which is different than api called for other operations.

Step-1: Add the dependencies
Create a simple maven project and add the following dependencies in the pom.xml if you are not using Spring boot. It is advisable to generate a Spring project from start.spring.io.

Java
<dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    
    <!-- Step-1 Add the dependencies -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
     <version>1.9.5</version>
    </dependency>
<dependencies>
Step-2: Sample UserRepository
We will apply aspects to DAO classes. So we create a sample User Dao class and the Java configuration to register this class as below.

Java
package c.jbd.saop.cjp.dao;
import org.springframework.stereotype.Repository;

//A very stupid demo repository
@Repository
public class UserRepository {
  
  //Add a user
  public UserRepository add(String username){
    if(username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("New user added: " + username);
    return this;
  }
  
  //Update an user
  public UserRepository update(String username, String email) {
    System.out.println("Update email: " + email);
    return this;
  }
  
  //Delete an user
  public boolean delete(String username){
    if (username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("User deleted: " + username);
    return true;
  }
}
Java
package c.jbd.saop.cjp;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy //Enable AspectJ
@ComponentScan("c.jbd.saop.cjp")
public class ApplicationConfig {
}

Just to remind, @EnableAspectJAutoProxy is used to enable the AspectJ feature in the application.

Step-3: Create Pointcut expressions and Aspects

Instead of creating two separate classes, I will use a Single class AnalyticsAdvice. In a practical use case, you should try to keep the Expressions and Aspects in separate classes.

Java
package c.jbd.saop.cjp.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;

@Configuration
@Aspect
public class AnalyticsAdvice {
  
  //All Dao classes
  @Pointcut("within(c.jbd.saop.cjp.dao.*)")
  private void allDao() {
  }

  // All update() methods
  @Pointcut("execution(* update(..))")
  private void updateMethod() {
  }

  //Special analytics for Update methods
  @Before("allDao() && updateMethod()")
  public void specialAnalytics(JoinPoint joinPoint) {
    System.out.println("Call special analytics for: "
        + joinPoint.getSignature());
  }

  //general analytics for other methods
  @Before("allDao() && !updateMethod()")
  public void generalAnalytics(JoinPoint joinPoint) {
    System.out.println("Call general analytics for:"
        + joinPoint.getSignature());
  }
}

As you can see there are 2 pointcut expressions, as the name suggests:
allDao() – for matching with all the DAO classes using the within designator
updateMethod() – for matching with update() methods of any class with any number of parameters.

The aspect @Before("allDao() && updateMethod()") matches with all the update() methods inside dao package. This is what is needed to call the special analytics api.

Similarly, the aspect @Before("allDao() && !updateMethod()") matches with all the Dao class methods except update methods. This is used to invoke the general analytics api.

Step-4: Test the example

Finally, we will test the example to see its output. Add the test dependencies in the pom.xml before running the test cases.

XML
  <dependencies>
  ... 
   <!-- Test Dependencies-->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.2.RELEASE</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
...
<!-- Important for Jupiter-engine -->
<build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.0.0-M3</version>
      </plugin>
    </plugins>
  </build>
</project>

Always remember to upgrade the maven-surefire-plugin to 3.0.0-M3 or higher when using Junit-jupiter-*.

Once you have added the test dependencies, create the Test class inside src\test.

Java
package c.jbd.saop.cjp;
import c.jbd.saop.cjp.dao.UserRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

@SpringJUnitConfig(ApplicationConfig.class)
public class TestAnalytics {

  @Autowired
  UserRepository userRepository;

  @Test
  public void notNull(){
    Assertions.assertNotNull(userRepository);
  }

  @Test
  public void testAddUser(){
    userRepository.add("Alexa");
  }

  @Test
  public void testUpdateUser(){
    userRepository.update("Alexa", "[email protected]");
  }
}
Output:

Call general analytics for:UserRepository c.jbd.saop.cjp.dao.UserRepository.add(String)
New user added: Alexa

Call special analytics for: UserRepository c.jbd.saop.cjp.dao.UserRepository.update(String,String)
Update email: [email protected]

As you can see in the above example, special analytics API is invoked before the update method call, for all other DAO methods the general analytics in invoked.

Conclusion:

This article explains how to Combine and Reuse pointcut expressions in Spring AOP. The complete example is available on GitHub repo. Next, we will learn about the @AfterReturning advice.


By |Last Updated: April 2nd, 2024|Categories: Spring Framework|