Spring Boot Tutorial
Aspect-Oriented Programming (AOP) with Spring Boot
Some requirements cut across your whole application: log every service call, measure how long methods take, audit who changed what, check permissions, retry failed calls, translate exceptions. If you write that code inside every method, business logic drowns in repetition and a change to the logging format means editing hundreds of files. These are called cross-cutting concerns.
Aspect-Oriented Programming (AOP) lets you write a cross-cutting concern once, in an aspect, and declare where it applies. Spring itself uses AOP for @Transactional, @Cacheable, @Async, @PreAuthorize and @Retryable. In this lesson you will learn AOP terminology, set up Spring AOP in Spring Boot 4, write your first aspect, master pointcut expressions, and understand how Spring's proxy-based AOP works — including its limitations.
AOP Terminology
AOP has its own vocabulary. Once these terms click, the rest is straightforward:
- Aspect — a class that holds cross-cutting logic, annotated with
@Aspect(e.g.LoggingAspect). - Join point — a point during execution where an aspect can run. In Spring AOP this is always a method execution on a Spring bean.
- Advice — the code the aspect runs at a join point: before, after, after returning, after throwing, or around.
- Pointcut — an expression that selects join points, e.g. "all public methods in the service package".
- Target object — the bean being advised (your
OrderService). - Proxy — the object Spring creates around the target to run advice before/after delegating to it.
- Weaving — linking aspects with targets. Spring AOP weaves at runtime using proxies; full AspectJ can weave at compile or load time.
Setting Up AOP in Spring Boot 4
Add spring-boot-starter-aspectj (renamed from spring-boot-starter-aop in Spring Boot 4). It brings the AspectJ annotations and weaver library that Spring AOP uses to parse pointcut expressions, and Spring Boot enables @EnableAspectJAutoProxy automatically. Declare an aspect as a @Component with @Aspect, and Spring applies it to every matching bean at startup.
How Spring AOP Works: Proxies
When a bean matches a pointcut, Spring does not modify its class. It creates a proxy — by default a CGLIB subclass in Spring Boot — and injects the proxy wherever the bean is needed. Calls go through the proxy, which runs the advice and then calls the real method.
This design has important consequences. Only Spring beans can be advised (not objects you create with new). Only method executions are join points (not field access or constructors). Self-invocation bypasses the proxy: if placeOrder() calls this.validate(), advice on validate() does not run. Final classes and final or private methods cannot be advised by CGLIB proxies. If you need any of these, full AspectJ weaving is the alternative — but for most applications Spring AOP is exactly enough.
Pointcut Expressions
Pointcuts use the AspectJ expression language. The most useful designators are:
execution(* com.webnest.shop.service.*.*(..))— any method of any class in the service package. Pattern:execution(modifiers? returnType declaringType? methodName(params) throws?).execution(public * *..*Service.find*(..))— public methods starting with "find" in classes ending with "Service", in any package.within(com.webnest.shop..*)— every method of every type in a package and its sub-packages.@annotation(com.webnest.shop.aop.LogExecutionTime)— methods carrying a specific annotation (the cleanest, most explicit style).@within(org.springframework.stereotype.Service)— all methods of classes annotated with @Service.bean(*Controller)— beans whose name matches (Spring-specific).args(java.lang.Long, ..)— methods whose first argument is a Long; can also bind arguments to advice parameters.- Combine with
&&,||and!, and give reusable expressions a name with@Pointcut.
When to Use AOP (and When Not To)
AOP is ideal for concerns that are truly orthogonal to business logic and apply to many places: logging, metrics, auditing, security checks, retries, exception translation, multi-tenancy filters. Avoid hiding core business rules in aspects — a reader of placeOrder() should not need to discover that an aspect silently changes its result. Prefer annotation-based pointcuts (@annotation(...)) over broad package patterns so it is visible in the code which methods are affected.
Examples
Dependency (Spring Boot 4)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aspectj</artifactId> <!-- was spring-boot-starter-aop -->
</dependency>
// Gradle
implementation("org.springframework.boot:spring-boot-starter-aspectj")
(No @EnableAspectJAutoProxy needed — Spring Boot enables auto-proxying when AspectJ is on the classpath.)
Your first aspect: log every service method call
package com.webnest.shop.aop;
import org.aspectj.lang.JoinPoint;
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;
import java.util.Arrays;
@Aspect
@Component
public class ServiceLoggingAspect {
private static final Logger log = LoggerFactory.getLogger(ServiceLoggingAspect.class);
@Before("execution(* com.webnest.shop.service.*.*(..))")
public void logCall(JoinPoint joinPoint) {
log.info("Calling {}.{} with {}",
joinPoint.getSignature().getDeclaringType().getSimpleName(),
joinPoint.getSignature().getName(),
Arrays.toString(joinPoint.getArgs()));
}
}
// A normal service — it contains no logging code at all
@Service
public class CourseService {
public Course findBySlug(String slug) { ... }
public Course enroll(Long studentId, String slug) { ... }
}
INFO ServiceLoggingAspect : Calling CourseService.findBySlug with [spring-boot]
INFO ServiceLoggingAspect : Calling CourseService.enroll with [7, spring-boot]
Reusable named pointcuts combined with && and ||
@Aspect
@Component
public class Pointcuts {
@Pointcut("within(com.webnest.shop.service..*)")
public void inServiceLayer() {}
@Pointcut("within(com.webnest.shop.web..*)")
public void inWebLayer() {}
@Pointcut("execution(* *..*Repository.delete*(..))")
public void anyDelete() {}
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethod() {}
@Pointcut("inServiceLayer() && transactionalMethod()")
public void transactionalService() {}
}
@Aspect
@Component
public class DeleteAuditAspect {
// reference a pointcut declared in another class by its fully qualified name
@Before("com.webnest.shop.aop.Pointcuts.anyDelete()")
public void auditDelete(JoinPoint jp) {
System.out.println("DELETE requested: " + jp.getSignature().toShortString()
+ " args=" + Arrays.toString(jp.getArgs()));
}
}
DELETE requested: CourseRepository.deleteById(..) args=[12]
Seeing the proxy and the self-invocation limitation
@Service
public class ReportService {
public void generateAll() {
System.out.println("generateAll");
generateOne(1); // self-call: goes to 'this', NOT through the proxy
}
public void generateOne(int id) {
System.out.println("generateOne " + id);
}
}
@Component
class ProxyDemo implements CommandLineRunner {
private final ReportService reports;
ProxyDemo(ReportService reports) {
this.reports = reports;
}
@Override
public void run(String... args) {
System.out.println(reports.getClass().getName());
reports.generateAll(); // advised
reports.generateOne(2); // advised
}
}
com.webnest.shop.service.ReportService$$SpringCGLIB$$0
INFO Calling ReportService.generateAll with []
generateAll
generateOne 1 <- no log line: self-invocation bypassed the proxy
INFO Calling ReportService.generateOne with [2]
generateOne 2
Common Mistakes
- Adding spring-boot-starter-aop in a Spring Boot 4 project — the starter is now spring-boot-starter-aspectj.
- Forgetting @Component on the @Aspect class, so Spring never registers the aspect.
- Expecting advice to run on self-invocations, private methods, final methods or objects created with new.
- Writing overly broad pointcuts like execution(* *(..)) that advise every bean, including Spring's own infrastructure, slowing startup and causing odd errors.
- Hiding important business rules in aspects where readers of the business code cannot see them.
Key Points to Remember
- AOP modularises cross-cutting concerns (logging, metrics, auditing, security) into aspects.
- Aspect = advice (what) + pointcut (where); in Spring AOP join points are bean method executions.
- Spring Boot 4 uses spring-boot-starter-aspectj; aspects are @Aspect @Component classes.
- Spring AOP is proxy-based: only external calls to public, non-final bean methods are advised.
- Prefer annotation-based pointcuts (@annotation) for explicit, readable targeting.
Practice the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Use your local JDK or project IDE for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.