Spring Boot AOP (Aspect-Oriented Programming)
Some code is needed in many places but has nothing to do with the business logic around it: logging, timing, security checks, auditing. Copying it into every method makes the code long, repetitive and easy to get wrong. Aspect-Oriented Programming (AOP) lets you write that code once and apply it automatically wherever it is needed.
This tutorial explains the core AOP concepts, how Spring applies aspects through proxies, every type of advice, pointcut expressions, custom annotations, real-world examples, testing and the limitations to watch out for.
1. What Is AOP?
Imagine you want to log the start, end and duration of every service method. Without AOP, each method carries the same extra lines:
public Order placeOrder(OrderRequest request) {
log.info("placeOrder started");
long start = System.currentTimeMillis();
try {
// the actual business logic: 3 lines
Order order = Order.from(request);
inventory.reserve(order);
return orderRepository.save(order);
} finally {
log.info("placeOrder took {} ms", System.currentTimeMillis() - start);
}
}
public void cancelOrder(Long id) {
log.info("cancelOrder started");
long start = System.currentTimeMillis();
// ... the same lines again, in every method
}
The logging and timing are cross-cutting concerns: they cut across many classes, but do not belong to any of them. AOP moves them into one separate class, called an aspect, and Spring applies it automatically. The service then contains only business logic:
public Order placeOrder(OrderRequest request) {
Order order = Order.from(request);
inventory.reserve(order);
return orderRepository.save(order);
}
2. Why Use AOP? Common Use Cases
- No duplicated code: the concern is written once.
- Cleaner business logic: methods show only what they really do.
- Consistency: every matching method gets exactly the same behavior.
- Easy to change: update or remove the concern in one place.
| Use case | What the aspect does |
|---|---|
| Logging and tracing | Log method calls, arguments and results |
| Performance monitoring | Measure and record how long methods take |
| Auditing | Record who did what, and when |
| Security | Check permissions before a method runs |
| Retry | Repeat a call that failed with a temporary error |
| Error translation | Convert technical exceptions into business ones |
You already use AOP every day: Spring itself implements @Transactional, @Cacheable, @Async and method security with exactly this technique.
3. Core AOP Concepts
| Term | Meaning | In the logging example |
|---|---|---|
| Aspect | A class that holds cross-cutting logic, marked @Aspect | LoggingAspect |
| Join point | A point in the program where an aspect can run; in Spring AOP, always a method execution | The call to placeOrder() |
| Pointcut | An expression that selects which join points to apply the aspect to | “Every public method in the service package” |
| Advice | The code that runs, and when: before, after or around the method | Log the start time, then the duration |
| Target | The original object whose methods are advised | The real OrderService |
| Proxy | The wrapper Spring creates around the target | What other beans actually call |
| Weaving | Combining aspects with the target code | Spring does it at runtime, with proxies |
A simple way to remember it: the pointcut says where, the advice says what and when, and the aspect is the class that holds both.
4. How Spring AOP Works
Spring AOP works with proxies. When a bean matches an aspect’s pointcut, Spring does not hand out the bean itself. It creates a proxy object around it and injects that instead:
OrderController ──▶ Proxy of OrderService ──▶ real OrderService.placeOrder()
│ before advice runs │
│ │ business logic runs
│ after advice runs ◀──────┘
| Proxy type | Used when | Notes |
|---|---|---|
| CGLIB (subclass) | Default in Spring Boot | Creates a subclass of your class at runtime; cannot advise final classes or methods |
| JDK dynamic proxy | When configured with spring.aop.proxy-target-class=false and the bean implements an interface | Only the interface methods are advised |
Because everything goes through the proxy, an aspect only runs when a method is called from another bean. This is the key to understanding most AOP surprises, covered in section 14.
5. Enabling AOP in Spring Boot
Add the AOP starter. It brings AspectJ’s annotations and switches Spring’s AOP support on automatically:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
In Spring Boot 4, this starter is named spring-boot-starter-aspectj. You do not need @EnableAspectJAutoProxy; Spring Boot’s auto-configuration enables it for you.
6. Your First Aspect
An aspect is a normal Spring bean with @Aspect. This one logs every public method call in the service package, with its arguments:
package com.javabytechie.shop.aop;
import java.util.Arrays;
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;
@Aspect
@Component
public class LoggingAspect {
private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);
@Before("execution(public * com.javabytechie.shop.service..*(..))")
public void logMethodCall(JoinPoint joinPoint) {
log.info("Calling {} with arguments {}",
joinPoint.getSignature().toShortString(),
Arrays.toString(joinPoint.getArgs()));
}
}
Calling OrderService.placeOrder(..) with arguments [OrderRequest[customerId=7, items=[BOOK-1]]]
Both annotations are needed: @Aspect marks the class as an aspect, and @Component makes it a Spring bean. Without @Component, Spring never finds the aspect. The JoinPoint parameter gives access to details of the current call, such as the method signature and the arguments.
7. The Five Types of Advice
| Advice | Runs | Can stop or change the call? |
|---|---|---|
@Before | Before the method | Only by throwing an exception |
@AfterReturning | After the method returns normally | Can read the return value |
@AfterThrowing | After the method throws an exception | Can read the exception |
@After | After the method, whether it returned or threw (like finally) | No |
@Around | Before and after; it decides whether the method runs at all | Yes: arguments, result and exceptions |
This aspect uses all five on the same methods, so you can see exactly when each one runs:
@Aspect
@Component
public class OrderEventsAspect {
private static final Logger log = LoggerFactory.getLogger(OrderEventsAspect.class);
private static final String ORDER_METHODS = "execution(* com.javabytechie.shop.service.OrderService.*(..))";
@Before(ORDER_METHODS)
public void before(JoinPoint jp) {
log.info("1. Before {}", jp.getSignature().getName());
}
@AfterReturning(pointcut = ORDER_METHODS, returning = "result")
public void afterReturning(JoinPoint jp, Object result) {
log.info("2a. {} returned {}", jp.getSignature().getName(), result);
}
@AfterThrowing(pointcut = ORDER_METHODS, throwing = "ex")
public void afterThrowing(JoinPoint jp, Exception ex) {
log.warn("2b. {} failed: {}", jp.getSignature().getName(), ex.getMessage());
}
@After(ORDER_METHODS)
public void after(JoinPoint jp) {
log.info("3. After {} (always runs)", jp.getSignature().getName());
}
}
The names in returning = "result" and throwing = "ex" must match the advice method’s parameter names. Their types also act as filters: Exception ex matches only methods that throw an Exception, and a more specific type would narrow it further.
8. @Around Advice in Depth
@Around is the most powerful advice: it wraps the method call completely. The advice receives a ProceedingJoinPoint and decides when, or whether, to call proceed():
@Aspect
@Component
public class PerformanceAspect {
private static final Logger log = LoggerFactory.getLogger(PerformanceAspect.class);
@Around("execution(public * com.javabytechie.shop.service..*(..))")
public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.nanoTime();
try {
return joinPoint.proceed(); // run the real method
} finally {
long millis = (System.nanoTime() - start) / 1_000_000;
if (millis > 500) {
log.warn("SLOW: {} took {} ms", joinPoint.getSignature().toShortString(), millis);
} else {
log.debug("{} took {} ms", joinPoint.getSignature().toShortString(), millis);
}
}
}
}
Three rules for @Around:
- Call
proceed(), or the real method never runs. - Return its result, or the caller receives
null. - Let exceptions pass through with
throws Throwable, unless you deliberately handle them.
Use the simplest advice that does the job. If you only need to act before a method, @Before is clearer and cannot accidentally skip the call.
9. Pointcut Expressions
Pointcut expressions decide which methods an advice applies to. The most common designator is execution:
execution(public * com.javabytechie.shop.service..*.*(..))
| Part | Meaning |
|---|---|
public | Visibility (optional; leave it out to match any) |
* | Any return type |
com.javabytechie.shop.service..* | Any class in the service package or its sub-packages (..) |
.* | Any method name |
(..) | Any number and type of parameters |
| Expression | Matches |
|---|---|
execution(* com.javabytechie.shop.service.*.*(..)) | Every method of classes directly in the service package |
execution(* com.javabytechie.shop..*(..)) | Every method in shop and all its sub-packages |
execution(* *..OrderService.place*(..)) | Methods starting with place in any OrderService |
execution(* *(String, ..)) | Methods whose first parameter is a String |
within(com.javabytechie.shop.web..*) | Every method of classes in the web packages |
@annotation(com.javabytechie.shop.aop.Audited) | Methods annotated with @Audited |
@within(org.springframework.stereotype.Service) | Every method of classes annotated with @Service |
bean(*Service) | Beans whose names end with Service (Spring-only) |
9.1 Reusable and combined pointcuts
Name pointcuts with @Pointcut, then combine them with &&, || and !:
@Aspect
@Component
public class ShopPointcuts {
@Pointcut("within(com.javabytechie.shop.service..*)")
public void serviceLayer() {}
@Pointcut("execution(* *..*Repository.*(..))")
public void repositoryMethods() {}
@Pointcut("execution(* get*(..)) || execution(* find*(..))")
public void readMethods() {}
@Before("serviceLayer() && !readMethods()")
public void beforeWrite(JoinPoint jp) {
// runs for service methods that are NOT reads
}
}
Named pointcuts make expressions readable and keep them in one place. To use them from another aspect, refer to them by their full name, such as com.javabytechie.shop.aop.ShopPointcuts.serviceLayer().
10. AOP with Custom Annotations
Matching methods by package is broad. A custom annotation lets developers choose exactly which methods get the behavior, and even pass settings to the aspect:
package com.javabytechie.shop.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
long warnAboveMillis() default 500;
}
@Aspect
@Component
public class ExecutionTimeAspect {
private static final Logger log = LoggerFactory.getLogger(ExecutionTimeAspect.class);
@Around("@annotation(logExecutionTime)")
public Object measure(ProceedingJoinPoint joinPoint, LogExecutionTime logExecutionTime) throws Throwable {
long start = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long millis = System.currentTimeMillis() - start;
if (millis > logExecutionTime.warnAboveMillis()) {
log.warn("{} took {} ms", joinPoint.getSignature().toShortString(), millis);
}
}
}
}
@Service
public class ReportService {
@LogExecutionTime(warnAboveMillis = 2000)
public Report buildMonthlyReport(YearMonth month) {
// ...
}
}
Writing @annotation(logExecutionTime) with a lowercase name binds the annotation to the advice parameter of the same name, so the aspect can read its settings, here warnAboveMillis. The parameter’s type tells Spring which annotation to look for. If you do not need the settings, use the fully qualified name instead, such as @annotation(com.javabytechie.shop.aop.LogExecutionTime).
11. Real-World Examples
11.1 Audit logging
Record who performed important actions, using the logged-in user from Spring Security:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Audited {
String action();
}
@Aspect
@Component
public class AuditAspect {
private final AuditRepository auditRepository;
public AuditAspect(AuditRepository auditRepository) {
this.auditRepository = auditRepository;
}
@AfterReturning("@annotation(audited)")
public void record(JoinPoint joinPoint, Audited audited) {
String user = Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
.map(Authentication::getName)
.orElse("anonymous");
auditRepository.save(new AuditEntry(user, audited.action(), Instant.now()));
}
}
@Audited(action = "CANCEL_ORDER")
public void cancelOrder(Long orderId) { ... }
Using @AfterReturning means only successful actions are recorded.
11.2 Retrying temporary failures
Repeat a call a few times when a dependency briefly fails:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RetryOnFailure {
int attempts() default 3;
long delayMillis() default 200;
}
@Aspect
@Component
public class RetryAspect {
private static final Logger log = LoggerFactory.getLogger(RetryAspect.class);
@Around("@annotation(retry)")
public Object retry(ProceedingJoinPoint joinPoint, RetryOnFailure retry) throws Throwable {
for (int attempt = 1; ; attempt++) {
try {
return joinPoint.proceed();
} catch (ResourceAccessException ex) { // only temporary network errors
if (attempt >= retry.attempts()) {
throw ex;
}
log.warn("Attempt {} of {} failed, retrying", attempt, retry.attempts());
Thread.sleep(retry.delayMillis());
}
}
}
}
This shows how aspects work, and is fine for simple needs. For production retries with backoff, metrics and circuit breakers, prefer a dedicated library such as Resilience4j, described in the Retry Pattern tutorial.
12. Ordering Multiple Aspects
When several aspects apply to the same method, control their order with @Order. A lower number means higher priority, so that aspect runs first on the way in and last on the way out:
@Aspect
@Component
@Order(1) // outermost: sees the total time, including the other aspects
public class PerformanceAspect { ... }
@Aspect
@Component
@Order(2)
public class AuditAspect { ... }
PerformanceAspect (before) ─▶ AuditAspect (before) ─▶ method
PerformanceAspect (after) ◀─ AuditAspect (after) ◀─┘
Without @Order, the order between aspects is undefined and may change between runs. Within one aspect, advice types run in a fixed order: @Around, @Before, then after the method @After, @AfterReturning or @AfterThrowing.
13. Testing an Aspect
Test an aspect without starting Spring, using AspectJProxyFactory, which builds the same kind of proxy Spring would:
class RetryAspectTest {
static class FlakyClient {
int calls = 0;
@RetryOnFailure(attempts = 3, delayMillis = 1)
public String fetch() {
calls++;
if (calls < 3) {
throw new ResourceAccessException("temporary network error");
}
return "ok";
}
}
@Test
void retriesUntilTheCallSucceeds() {
FlakyClient target = new FlakyClient();
AspectJProxyFactory factory = new AspectJProxyFactory(target);
factory.setProxyTargetClass(true);
factory.addAspect(new RetryAspect());
FlakyClient proxy = factory.getProxy();
assertThat(proxy.fetch()).isEqualTo("ok");
assertThat(target.calls).isEqualTo(3);
}
}
For aspects that depend on other beans, such as AuditAspect with its repository, a @SpringBootTest that calls an annotated method and checks the side effect works well.
14. Limitations of Spring AOP
| Limitation | Why | What to do |
|---|---|---|
| Calls within the same class are not advised | The call does not go through the proxy | Move the method to another bean |
private methods are not advised | Proxies cannot intercept them | Use public methods for advised behavior |
final classes and methods are not advised (CGLIB) | A subclass cannot override them | Remove final, or use interfaces |
| Only Spring beans are advised | Spring only creates proxies for beans | Let Spring manage the object, or use AspectJ weaving |
| Only method executions are join points | Proxies wrap method calls only | Use AspectJ for field access or constructors |
static methods are not advised | They belong to the class, not an object | Use instance methods |
15. Spring AOP vs AspectJ
| Spring AOP | AspectJ | |
|---|---|---|
| How it works | Runtime proxies | Weaves bytecode at compile time or class-load time |
| Join points | Method executions on Spring beans | Methods, constructors, field access, static methods and more |
| Self-invocation | Not advised | Advised |
| Setup | One starter, nothing else | Compiler plugin or a Java agent |
| Performance | Small per-call overhead | Close to hand-written code |
| Best for | Most Spring applications | Advanced needs beyond method calls |
Spring AOP uses AspectJ’s annotations and pointcut language, but not its weaving. Start with Spring AOP; switch to full AspectJ only when you hit a limitation you cannot design around.
16. Best Practices
- Use AOP for true cross-cutting concerns, not for business logic, which should stay visible in the code.
- Prefer custom annotations over broad package pointcuts, so it is obvious which methods are affected.
- Choose the simplest advice:
@Beforeor@AfterReturningbefore reaching for@Around. - Keep advice fast; it runs on every matching call.
- Never log sensitive arguments such as passwords or card numbers.
- Set
@Orderwhen several aspects can apply to the same method. - Test your aspects, including that they do not apply where they should not.
- Use a logger, never
System.out.println.
17. Summary
AOP moves cross-cutting concerns, such as logging, timing, auditing and retries, out of your business code and into aspects. In Spring Boot, add the AOP starter, write a class with @Aspect and @Component, choose where with a pointcut and when with one of the five advice types. Spring applies it through proxies, which is why only public calls between beans are advised. Custom annotations, named pointcuts, @Order and tests keep aspects clear and predictable, and full AspectJ is there for the rare cases that need more.
18. Frequently Asked Questions (FAQ)
Q1. Why does my aspect never run?
Work through this checklist:
- The AOP starter is missing from your dependencies.
@Componentis missing on the aspect, so Spring never creates it.- The pointcut does not match: a typo in the package name, or
.instead of..for sub-packages. - The annotation in
@annotation(...)is not fully qualified and is in a different package from the aspect. - The method is called from the same class, so it skips the proxy.
- The method is
private,finalorstatic, or the object is not a Spring bean.
A quick test: temporarily use a very broad pointcut, such as within(com.javabytechie..*), and see whether the advice runs at all.
Q2. Does AOP slow down my application?
Only slightly. Each advised call passes through the proxy and the advice, which costs a tiny amount of time, usually far less than a database query or HTTP call.
It becomes noticeable only when:
- The pointcut is very broad and matches small methods that are called millions of times.
- The advice itself does slow work, such as writing to a database on every call.
Keep pointcuts targeted, keep advice fast, and measure if in doubt.
Q3. Why does my bean’s class name contain $$SpringCGLIB$$?
Because you are looking at the proxy, not your original class. When an aspect applies to a bean, Spring creates a subclass at runtime, with a name such as OrderService$$SpringCGLIB$$0, and injects it in place of the original.
This is normal and harmless. It also explains why getClass() on an injected bean may not return exactly your class. Use AopUtils.getTargetClass(bean) if you need the original class.
Q4. Should I use an aspect, a HandlerInterceptor or a servlet filter?
| Servlet filter | HandlerInterceptor | Aspect | |
|---|---|---|---|
| Works on | Every HTTP request | Requests handled by Spring MVC controllers | Any Spring bean method |
| Sees | Raw request and response | Request, response and the handler method | Method arguments and return values |
| Typical use | Security, CORS, compression | Web logging, locale, request timing | Service-level logging, auditing, retries |
Use filters and interceptors for concerns about HTTP requests, and aspects for concerns about method calls, especially in the service layer, where no HTTP request may be involved at all.
Q5. Can I apply an aspect to JPA entities?
Not with Spring AOP. Entities are created by Hibernate, not by Spring, so Spring never wraps them in a proxy.
For entity events, use JPA’s own callbacks instead:
@Entity
@EntityListeners(AuditListener.class)
public class Order {
@PrePersist
void beforeInsert() {
this.createdAt = Instant.now();
}
}
Annotations such as @PrePersist, @PreUpdate and @PostLoad, or an entity listener class, cover most needs. Full AspectJ weaving could advise entities, but it is rarely worth the setup.
Q6. How do I get the method name and argument names in an advice?
Cast the signature to MethodSignature:
@Before("@annotation(com.javabytechie.shop.aop.Audited)")
public void describe(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String className = signature.getDeclaringType().getSimpleName();
String methodName = signature.getName();
String[] parameterNames = signature.getParameterNames();
Object[] values = joinPoint.getArgs();
for (int i = 0; i < values.length; i++) {
log.info("{}.{}: {} = {}", className, methodName, parameterNames[i], values[i]);
}
}
signature.getMethod() also gives the full java.lang.reflect.Method, for example to read other annotations on it.
Q7. Can an aspect change a method’s arguments or return value?
Yes, but only @Around advice can. Pass new arguments to proceed(), and return whatever result you want:
@Around("@annotation(com.javabytechie.shop.aop.TrimStrings)")
public Object trimArguments(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof String text) {
args[i] = text.trim();
}
}
return joinPoint.proceed(args); // call the method with the cleaned arguments
}
Use this sparingly. Changing data invisibly can surprise other developers, who see one value at the call site and a different one inside the method.
Q8. How do I switch an aspect on only in some environments?
An aspect is a normal bean, so the usual conditions work:
@Aspect
@Component
@Profile("dev") // only in the dev profile
public class DetailedLoggingAspect { ... }
@Aspect
@Component
@ConditionalOnProperty(name = "shop.audit.enabled", havingValue = "true")
public class AuditAspect { ... }
If the bean is not created, the aspect simply does not exist, and the advised methods run without it.
Q9. What happens if my advice throws an exception?
The exception reaches the caller, just as if the method itself had thrown it:
- An exception in
@Beforeadvice means the method never runs. - An exception in
@AfterReturningadvice turns a successful call into a failure for the caller. - Inside a transaction, it can cause a rollback.
For non-essential concerns, such as logging or metrics, catch exceptions inside the advice, so a logging problem never breaks a business operation.
Q10. Does my aspect run inside or outside the transaction?
It depends on the order. @Transactional is itself an aspect, with the lowest priority by default, so it sits closest to your method. A custom aspect with a normal @Order value therefore runs outside the transaction:
YourAspect (Order 1) ─▶ Transaction advice ─▶ your method
This matters, for example, for a retry aspect: running outside the transaction, each retry starts a fresh transaction, which is usually what you want. If your aspect must run inside the transaction, give the transaction advice a higher priority with @EnableTransactionManagement(order = ...).
Q11. How do I log method arguments without exposing passwords?
Never log every argument blindly. Mark sensitive parameters and mask them in the aspect:
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Sensitive { }
// In the aspect
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Annotation[][] annotations = signature.getMethod().getParameterAnnotations();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
boolean sensitive = Arrays.stream(annotations[i]).anyMatch(a -> a instanceof Sensitive);
log.info("arg {} = {}", i, sensitive ? "****" : args[i]);
}
public void changePassword(String username, @Sensitive String newPassword) { ... }
Also be careful with whole objects: a DTO’s toString() may include sensitive fields, so exclude them there too.
Q12. Can an aspect read the current HTTP request?
Yes, when the call happens during a web request:
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
log.info("{} {} from {}", request.getMethod(), request.getRequestURI(), request.getRemoteAddr());
}
Always check for null: the same service method may also be called from a scheduled job or a message listener, where there is no request. If the aspect is really about HTTP requests, a filter or interceptor is usually the better tool.
Q13. What is the difference between AOP and the decorator pattern?
Both add behavior around existing code without changing it. The difference is scale and wiring:
- A decorator is a class you write by hand that wraps one specific interface and delegates to it. It is explicit and easy to follow, but you write one per interface.
- An aspect applies to many classes at once, chosen by a pointcut, and Spring does the wrapping for you.
For behavior on one or two types, a decorator is often clearer. For the same concern across dozens of classes, an aspect avoids a lot of repetitive code.
Q14. How many aspects is too many?
There is no fixed number, but every aspect adds behavior that is not visible in the code it affects. When a method behaves in surprising ways and the reason is hidden in an aspect, debugging becomes slow.
Good signs that you are using AOP well:
- Each aspect handles one clear, technical concern.
- Annotations such as
@Auditedmake it visible which methods are affected. - The team can list every aspect in the project from memory.
If an aspect starts to contain business rules, move that logic back into the service code.
Q15. Can I use ready-made aspects for metrics instead of writing my own?
Yes. Micrometer, which Spring Boot uses for metrics, provides annotation-based aspects:
@Timed(value = "orders.place", description = "Time to place an order")
public Order placeOrder(OrderRequest request) { ... }
@Observed(name = "reports.build") // timer metrics and a tracing span together
public Report buildMonthlyReport(YearMonth month) { ... }
These need the matching aspect beans, TimedAspect or ObservedAspect, which recent Spring Boot versions can register for you. The results appear in Actuator metrics and monitoring tools, without any custom timing code.
Q16. What does “weaving” mean?
Weaving is the step that combines aspects with your code. It can happen at three moments:
| Type | When | Used by |
|---|---|---|
| Compile-time | While compiling, using a special compiler | AspectJ |
| Load-time | When the JVM loads each class, using a Java agent | AspectJ |
| Runtime | When the application runs, by creating proxies | Spring AOP |
Runtime weaving needs no special build steps, which is why Spring AOP is so easy to start with, but it is also why it only works through proxies.
Q17. What is the difference between @annotation and @within in a pointcut?
@annotation(X)matches methods that themselves carry annotationX.@within(X)matches every method of classes annotated withX.
@Audited // @within(Audited) matches every method here
public class PaymentService {
@Audited // @annotation(Audited) matches only this one
public void refund(Long id) { ... }
public void charge(Long id) { ... }
}
Combine them with || to support both styles: annotate a whole class, or just selected methods.
Q18. Can an advised method be called from a static or private method?
The advice runs only when the call reaches the advised method through the proxy. Calls from a static or private method in the same class go directly to the object, so no advice runs, just as with any self-invocation.
Calls from a different bean always go through the proxy, regardless of whether the calling method there is private or static. The rule is about who holds the reference: the proxy or the raw object.
Q19. What are AOP introductions (@DeclareParents)?
An introduction makes existing beans implement an extra interface, with an implementation supplied by the aspect:
@Aspect
@Component
public class UsageTrackingIntroduction {
@DeclareParents(value = "com.javabytechie.shop.service.*+",
defaultImpl = DefaultUsageTracked.class)
public static UsageTracked mixin;
}
Every matching bean can then be cast to UsageTracked. Introductions are rarely used in everyday applications, because ordinary interfaces and composition are usually clearer, but they are useful to know when reading framework code.
Q20. Is AOP still useful in modern Spring applications?
Very much so, often without you noticing. @Transactional, @Cacheable, @Async, @Retryable, method security with @PreAuthorize, and Micrometer’s @Timed and @Observed are all built on AOP.
Understanding how aspects and proxies work explains the most common surprises with all of these features, such as self-invocation and private methods, even if you rarely write an aspect yourself.