Skip to content
thesarfo

Concept

Aspect-Oriented Programming

AOP fundamentals — aspects, advice, pointcuts, and join points — plus a working example demonstrating all five advice types, and how AOP differs structurally from OOP.

views 0

AOP is a programming paradigm that enables the modularization of cross-cutting concerns in software applications.

Cross-cutting concerns are aspects of an application that affect multiple parts of the codebase — logging, security, transactions, error handling. AOP lets you separate these from the core logic, keeping code more maintainable and less cluttered.

Key terminology

  • Aspect — a module that encapsulates a cross-cutting concern. Contains advice and pointcuts.
  • Advice — the code that runs when a pointcut matches. Types include “before,” “after,” “around,” and “after throwing.”
  • Pointcut — an expression defining where an aspect’s advice should apply — it selects specific join points.
  • Join point — a specific point in program execution, such as a method call, constructor invocation, or field access.

Creating aspects

Define a class annotated @Aspect, containing advice methods:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class LoggingAspect {
@Before("execution(* com.example.demo.*.*(..))")
public void logBefore() {
System.out.println("Before method execution");
}
}

LoggingAspect has a @Before advice that logs before any method execution in the com.example.demo package.

Defining pointcuts

Pointcuts define where advice applies, via an expression matching join points. In "execution(* com.example.demo.*.*(..))":

  1. execution — this is a pointcut for method execution.
  2. * — matches any return type.
  3. *.*(..) — matches any method name with any number of arguments.

Pointcut expressions can be highly customized to target specific methods and classes.

Writing advice

  1. @Before — runs before the join point. Good for logging, security checks, pre-processing.
  2. @After — runs after the join point. Good for cleanup or finalizing operations.
  3. @Around — wraps the join point entirely, letting you control its execution: modify input/output, act before and after, or even prevent it from running. The most powerful advice type.
  4. @AfterThrowing — runs if the join point throws an exception. Good for handling or logging exceptions.

Worked example: all advice types

A HelloService bean whose single method gets intercepted by every advice type in one aspect:

@Service
public class HelloService {
public String hello(String name) {
String message = "Hello ".concat(name);
System.out.println(message);
return message;
}
}
@Aspect
@Component
public class HelloServiceAspect {
// the annotations on aspect methods are called join points; the "execution(...)"
// expression in each annotation's brackets is the pointcut
@Before("execution(* dev.thesarfo.service.HelloService.hello(..))")
public void before() {
System.out.println("Before This method has been called");
}
// runs whether an exception is thrown or not
@After("execution(* dev.thesarfo.service.HelloService.hello(..))")
public void after() {
System.out.println("After this method has been called");
}
// runs only when no exception is thrown
@AfterReturning("execution(* dev.thesarfo.service.HelloService.hello(..))")
public void afterReturning() {
System.out.println("After this method has been called without an exception");
}
// runs only when the method throws
@AfterThrowing("execution(* dev.thesarfo.service.HelloService.hello(..))")
public void afterThrowing() {
System.out.println("After this method has been called with an exception");
}
// lets you fully control before, during, and after execution
// return type is Object because aspects are decoupled from the app, so you usually
// don't know the return type of the method being intercepted; the ProceedingJoinPoint
// is the instance of the method being intercepted
@Around("execution(* dev.thesarfo.service.HelloService.hello(..))")
public Object around(ProceedingJoinPoint joinPoint) {
System.out.println("A");
Object result = null;
try {
result = joinPoint.proceed();
System.out.println("B");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
}
@Configuration
@ComponentScan(basePackages = {"dev.thesarfo.service", "dev.thesarfo.aspects"})
@EnableAspectJAutoProxy
public class ProjectConfig {
}
public class App {
public static void main(String[] args) {
try (AnnotationConfigApplicationContext c = new AnnotationConfigApplicationContext(ProjectConfig.class)) {
HelloService service = c.getBean(HelloService.class);
String message = service.hello("Ernest");
System.out.println("Result is " + message);
}
}
}

Calling service.hello("Ernest") here triggers, in order: @Around’s “A”, @Before, the actual method body, @Around’s “B”, @AfterReturning, then @After.

Enabling AOP

Add @EnableAspectJAutoProxy to the main application class (or a @Configuration class, as above) to enable AspectJ-based proxy creation, letting Spring weave aspects into the application’s components:

@SpringBootApplication
@EnableAspectJAutoProxy
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

Advanced AOP techniques

AspectJ with custom annotations — define your own annotation and match on it in a pointcut:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
}
@Aspect
public class CustomAspect {
@Before("@annotation(Loggable)")
public void logMethod(JoinPoint joinPoint) {
// Log method execution
}
}

Any method annotated @Loggable gets logged by logMethod.

More expressive pointcuts — match on arguments too, not just method signatures:

@Around("execution(* com.example.service.*.*(..)) && args(arg1, arg2)")
public Object customAroundAdvice(ProceedingJoinPoint joinPoint, Object arg1, Object arg2) {
// Custom around advice logic
return joinPoint.proceed();
}

Aspect ordering — advice within an aspect defaults to order 0; use @Order to control execution order when multiple aspects apply to the same join point (lower values run first).

Exception handling@AfterThrowing can log or otherwise handle exceptions thrown by matched methods:

@Aspect
public class ExceptionHandlingAspect {
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
public void handleException(Throwable ex) {
// Handle the exception
}
}

AOP vs. OOP

Object-Oriented Programming

  • Class-based — the class is the primary unit, encapsulating data and behavior; objects are instances of classes.
  • Inheritance & polymorphism — build class hierarchies for code reuse and overriding; treat objects as instances of a parent type for flexible, interchangeable code.
  • Encapsulation — hide an object’s internal state behind public methods, promoting modularity and reducing dependencies.
  • Entities & relationships — design revolves around objects and how they interact.

Aspect-Oriented Programming

  • Aspect-based — the aspect is the primary unit of modularity, encapsulating a cross-cutting concern (logging, security, transactions) via advice and pointcuts.
  • Separation of concerns — cross-cutting concerns are pulled out of the main business logic entirely, instead of tangled into it.
  • Join points & advice — join points are specific execution points (method calls, field access); advice is the code that runs at them (before/after/around).
  • Pointcuts — expressions matching which join points an aspect applies to.
  • Weaving — the process of integrating aspects into the codebase, at compile time, load time, or runtime depending on the framework (AspectJ, Spring AOP).

In short: OOP structures code around objects and their interactions; AOP structures it around join points and advice, cleanly separating concerns that would otherwise cut across many objects. The two are complementary rather than competing — most Spring apps lean on both.