Fundamentals of Aspect-Oriented Programming
Core Concepts
AOP (Aspect Oriented Programming) is a programming paradigm focused on modularizing cross-cutting concerns. While OOP structures code around classes and objects, AOP concentrates on separating common functionalities—such as logging, security, or transaction management—from the core business logic.
Key terminology:
- Joinpoint: A specific point in the execution of a program, typically a method invocation.
- Pointcut: An expression that matches one or more joinpoints where advice should be applied.
- Advice: The action taken at a particular joinpoint (e.g., before, after, or around a method).
- Aspect: The modularization of a concern, combining pointcuts and advice.
- Target Object: The object being advised by one or more aspects.
- Weaving: The process of linking aspects with the target object to create an advised object.
- Proxy: An object created by the AOP framework to implement the aspect contracts.
Development Workflow
- Implementation: Develop the main business logic.
- Extraction: Identify and extract common functionalities in to seperate advice methods.
- Configuration: Define pointcuts to match business methods and bind them to advice using aspects.
- Execution: The Spring container monitors method executions and uses proxies to apply the advice dynamically.
Getting Started with XML Configuration
Dependencies
Add the AspectJ weaver to your project:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
Basic Setup
- Create a class containing your cross-cutting logic (e.g., logging).
- Declare this class as a Spring bean.
- Configure the AOP relationships in your XML context file.
<!-- Enable AOP -->
<aop:config>
<!-- Define a pointcut matching all methods in service layer -->
<aop:pointcut id="serviceLayer" expression="execution(* com.example.service.*.*(..))"/>
<!-- Define the aspect -->
<aop:aspect ref="loggingAspect">
<aop:before method="logStart" pointcut-ref="serviceLayer"/>
</aop:aspect>
</aop:config>
Pointcut Expressions
Expressions define where the advice should be applied. They follow the pattern:
execution( modifiers? return-type declaring-type? method-name(param) throws? )
Wildcards
*: Matches a single token (e.g., return type, method name)...: Matches zero or more parameters or packages.+: Matches a specific class and its subclasses.
Examples
execution(public * *(..)): All public methods.execution(* com.example.service.*Service.*(..)): All methods in classes ending with 'Service' in a specific package.execution(* com..*.find*(..)): Allfindmethods in any class under thecompackage hierarchy.
Logical Operators
Combine expressions using && (and), || (or), and ! (not).
Advice Types
1. Before Advice
Runs before the target method. If it throws an exception, the target method will not execute.
<aop:before method="validateArgs" pointcut="execution(* *..*(..))"/>
2. After Advice (Finally)
Runs after the target method, regardless of whether it threw an exception.
<aop:after method="cleanup" pointcut-ref="pt"/>
3. After Returning Advice
Runs only if the target method completes successfully.
<aop:after-returning method="processResult" pointcut-ref="pt" returning="result"/>
4. After Throwing Advice
Runs only if the target method throws an exception.
<aop:after-throwing method="handleError" pointcut-ref="pt" throwing="ex"/>
5. Around Advice
The most powerful advice. It wraps the method execution and can control whether the method actually runs.
public Object interceptOperation(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("Before method: " + pjp.getSignature().getName());
Object output = pjp.proceed(); // Execute the actual method
System.out.println("After method");
return output;
}
Annotation-Based Configuration
Enabling Annotasions
Use @EnableAspectJAutoProxy in your Java configuration class:
@Configuration
@ComponentScan("com.example")
@EnableAspectJAutoProxy
public class AppConfig { }
Defining Aspects
@Aspect
@Component
public class MonitoringAspect {
// Define a reusable pointcut
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
@Before("serviceLayer()")
public void logAccess(JoinPoint jp) {
System.out.println("Accessing: " + jp.getSignature().toShortString());
}
@Around("serviceLayer()")
public Object measureTime(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object val = pjp.proceed();
long end = System.currentTimeMillis();
System.out.println(pjp.getSignature() + " executed in " + (end - start) + "ms");
return val;
}
}
Proxy Mechanisms
Spring AOP uses proxies to apply aspects.
JDK Dynamic Proxy
- Requirement: The target object must implement at least one interface.
- Mechanism: Creates a proxy that implements the same interfaces as the target.
- Limitation: Cannot proxy methods not defined in an interface.
// Conceptual example of JDK Proxy
InvocationHandler handler = (proxy, method, args) -> {
System.out.println("Proxying: " + method.getName());
return method.invoke(targetObject, args);
};
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
loader, new Class[]{MyInterface.class}, handler);
CGLIB Proxy
- Requirement: No interface required.
- Mechanism: Generates a subclass of the target class at runtime.
- Limitation: Cannot proxy
finalclasses or methods.
// Conceptual example of CGLIB
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetClass.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
System.out.println("Intercepted: " + method.getName());
return proxy.invokeSuper(obj, args);
});
TargetClass proxy = (TargetClass) enhancer.create();
Switching Proxy Type
By default, Spring uses JDK proxies if interfaces exist, otherwise CGLIB. Force CGLIB usage:
- XML:
<aop:config proxy-target-class="true"> - Annotation:
@EnableAspectJAutoProxy(proxyTargetClass = true)
Practical Example: Performance Monitoring
Monitor the execution time of service layer methods.
@Aspect
@Component
public class PerformanceMonitor {
@Around("execution(* com.example.service.*.*(..))")
public Object trackPerformance(ProceedingJoinPoint pjp) throws Throwable {
String className = pjp.getSignature().getDeclaringTypeName();
String methodName = pjp.getSignature().getName();
long startTime = System.nanoTime();
try {
return pjp.proceed();
} finally {
long duration = System.nanoTime() - startTime;
System.out.printf("%s.%s executed in %.2f ms%n", className, methodName, duration / 1e6);
}
}
}