What Is
This annotation is a component of Aspect-Oriented Programming. It will define a class as an aspect in Spring AOP. When using aspects, you can define some behaviors that will happen when certain criteria match the defined configurations of your aspect, such as when a method annotated with some custom annotation is executed or a method from a path is called.
Why Is Important
Separate concerns: for example, you can create a logging aspect that will isolate the logging from your business logic
Reusability: you can reuse the functionalities that you created in your aspect in many places in your application, and keep a unique central point that makes it easier to modify and propagate to all points of your application
How To Use It
Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Step 1
You need to enable AOP in a Spring configuration. You can achieve this by using the annotation @EnableAspectJAutoProxy
:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
Step 2
Create your aspect handler class
Create a new class and add the @Aspect
annotation to the class level.
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
// Add behaviors here
}
Step 3
Define what behavior is going to happen when the criteria are matched.
Example
You want to log every time a method is invoked. So you can create a pointcut before the method is called. Using the @Before
annotation.
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
@Before("execution(* org.spring.mastery.service.TestService.testBefore(..))")
public void logBeforeMethod() {
logger.info("A method is about to be executed!");
}
}
The example above will execute before the following method is called:
package org.spring.mastery.service;
import org.springframework.stereotype.Service;
@Service
public class TestService {
public String testBefore(String name) {
return "Hello, " + name;
}
}
Or if you want it to, you can add a behavior after the method is executed, using @After
import org.aspectj.lang.annotation.After;
@After("execution(* org.spring.mastery.service.TestService.testAfter(..))")
public void logAfterMethod() {
logger.info("A method has just finished (success or error).");
}
This will be executed after the following method is executed:
package org.spring.mastery.service;
import org.springframework.stereotype.Service;
@Service
public class TestService {
public String testAfter(String name) {
return "Hello, " + name;
}
}
You can also add an aspect to detect the return of a method, and also catch the result using @AfterReturning
import org.aspectj.lang.annotation.AfterReturning;
@AfterReturning(pointcut = "execution(* org.spring.mastery.service.TestService.testAfterReturning(..))", returning = "result")
public void logAfterReturning(Object result) {
logger.info("Method returned value: {}", result);
}
The event above will be triggered when the following method returns something:
package org.spring.mastery.service;
import org.springframework.stereotype.Service;
@Service
public class TestService {
public String testAfterReturning(String name) {
return "Hello, " + name;
}
}
You can also detect and do some custom behavior if an exception has been thrown, using @AfterThrowing
import org.aspectj.lang.annotation.AfterThrowing;
@AfterThrowing(pointcut = "execution(* org.spring.mastery.service.TestService.testAfterThrowing(..))", throwing = "ex")
public void logAfterThrowing(Exception ex) {
logger.error("Method threw exception: {}", ex.getMessage());
}
If the method below throws an exception, the aspect method above will be executed:
package org.spring.mastery.service;
import org.springframework.stereotype.Service;
@Service
public class TestService {
public String testAfterThrowing() {
throw new RuntimeException("This is a test exception");
}
}
You can also do something before and after the method execution by using the @Around
annotation.
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
@Around("execution(* org.spring.mastery.service.TestService.testAround(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
logger.info("[Around] Before: {}", joinPoint.getSignature());
Object result = joinPoint.proceed();
logger.info("[Around] After: {}", joinPoint.getSignature());
return result;
}
The method below will trigger the aspect above, before and after the execution:
package org.spring.mastery.service;
import org.springframework.stereotype.Service;
@Service
public class TestService {
public String testAround(String name) {
return "Hello, " + name;
}
}
But if you don’t want to use the classpath to configure your aspects, you can create a custom annotation and use it in your code, which will make your aspect event reusable as well. You can add a custom annotation like this:
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Auditable {
}
And then configure your aspect to read your custom annotation:
import org.aspectj.lang.annotation.Before;
@Before("@annotation(com.example.aop.Auditable)")
public void audit() {
logger.info("Auditing action…");
}
Now you can reuse your custom annotation in any method you want, like this:
package org.spring.mastery.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spring.mastery.annotation.Auditable;
import org.springframework.stereotype.Service;
@Service
public class TestService {
private static final Logger logger = LoggerFactory.getLogger(TestService.class);
@Auditable
public void auditableMethod() {
logger.info("Executing auditable method");
}
}
Conclusion
Aspects are a powerful tool to use with Spring; they help you add behaviors to your code without interfering with your business logic. But be careful using this. One change in the package’s name can break your stuff, and make sure to maintain a good structure that will be easy to find where the behavior comes from.