0
# Aspect-Oriented Programming (AOP)
1
2
Spring AOP provides aspect-oriented programming implementation allowing you to define method interceptors and pointcuts to cleanly decouple cross-cutting concerns like logging, security, transactions, and caching from your business logic.
3
4
## Maven Dependencies
5
6
```xml
7
<!-- Spring AOP -->
8
<dependency>
9
<groupId>org.springframework</groupId>
10
<artifactId>spring-aop</artifactId>
11
<version>5.3.39</version>
12
</dependency>
13
14
<!-- AspectJ for advanced AOP features -->
15
<dependency>
16
<groupId>org.aspectj</groupId>
17
<artifactId>aspectjweaver</artifactId>
18
<version>1.9.7</version>
19
</dependency>
20
21
<!-- Enable AspectJ support -->
22
<dependency>
23
<groupId>org.springframework</groupId>
24
<artifactId>spring-aspects</artifactId>
25
<version>5.3.39</version>
26
</dependency>
27
```
28
29
## Core Imports
30
31
```java { .api }
32
// Core AOP interfaces
33
import org.aopalliance.intercept.MethodInterceptor;
34
import org.aopalliance.intercept.MethodInvocation;
35
import org.springframework.aop.MethodBeforeAdvice;
36
import org.springframework.aop.AfterReturningAdvice;
37
import org.springframework.aop.ThrowsAdvice;
38
39
// Pointcut and Advisor interfaces
40
import org.springframework.aop.Pointcut;
41
import org.springframework.aop.Advisor;
42
import org.springframework.aop.PointcutAdvisor;
43
import org.springframework.aop.ClassFilter;
44
import org.springframework.aop.MethodMatcher;
45
46
// Proxy creation
47
import org.springframework.aop.framework.ProxyFactory;
48
import org.springframework.aop.framework.AopProxy;
49
import org.springframework.aop.aspectj.AspectJProxyFactory;
50
51
// AspectJ annotations
52
import org.aspectj.lang.annotation.Aspect;
53
import org.aspectj.lang.annotation.Before;
54
import org.aspectj.lang.annotation.After;
55
import org.aspectj.lang.annotation.AfterReturning;
56
import org.aspectj.lang.annotation.AfterThrowing;
57
import org.aspectj.lang.annotation.Around;
58
import org.aspectj.lang.annotation.Pointcut;
59
import org.aspectj.lang.JoinPoint;
60
import org.aspectj.lang.ProceedingJoinPoint;
61
62
// Configuration
63
import org.springframework.context.annotation.EnableAspectJAutoProxy;
64
import org.springframework.aop.config.AopConfigUtils;
65
```
66
67
## Core AOP Concepts
68
69
### Advice Types
70
71
```java { .api }
72
// Tag interface for all advice types
73
public interface Advice {
74
}
75
76
// Method interceptor that sits around method invocations
77
@FunctionalInterface
78
public interface MethodInterceptor extends Interceptor {
79
Object invoke(MethodInvocation invocation) throws Throwable;
80
}
81
82
// Advice invoked before method execution
83
public interface MethodBeforeAdvice extends BeforeAdvice {
84
void before(Method method, Object[] args, Object target) throws Throwable;
85
}
86
87
// Advice invoked after successful method execution
88
public interface AfterReturningAdvice extends AfterAdvice {
89
void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
90
}
91
92
// Advice invoked after method throws exception
93
public interface ThrowsAdvice extends AfterAdvice {
94
// Marker interface - implementations should have methods like:
95
// public void afterThrowing(Exception ex)
96
// public void afterThrowing(Method method, Object[] args, Object target, Exception ex)
97
}
98
```
99
100
### Pointcut and Advisor
101
102
```java { .api }
103
// Core interface expressing where advice should be applied
104
public interface Pointcut {
105
ClassFilter getClassFilter();
106
MethodMatcher getMethodMatcher();
107
108
Pointcut TRUE = TruePointcut.INSTANCE;
109
}
110
111
// Interface for matching classes
112
@FunctionalInterface
113
public interface ClassFilter {
114
boolean matches(Class<?> clazz);
115
ClassFilter TRUE = TrueClassFilter.INSTANCE;
116
}
117
118
// Interface for matching methods
119
public interface MethodMatcher {
120
boolean matches(Method method, Class<?> targetClass);
121
boolean isRuntime();
122
boolean matches(Method method, Class<?> targetClass, Object... args);
123
124
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
125
}
126
127
// Base interface holding advice and filter determining applicability
128
public interface Advisor {
129
Advice getAdvice();
130
boolean isPerInstance();
131
}
132
133
// Superinterface for all Advisors driven by Pointcut
134
public interface PointcutAdvisor extends Advisor {
135
Pointcut getPointcut();
136
}
137
```
138
139
### Proxy Creation
140
141
```java { .api }
142
// Delegate interface for AOP proxy creation
143
public interface AopProxy {
144
Object getProxy();
145
Object getProxy(ClassLoader classLoader);
146
}
147
148
// Factory for AOP proxies for programmatic use
149
public class ProxyFactory extends AdvisedSupport {
150
public ProxyFactory();
151
public ProxyFactory(Object target);
152
public ProxyFactory(Class<?>... proxyInterfaces);
153
public ProxyFactory(Class<?> proxyInterface, Interceptor interceptor);
154
155
public Object getProxy();
156
public Object getProxy(ClassLoader classLoader);
157
158
// Static convenience methods
159
public static <T> T getProxy(Class<T> proxyInterface, Interceptor interceptor);
160
public static <T> T getProxy(Class<T> proxyInterface, TargetSource targetSource);
161
}
162
163
// AspectJ-based extension of ProxyFactory
164
public class AspectJProxyFactory extends ProxyCreatorSupport {
165
public AspectJProxyFactory();
166
public AspectJProxyFactory(Object target);
167
168
public void addAspect(Object aspectInstance);
169
public void addAspect(Class<?> aspectClass);
170
public <T> T getProxy();
171
public <T> T getProxy(ClassLoader classLoader);
172
}
173
```
174
175
## AspectJ Integration
176
177
### Enabling AspectJ Support
178
179
```java { .api }
180
// Enables support for handling components marked with AspectJ's @Aspect annotation
181
@Target(ElementType.TYPE)
182
@Retention(RetentionPolicy.RUNTIME)
183
@Documented
184
@Import(AspectJAutoProxyRegistrar.class)
185
public @interface EnableAspectJAutoProxy {
186
boolean proxyTargetClass() default false;
187
boolean exposeProxy() default false;
188
}
189
190
// Configuration class enabling AspectJ auto proxy
191
@Configuration
192
@EnableAspectJAutoProxy
193
public class AopConfig {
194
// AspectJ aspects will be automatically detected and applied
195
}
196
```
197
198
### AspectJ Annotations
199
200
```java { .api }
201
// Marks a class as an aspect
202
@Retention(RetentionPolicy.RUNTIME)
203
@Target(ElementType.TYPE)
204
public @interface Aspect {
205
String value() default "";
206
}
207
208
// Declares a pointcut
209
@Retention(RetentionPolicy.RUNTIME)
210
@Target(ElementType.METHOD)
211
public @interface Pointcut {
212
String value() default "";
213
String argNames() default "";
214
}
215
216
// Before advice
217
@Retention(RetentionPolicy.RUNTIME)
218
@Target(ElementType.METHOD)
219
public @interface Before {
220
String value();
221
String argNames() default "";
222
}
223
224
// After returning advice
225
@Retention(RetentionPolicy.RUNTIME)
226
@Target(ElementType.METHOD)
227
public @interface AfterReturning {
228
String value() default "";
229
String pointcut() default "";
230
String returning() default "";
231
String argNames() default "";
232
}
233
234
// After throwing advice
235
@Retention(RetentionPolicy.RUNTIME)
236
@Target(ElementType.METHOD)
237
public @interface AfterThrowing {
238
String value() default "";
239
String pointcut() default "";
240
String throwing() default "";
241
String argNames() default "";
242
}
243
244
// After (finally) advice
245
@Retention(RetentionPolicy.RUNTIME)
246
@Target(ElementType.METHOD)
247
public @interface After {
248
String value();
249
String argNames() default "";
250
}
251
252
// Around advice
253
@Retention(RetentionPolicy.RUNTIME)
254
@Target(ElementType.METHOD)
255
public @interface Around {
256
String value();
257
String argNames() default "";
258
}
259
```
260
261
### Join Points
262
263
```java { .api }
264
// Runtime context for advice execution
265
public interface JoinPoint {
266
String toString();
267
String toShortString();
268
String toLongString();
269
Object getThis();
270
Object getTarget();
271
Object[] getArgs();
272
Signature getSignature();
273
SourceLocation getSourceLocation();
274
String getKind();
275
StaticPart getStaticPart();
276
}
277
278
// For around advice that can control method execution
279
public interface ProceedingJoinPoint extends JoinPoint {
280
Object proceed() throws Throwable;
281
Object proceed(Object[] args) throws Throwable;
282
}
283
284
// Signature information
285
public interface Signature {
286
String toString();
287
String toShortString();
288
String toLongString();
289
String getName();
290
int getModifiers();
291
Class<?> getDeclaringType();
292
String getDeclaringTypeName();
293
}
294
295
public interface MethodSignature extends CodeSignature {
296
Class<?> getReturnType();
297
Method getMethod();
298
}
299
```
300
301
## Practical Usage Examples
302
303
### Basic AspectJ Aspects
304
305
```java { .api }
306
// Logging aspect
307
@Aspect
308
@Component
309
public class LoggingAspect {
310
311
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
312
313
// Pointcut for all service methods
314
@Pointcut("execution(* com.example.service.*.*(..))")
315
public void serviceLayer() {}
316
317
// Pointcut for all repository methods
318
@Pointcut("execution(* com.example.repository.*.*(..))")
319
public void repositoryLayer() {}
320
321
// Combined pointcut
322
@Pointcut("serviceLayer() || repositoryLayer()")
323
public void businessLayer() {}
324
325
// Before advice
326
@Before("businessLayer()")
327
public void logBefore(JoinPoint joinPoint) {
328
logger.info("Calling method: {}.{}() with args: {}",
329
joinPoint.getTarget().getClass().getSimpleName(),
330
joinPoint.getSignature().getName(),
331
Arrays.toString(joinPoint.getArgs()));
332
}
333
334
// After returning advice
335
@AfterReturning(pointcut = "businessLayer()", returning = "result")
336
public void logAfterReturning(JoinPoint joinPoint, Object result) {
337
logger.info("Method {}.{}() returned: {}",
338
joinPoint.getTarget().getClass().getSimpleName(),
339
joinPoint.getSignature().getName(),
340
result);
341
}
342
343
// After throwing advice
344
@AfterThrowing(pointcut = "businessLayer()", throwing = "exception")
345
public void logAfterThrowing(JoinPoint joinPoint, Exception exception) {
346
logger.error("Method {}.{}() threw exception: {}",
347
joinPoint.getTarget().getClass().getSimpleName(),
348
joinPoint.getSignature().getName(),
349
exception.getMessage(), exception);
350
}
351
}
352
```
353
354
### Performance Monitoring Aspect
355
356
```java { .api }
357
@Aspect
358
@Component
359
public class PerformanceAspect {
360
361
private static final Logger logger = LoggerFactory.getLogger(PerformanceAspect.class);
362
363
@Around("@annotation(Timed)")
364
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
365
long startTime = System.currentTimeMillis();
366
367
try {
368
Object result = joinPoint.proceed();
369
return result;
370
} finally {
371
long endTime = System.currentTimeMillis();
372
long executionTime = endTime - startTime;
373
374
logger.info("Method {}.{}() executed in {} ms",
375
joinPoint.getTarget().getClass().getSimpleName(),
376
joinPoint.getSignature().getName(),
377
executionTime);
378
}
379
}
380
381
@Around("execution(* com.example.service.*.*(..))")
382
public Object monitorServiceMethods(ProceedingJoinPoint joinPoint) throws Throwable {
383
String methodName = joinPoint.getSignature().getName();
384
StopWatch stopWatch = new StopWatch();
385
386
stopWatch.start();
387
try {
388
Object result = joinPoint.proceed();
389
return result;
390
} finally {
391
stopWatch.stop();
392
logger.debug("Service method {} took {} ms", methodName, stopWatch.getTotalTimeMillis());
393
}
394
}
395
}
396
397
// Custom annotation for marking methods to be timed
398
@Target(ElementType.METHOD)
399
@Retention(RetentionPolicy.RUNTIME)
400
public @interface Timed {
401
String value() default "";
402
}
403
```
404
405
### Security Aspect
406
407
```java { .api }
408
@Aspect
409
@Component
410
public class SecurityAspect {
411
412
@Autowired
413
private SecurityService securityService;
414
415
@Before("@annotation(requiresRole)")
416
public void checkRole(JoinPoint joinPoint, RequiresRole requiresRole) {
417
String[] requiredRoles = requiresRole.value();
418
419
if (!securityService.hasAnyRole(requiredRoles)) {
420
throw new SecurityException("Access denied. Required roles: " +
421
Arrays.toString(requiredRoles));
422
}
423
}
424
425
@Around("@annotation(requiresPermission)")
426
public Object checkPermission(ProceedingJoinPoint joinPoint, RequiresPermission requiresPermission) throws Throwable {
427
String permission = requiresPermission.value();
428
429
if (!securityService.hasPermission(permission)) {
430
throw new SecurityException("Access denied. Required permission: " + permission);
431
}
432
433
return joinPoint.proceed();
434
}
435
}
436
437
// Security annotations
438
@Target(ElementType.METHOD)
439
@Retention(RetentionPolicy.RUNTIME)
440
public @interface RequiresRole {
441
String[] value();
442
}
443
444
@Target(ElementType.METHOD)
445
@Retention(RetentionPolicy.RUNTIME)
446
public @interface RequiresPermission {
447
String value();
448
}
449
450
// Usage in service classes
451
@Service
452
public class UserService {
453
454
@RequiresRole({"ADMIN", "USER_MANAGER"})
455
public User createUser(User user) {
456
// Create user logic
457
return user;
458
}
459
460
@RequiresPermission("user:delete")
461
public void deleteUser(Long userId) {
462
// Delete user logic
463
}
464
}
465
```
466
467
### Caching Aspect
468
469
```java { .api }
470
@Aspect
471
@Component
472
public class CachingAspect {
473
474
private final CacheManager cacheManager;
475
476
public CachingAspect(CacheManager cacheManager) {
477
this.cacheManager = cacheManager;
478
}
479
480
@Around("@annotation(cacheable)")
481
public Object cache(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
482
String cacheName = cacheable.value();
483
String key = generateKey(joinPoint);
484
485
// Check cache first
486
Object cached = cacheManager.get(cacheName, key);
487
if (cached != null) {
488
return cached;
489
}
490
491
// Execute method and cache result
492
Object result = joinPoint.proceed();
493
cacheManager.put(cacheName, key, result);
494
495
return result;
496
}
497
498
@After("@annotation(cacheEvict)")
499
public void evictCache(JoinPoint joinPoint, CacheEvict cacheEvict) {
500
String cacheName = cacheEvict.value();
501
if (cacheEvict.allEntries()) {
502
cacheManager.clear(cacheName);
503
} else {
504
String key = generateKey(joinPoint);
505
cacheManager.evict(cacheName, key);
506
}
507
}
508
509
private String generateKey(JoinPoint joinPoint) {
510
StringBuilder keyBuilder = new StringBuilder();
511
keyBuilder.append(joinPoint.getSignature().getName());
512
513
for (Object arg : joinPoint.getArgs()) {
514
keyBuilder.append(":").append(arg != null ? arg.toString() : "null");
515
}
516
517
return keyBuilder.toString();
518
}
519
}
520
521
@Target(ElementType.METHOD)
522
@Retention(RetentionPolicy.RUNTIME)
523
public @interface Cacheable {
524
String value();
525
}
526
527
@Target(ElementType.METHOD)
528
@Retention(RetentionPolicy.RUNTIME)
529
public @interface CacheEvict {
530
String value();
531
boolean allEntries() default false;
532
}
533
```
534
535
### Programmatic AOP with ProxyFactory
536
537
```java { .api }
538
// Using ProxyFactory directly for programmatic AOP
539
public class AopExample {
540
541
public void demonstrateProxyFactory() {
542
// Create target object
543
UserService target = new UserServiceImpl();
544
545
// Create proxy factory
546
ProxyFactory proxyFactory = new ProxyFactory(target);
547
548
// Add advice
549
proxyFactory.addAdvice(new MethodInterceptor() {
550
@Override
551
public Object invoke(MethodInvocation invocation) throws Throwable {
552
System.out.println("Before method: " + invocation.getMethod().getName());
553
554
try {
555
Object result = invocation.proceed();
556
System.out.println("After method: " + invocation.getMethod().getName());
557
return result;
558
} catch (Exception e) {
559
System.out.println("Exception in method: " + invocation.getMethod().getName());
560
throw e;
561
}
562
}
563
});
564
565
// Get proxy
566
UserService proxy = (UserService) proxyFactory.getProxy();
567
568
// Use proxy - advice will be applied
569
User user = proxy.findById(1L);
570
}
571
}
572
573
// Method-specific interception
574
public class ValidationInterceptor implements MethodInterceptor {
575
576
@Override
577
public Object invoke(MethodInvocation invocation) throws Throwable {
578
Method method = invocation.getMethod();
579
Object[] args = invocation.getArguments();
580
581
// Validate arguments before method execution
582
for (Object arg : args) {
583
if (arg == null) {
584
throw new IllegalArgumentException("Null argument not allowed for method: " + method.getName());
585
}
586
}
587
588
// Proceed with method execution
589
return invocation.proceed();
590
}
591
}
592
593
// Using specific pointcuts
594
public class ServicePointcutAdvisor extends DefaultPointcutAdvisor {
595
596
public ServicePointcutAdvisor() {
597
// Create pointcut that matches all methods in service classes
598
Pointcut pointcut = new StaticMethodMatcherPointcut() {
599
@Override
600
public boolean matches(Method method, Class<?> targetClass) {
601
return targetClass.getSimpleName().endsWith("Service");
602
}
603
};
604
605
setPointcut(pointcut);
606
setAdvice(new LoggingInterceptor());
607
}
608
}
609
```
610
611
### Advanced Pointcut Expressions
612
613
```java { .api }
614
@Aspect
615
@Component
616
public class AdvancedPointcutAspect {
617
618
// Method execution pointcuts
619
@Pointcut("execution(public * com.example..*(..))")
620
public void publicMethods() {}
621
622
@Pointcut("execution(* com.example.service.*.save*(..))")
623
public void saveMethods() {}
624
625
@Pointcut("execution(* com.example.repository.*Repository.find*(..))")
626
public void findMethods() {}
627
628
// Annotation-based pointcuts
629
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
630
public void transactionalMethods() {}
631
632
@Pointcut("@within(org.springframework.stereotype.Service)")
633
public void serviceClasses() {}
634
635
// Type-based pointcuts
636
@Pointcut("within(com.example.service..*)")
637
public void withinServicePackage() {}
638
639
@Pointcut("target(com.example.service.UserService)")
640
public void userServiceTarget() {}
641
642
// Argument-based pointcuts
643
@Pointcut("args(java.lang.String, ..)")
644
public void methodsWithStringFirstArg() {}
645
646
@Pointcut("args(userId) && execution(* com.example.service.*.*(..))")
647
public void serviceMethodsWithUserId(Long userId) {}
648
649
// Bean pointcuts
650
@Pointcut("bean(*Service)")
651
public void serviceBeans() {}
652
653
@Pointcut("bean(userService)")
654
public void userServiceBean() {}
655
656
// Complex combinations
657
@Around("(serviceClasses() || repositoryClasses()) && publicMethods() && !execution(* toString())")
658
public Object aroundBusinessMethods(ProceedingJoinPoint joinPoint) throws Throwable {
659
// Complex advice logic
660
return joinPoint.proceed();
661
}
662
663
// Pointcut with argument binding
664
@Before("execution(* com.example.service.*.update*(..)) && args(entity)")
665
public void beforeUpdate(Object entity) {
666
System.out.println("Updating entity: " + entity);
667
}
668
669
// Multiple argument binding
670
@Before("execution(* com.example.service.UserService.updateUser(..)) && args(userId, userData)")
671
public void beforeUserUpdate(Long userId, UserData userData) {
672
System.out.println("Updating user " + userId + " with data: " + userData);
673
}
674
}
675
```
676
677
### Integration with Spring Features
678
679
```java { .api }
680
// AOP with Spring Boot auto-configuration
681
@SpringBootApplication
682
@EnableAspectJAutoProxy
683
public class Application {
684
public static void main(String[] args) {
685
SpringApplication.run(Application.class, args);
686
}
687
}
688
689
// Conditional aspects based on properties
690
@Aspect
691
@Component
692
@ConditionalOnProperty(name = "app.audit.enabled", havingValue = "true")
693
public class AuditAspect {
694
695
@AfterReturning(pointcut = "@annotation(Auditable)", returning = "result")
696
public void auditMethod(JoinPoint joinPoint, Object result) {
697
// Audit logic
698
}
699
}
700
701
// Profile-specific aspects
702
@Aspect
703
@Component
704
@Profile("development")
705
public class DebugAspect {
706
707
@Around("execution(* com.example..*(..))")
708
public Object debugMethod(ProceedingJoinPoint joinPoint) throws Throwable {
709
System.out.println("DEBUG: Entering " + joinPoint.getSignature().getName());
710
Object result = joinPoint.proceed();
711
System.out.println("DEBUG: Exiting " + joinPoint.getSignature().getName());
712
return result;
713
}
714
}
715
716
// Integration with other Spring features
717
@Aspect
718
@Component
719
public class IntegrationAspect {
720
721
@Autowired
722
private ApplicationEventPublisher eventPublisher;
723
724
@Autowired
725
private MeterRegistry meterRegistry;
726
727
@AfterReturning("execution(* com.example.service.OrderService.createOrder(..))")
728
public void afterOrderCreated(JoinPoint joinPoint) {
729
// Publish application event
730
eventPublisher.publishEvent(new OrderCreatedEvent(this, "Order created"));
731
732
// Update metrics
733
meterRegistry.counter("orders.created").increment();
734
}
735
}
736
```
737
738
## Configuration and Best Practices
739
740
### XML Configuration (Alternative)
741
742
```xml
743
<!-- Enable AspectJ auto proxy in XML -->
744
<aop:aspectj-autoproxy proxy-target-class="true"/>
745
746
<!-- Register aspect as bean -->
747
<bean id="loggingAspect" class="com.example.LoggingAspect"/>
748
749
<!-- Programmatic AOP configuration -->
750
<aop:config>
751
<aop:aspect ref="loggingAspect">
752
<aop:pointcut id="businessMethods"
753
expression="execution(* com.example.service.*.*(..))"/>
754
<aop:before method="logBefore" pointcut-ref="businessMethods"/>
755
<aop:after-returning method="logAfterReturning"
756
pointcut-ref="businessMethods"
757
returning="result"/>
758
</aop:aspect>
759
</aop:config>
760
```
761
762
### Performance Considerations
763
764
```java { .api }
765
// Optimize pointcut expressions for better performance
766
@Aspect
767
@Component
768
public class OptimizedAspect {
769
770
// More specific pointcuts perform better
771
@Pointcut("execution(* com.example.service.UserService.*(..))") // Good
772
public void userServiceMethods() {}
773
774
@Pointcut("execution(* *.*(..))") // Avoid - too broad
775
public void allMethods() {}
776
777
// Use within() for better performance when targeting packages
778
@Pointcut("within(com.example.service..*)")
779
public void servicePackage() {}
780
781
// Combine pointcuts efficiently
782
@Pointcut("servicePackage() && execution(public * save*(..))")
783
public void publicSaveMethods() {}
784
}
785
```
786
787
Spring AOP provides a powerful and flexible way to implement cross-cutting concerns in your applications. It integrates seamlessly with the Spring IoC container and provides both annotation-driven and programmatic approaches to aspect-oriented programming.