1. Introduction:
After returning advice gets invoked after a matched method finishes its execution and returns a value. You already know, the name for such a matched method is Joinpoint. You can declare an After returning advice using the @AfterReturning
annotation.
You already know about the 5 types of advice from the previous article as listed below.
- Before advice –
@Before
- After returning –
@AfterReturning
- After throwing –
@AfterThrowing
- After (finally) advice –
@After
- Around advice –
@Around
2. Execution of @AfterReturning Advice
The @AfterReturning
advice is executed after the Joinpoint method returns normally. We are only referring to method execution to be returned normally, the matched method can have any return type.
@Configuration
@Aspect
public class UppercaseAspect {
@AfterReturning("c.jbd.saop.ar.aspect.DaoExpressions.findMethod()")
public void uppercaseUsername(JoinPoint joinPoint, User result){
//advice body
}
}
Accessing the return value
You may need to access the actual value that was returned from the Joinpoint. You can use the @AfterReturning that binds the returning
value to get that access. The below example shows accessing the return value and Joinpoint.
@Configuration
@Aspect
public class UppercaseAspect {
@AfterReturning(
pointcut = "c.jbd.saop.ar.aspect.DaoExpressions.findMethod()",
returning = "result")
public void uppercaseUsername(JoinPoint joinPoint, User result){
//Advice body goes here
}
}
- The name used in the
returning
attribute must correspond to the name of a parameter in the advice method. - When a method execution returns, the return value is passed to the advice method.
3. Example of After returning advice – @AfterReturning
Problem statement – Let’s say we have a User management app. We need to make sure that the uppercased username is always returned from the userDao. Will use the @AfterReturning
to manipulate the actual return values from the UserRepository:find()
method.
Create a maven project and add the following dependencies in the pom.xml, if you are adding the dependencies manually.
<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 the aspect to DAO classes. So we create the sample code – UserRepository, User and the Java configuration to register this class as below. As we discussed before, the annotation @EnableAspectJAutoProxy
is used to enable the Spring-Aop.
package c.jbd.saop.ar.dao;
import c.jbd.saop.ar.pojo.User;
import org.springframework.stereotype.Repository;
//A very stupid demo repository
@Repository
public class UserRepository {
//find an User
public User find(String username) {
if (username == null) {
throw new RuntimeException("username is null", new NullPointerException());
}
return new User(username, "[email protected]");
}
//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.ar.pojo;
import java.util.StringJoiner;
//The User pojo
public class User {
private String username;
private String email;
public User(String username, String email){
this.email = email;
this.username = username;
}
public String getUsername() {
return username;
}
public User setUsername(String username) {
this.username = username;
return this;
}
public String getEmail() {
return email;
}
public User setEmail(String email) {
this.email = email;
return this;
}
@Override
public String toString() {
return new StringJoiner(", ", User.class.getSimpleName() + "[", "]")
.add("username='" + username + "'")
.add("email='" + email + "'")
.toString();
}
}
package c.jbd.saop.ar;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("c.jbd.saop.ar")
public class ApplicationConfig {
}
I will keep the Pointcut expressions and Advice method in separate classes. I just tried to show you the combining and sharing of the expressions. The entire expression could be simplified as "execution(* c.jbd.saop.ar.dao.UserRepository.find(..))"
.
package c.jbd.saop.ar.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class DaoExpressions {
//Match with only UserRepository
@Pointcut("within(c.jbd.saop.ar.dao.UserRepository)")
public void userDao() {}
//Match with any find() method
@Pointcut("execution(* find(..))")
public void findMethod() {}
@Pointcut("userDao() && findMethod()")
public void userDaoFind(){}
}
package c.jbd.saop.ar.aspect;
import c.jbd.saop.ar.pojo.User;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
@Configuration
@Aspect
public class UppercaseAspect {
@AfterReturning(
pointcut = "c.jbd.saop.ar.aspect.DaoExpressions.findMethod()",
returning = "result")
public void uppercaseUsername(JoinPoint joinPoint, User result){
System.out.println("After method - " +
joinPoint.getSignature().toShortString());
System.out.println("original result:" + result);
if (result.getUsername() != null){
result.setUsername(result.getUsername().toUpperCase());
}
System.out.println("final result: " + result);
}
}
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.ar;
import c.jbd.saop.ar.dao.UserRepository;
import c.jbd.saop.ar.pojo.User;
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 TestAfterReturning {
@Autowired
private UserRepository userRepository;
@Test
public void checkNull(){
Assertions.assertNotNull(userRepository);
}
@Test
public void testFindUser(){
String username = "JsTobigdata";
User user = userRepository.find(username);
Assertions.assertEquals(user.getUsername(), username.toUpperCase());
Assertions.assertNotEquals(user.getUsername(), username);
}
}
The tests should pass with the following output in the console.
Output:After method - UserRepository.find(..) original result:User[username='JsTobigdata', email='[email protected]'] final result: User[username='JSTOBIGDATA', email='[email protected]']
Conclusion:
I explained about After returning advice in Spring AOP using @AfterReturning
. You can find the complete code example in the GitHub repo. Next, we will learn about the @AfterThrowing advice.