Let's create a hands-on example where we set up an aspect for logging.
1. Logging: what and why?
Logging is the process of recording data about your application's execution. It helps you see what's happening "under the hood" and becomes a primary artifact for debugging and analysis in production environments.
For example, if you have a method that processes an order in an online store, it's useful to know:
- When that method was called.
- What data was passed as parameters.
- What happened inside the method: success or error.
With AOP we can inject logging without touching business logic. So your code stays clean and the functionality is added at the aspect level.
2. Project setup
Step 1: Add dependencies
Create a Spring Boot project (if you haven't already) and make sure spring-boot-starter-aop is in your pom.xml (or build.gradle):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Step 2: Enable AOP support
Spring Boot will automatically enable AOP if you added the dependency. No extra configuration is required. But if you're using Spring Framework without Boot, make sure you have the @EnableAspectJAutoProxy component in your configuration.
3. Creating an aspect for logging
Let's start with a simple aspect that logs method calls.
Step 1: Create the aspect class
Create a new class LoggingAspect in your project:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.demo.service.*.*(..))") // Specify where to apply the aspect
public void logBeforeMethodExecution() {
System.out.println("A method in the service layer is about to be called.");
}
}
Code breakdown:
@Aspect— indicates this class is an aspect.@Component— registers the aspect as a Spring Bean.@Before— defines that the specified action (logBeforeMethodExecution) should run before the method call."execution(* com.example.demo.service.*.*(..))"— this is a Pointcut expression. It selects all methods from thecom.example.demo.servicepackage.
Step 2: Build a service to test
Let's add a simple service to check our logging. Create OrderService in the service package:
package com.example.demo.service;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
public void processOrder(String orderId) {
System.out.println("Processing order with ID: " + orderId);
}
}
Step 3: Create a controller
To test the aspect, create a controller. Add a new class OrderController:
package com.example.demo.controller;
import com.example.demo.service.OrderService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/process-order")
public String processOrder(@RequestParam String orderId) {
orderService.processOrder(orderId);
return "Order processed!";
}
}
4. Let's test!
Run the app and open your browser. Enter a URL like:
http://localhost:8080/process-order?orderId=123
You should see this output in the console:
A method in the service layer is about to be called.
Processing order with ID: 123
Victory! You successfully added logging to a service method without changing its code.
5. Improving the aspect: logging arguments
Let's add logging of method parameters. Use the JoinPoint API. Update your aspect like this:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.demo.service.*.*(..))")
public void logBeforeMethodExecution(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] methodArguments = joinPoint.getArgs();
System.out.println("Method " + methodName + " is called with arguments: ");
for (Object arg : methodArguments) {
System.out.println(" " + arg);
}
}
}
Now when you call the method you'll see:
Method processOrder is called with arguments:
123
Processing order with ID: 123
This makes logging much more informative.
6. Adding @Around to measure execution time
Next step: measure how long methods take. Update the aspect to use @Around:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class PerformanceLoggingAspect {
@Around("execution(* com.example.demo.service.*.*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed(); // Execute the method
long endTime = System.currentTimeMillis();
System.out.println("Execution time of " + joinPoint.getSignature().getName() +
": " + (endTime - startTime) + "ms");
return result;
}
}
Now when you invoke the method you'll also get info about its execution time:
Processing order with ID: 123
Execution time of processOrder: 5ms
7. Feedback: common mistakes
One of the most common mistakes when using AOP is incorrectly defining a Pointcut. For example, if you define a too "broad" Pointcut, your aspect might start firing on unexpected methods, like toString(). To avoid that, try to be explicit about packages and classes.
8. What's next?
Adding logging levels, dynamically controlling aspects via config files, creating aspects for exception handling... Welcome to the world of endless possibilities with Spring AOP!
Now you've got a tool to make your life easier. Use it wisely and carefully, like a magic wand. 😉
GO TO FULL VERSION