Core Concepts of Aspect-Oriented Programming
Overview
Aspect-Oriented Programming (AOP) focuses on modularizing cross-cutting concerns. It employs either compile-time weaving or runtime dynamic proxies to apply additional behavior to existing code without altering the source files.
Purpose: To inject supplementary logic (like logging or security) into methods during runtime without touching the original codebase. Benefits: Eliminates code duplication, accelerates development, and simplifies maintenance. Mechanism: Spring utilizes dynamic proxying at runtime to generate proxy objects that wrap the original beans.
Proxying Techniques
- JDK Dynamic Proxy: Requires the target to implement at least one interface. The proxy implements the same interfaces.
- CGLIB Proxy: Creates a subclass of the target class. Useful when the target does not implement interfacse.
Key Terminology
| Term | Description |
|---|---|
| Target Object | The original object being advised by one or more aspects. |
| Proxy | The object created by the AOP framework after applying advice to the target object. |
| Join Point | A specific point during program execution, such as method invocation. In Spring AOP, this is always a method execution. |
| Pointcut | A predicate that matches join points. Advice is associated with a pointcut expression and runs at any join point matched by the pointcut. |
| Advice | Action taken at a particular join point. Different types include "around", "before", and "after". |
| Aspect | A modularization of a concern that cuts across multiple classes. It combines pointcuts and advice. |
| Weaving | Linking aspects with other application types or objects to create an advised object. Spring performs weaving at runtime. |
Development Tasks
- Implement the core business logic (Target methods).
- Define an Aspect class containing the enhancement logic (Advice methods).
- Configure the weave relationship (mapping advice to specific pointcuts) via XML or Annotations.
Pointcut Expression Syntax
The standard syntax for defining where advice should be applied is:
execution([Visibility] ReturnType [ClassPath].MethodName(Args))
- Visibility can be omitted.
- Use
*as a wildcard for return type, package, class, or method names. - A single
.matches the current package;..matches the current package and all sub-packages. ..in arguments matches any number and type of arguments.
Examples:
// Matches a specific public method
execution(public void com.example.service.Calculator.add())
// Matches any method returning void in the service package
execution(void com.example.service.*.*(..))
// Matches any method in the service package or sub-packages
execution(* com.example.service..*.*(..))
Advice Types
| Advice Type | XML Tag | Annotation | Behavior |
|---|---|---|---|
| Before | <aop:before> |
@Before |
Executes logic prior to the method invocation. |
| After Returning | <aop:after-returning> |
@AfterReturning |
Executes logic after the method completes successfully. |
| Around | <aop:around> |
@Around |
Wraps the method, alllowing logic before and after, and capable of modifying return values or handling exceptions. |
| After Throwing | <aop:after-throwing> |
@AfterThrowing |
Executes logic if the method throws an exception. |
| After (Finally) | <aop:after> |
@After |
Executes logic regardless of the outcome (similar to finally). |
Implementation Guide
1. XML-Based Configuration
(1) Dependencies
Add the necessary Spring and AspectJ libraries to your project.
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
(2) Target Class
Define a simple service interface and its implementation.
package com.cqgcxy.service;
public interface PhoneService {
void select();
}
package com.cqgcxy.service.impl;
import com.cqgcxy.service.PhoneService;
public class PhoneServiceImpl implements PhoneService {
@Override
public void select() {
System.out.println("Global Market: Huawei Dominance");
}
}
(3) Aspect Class
Create a class to handle the logging logic.
package com.cqgcxy.monitor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import java.time.LocalDateTime;
public class PerformanceMonitor {
public Object trackExecution(ProceedingJoinPoint pjp) throws Throwable {
Signature sig = pjp.getSignature();
LocalDateTime start = LocalDateTime.now();
System.out.println(start + " -> " + sig + " -> Start");
Object result = pjp.proceed();
LocalDateTime end = LocalDateTime.now();
System.out.println(end + " -> " + sig + " -> End");
return result;
}
}
(4) Spring Configuration (XML)
Register beans and define the AOP weaving.
<bean id="phoneService" class="com.cqgcxy.service.impl.PhoneServiceImpl" />
<bean id="monitor" class="com.cqgcxy.monitor.PerformanceMonitor" />
<aop:config>
<aop:aspect ref="monitor">
<!-- Option 1: Inline pointcut -->
<aop:around
method="trackExecution"
pointcut="execution(* com.cqgcxy.service..*(..))" />
<!-- Option 2: Referenced pointcut -->
<!--
<aop:pointcut id="serviceOps" expression="execution(* com.cqgcxy.service..*(..))"/>
<aop:around method="trackExecution" pointcut-ref="serviceOps"/>
-->
</aop:aspect>
</aop:config>
(5) Test Case
package com.cqgcxy.service.impl;
import com.cqgcxy.service.PhoneService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class PhoneServiceImplTest {
@Autowired
private PhoneService phoneService;
@Test
public void testSelect() {
phoneService.select();
}
}
2. Annotation-Based Configuration
(1) Component Scanning and Enabling AOP
Use a Java configuration class to replace the XML file.
@Configuration
@ComponentScan("com.cqgcxy")
@EnableAspectJAutoProxy
public class AppConfig {
}
(2) Annotated Service
package com.cqgcxy.service.impl;
import com.cqgcxy.service.PhoneService;
import org.springframework.stereotype.Service;
@Service
public class PhoneServiceImpl implements PhoneService {
@Override
public void select() {
System.out.println("Global Market: Huawei Dominance");
}
}
(3) Aspect with Annotations
Annotate the class with @Aspect and @Component.
package com.cqgcxy.monitor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.LocalDateTime;
@Aspect
@Component
public class PerformanceMonitor {
@Around("execution(* com.cqgcxy.service.impl.PhoneServiceImpl.select(..))")
public Object trackExecution(ProceedingJoinPoint pjp) throws Throwable {
LocalDateTime start = LocalDateTime.now();
System.out.println(start + " -> " + pjp.getSignature() + " -> Start");
Object result = pjp.proceed();
LocalDateTime end = LocalDateTime.now();
long millis = Duration.between(start, end).toMillis();
System.out.println(end + " -> " + pjp.getSignature() + " -> End (" + millis + "ms)");
return result;
}
}
(4) Test Case
package com.cqgcxy.service.impl;
import com.cqgcxy.service.PhoneService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class PhoneServiceImplTest {
@Autowired
private PhoneService phoneService;
@Test
public void testSelect() {
phoneService.select();
}
}
Utility Methods in ProceedingJoinPoint
The ProceedingJoinPoint interface provides context about the intercepted method:
Object proceed(): Executes the next advice or the target method.Object proceed(Object[] args): Executes the target method with modified arguments.Signature getSignature(): Retrieves method details (name, return type, etc.).Object getTarget(): Returns the actual target object instance.Object getThis(): Returns the proxy object.Object[] getArgs(): Returns the array of arguments passed to the method.