00:00

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:

OrderService.java (without AOP)
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:

OrderService.java (with AOP)
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 caseWhat the aspect does
Logging and tracingLog method calls, arguments and results
Performance monitoringMeasure and record how long methods take
AuditingRecord who did what, and when
SecurityCheck permissions before a method runs
RetryRepeat a call that failed with a temporary error
Error translationConvert 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

TermMeaningIn the logging example
AspectA class that holds cross-cutting logic, marked @AspectLoggingAspect
Join pointA point in the program where an aspect can run; in Spring AOP, always a method executionThe call to placeOrder()
PointcutAn expression that selects which join points to apply the aspect to“Every public method in the service package”
AdviceThe code that runs, and when: before, after or around the methodLog the start time, then the duration
TargetThe original object whose methods are advisedThe real OrderService
ProxyThe wrapper Spring creates around the targetWhat other beans actually call
WeavingCombining aspects with the target codeSpring 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:

Calling an advised method
OrderController ──▶ Proxy of OrderService ──▶ real OrderService.placeOrder()
                     │ before advice runs        │
                     │                           │ business logic runs
                     │ after advice runs  ◀──────┘
Proxy typeUsed whenNotes
CGLIB (subclass)Default in Spring BootCreates a subclass of your class at runtime; cannot advise final classes or methods
JDK dynamic proxyWhen configured with spring.aop.proxy-target-class=false and the bean implements an interfaceOnly 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:

pom.xml
<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:

LoggingAspect.java
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()));
    }
}
Console
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

AdviceRunsCan stop or change the call?
@BeforeBefore the methodOnly by throwing an exception
@AfterReturningAfter the method returns normallyCan read the return value
@AfterThrowingAfter the method throws an exceptionCan read the exception
@AfterAfter the method, whether it returned or threw (like finally)No
@AroundBefore and after; it decides whether the method runs at allYes: arguments, result and exceptions

This aspect uses all five on the same methods, so you can see exactly when each one runs:

OrderEventsAspect.java
@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():

PerformanceAspect.java
@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:

  1. Call proceed(), or the real method never runs.
  2. Return its result, or the caller receives null.
  3. 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:

Example
execution(public * com.javabytechie.shop.service..*.*(..))
PartMeaning
publicVisibility (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
ExpressionMatches
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 !:

ShopPointcuts.java
@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:

LogExecutionTime.java
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;
}
ExecutionTimeAspect.java
@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);
            }
        }
    }
}
ReportService.java
@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:

Audited.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Audited {
    String action();
}
AuditAspect.java
@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()));
    }
}
OrderService.java
@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:

RetryOnFailure.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RetryOnFailure {
    int attempts() default 3;
    long delayMillis() default 200;
}
RetryAspect.java
@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 order
@Aspect
@Component
@Order(1)        // outermost: sees the total time, including the other aspects
public class PerformanceAspect { ... }

@Aspect
@Component
@Order(2)
public class AuditAspect { ... }
Execution order
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:

RetryAspectTest.java
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

LimitationWhyWhat to do
Calls within the same class are not advisedThe call does not go through the proxyMove the method to another bean
private methods are not advisedProxies cannot intercept themUse public methods for advised behavior
final classes and methods are not advised (CGLIB)A subclass cannot override themRemove final, or use interfaces
Only Spring beans are advisedSpring only creates proxies for beansLet Spring manage the object, or use AspectJ weaving
Only method executions are join pointsProxies wrap method calls onlyUse AspectJ for field access or constructors
static methods are not advisedThey belong to the class, not an objectUse instance methods

15. Spring AOP vs AspectJ

Spring AOPAspectJ
How it worksRuntime proxiesWeaves bytecode at compile time or class-load time
Join pointsMethod executions on Spring beansMethods, constructors, field access, static methods and more
Self-invocationNot advisedAdvised
SetupOne starter, nothing elseCompiler plugin or a Java agent
PerformanceSmall per-call overheadClose to hand-written code
Best forMost Spring applicationsAdvanced 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: @Before or @AfterReturning before reaching for @Around.
  • Keep advice fast; it runs on every matching call.
  • Never log sensitive arguments such as passwords or card numbers.
  • Set @Order when 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:

  1. The AOP starter is missing from your dependencies.
  2. @Component is missing on the aspect, so Spring never creates it.
  3. The pointcut does not match: a typo in the package name, or . instead of .. for sub-packages.
  4. The annotation in @annotation(...) is not fully qualified and is in a different package from the aspect.
  5. The method is called from the same class, so it skips the proxy.
  6. The method is private, final or static, 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 filterHandlerInterceptorAspect
Works onEvery HTTP requestRequests handled by Spring MVC controllersAny Spring bean method
SeesRaw request and responseRequest, response and the handler methodMethod arguments and return values
Typical useSecurity, CORS, compressionWeb logging, locale, request timingService-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 @Before advice means the method never runs.
  • An exception in @AfterReturning advice 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 @Audited make 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:

TypeWhenUsed by
Compile-timeWhile compiling, using a special compilerAspectJ
Load-timeWhen the JVM loads each class, using a Java agentAspectJ
RuntimeWhen the application runs, by creating proxiesSpring 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 annotation X.
  • @within(X) matches every method of classes annotated with X.
@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.