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:
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}
@Pointcut("within(com.jsbd.trading..*)")
private void inTrading() {}
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
- 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.
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() {}
}
@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.
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.
<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>
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.
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;
}
}
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.
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.
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
designatorupdateMethod()
– 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.
Finally, we will test the example to see its output. Add the test dependencies in the pom.xml
before running the test cases.
<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
.
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]");
}
}
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.