Comprehensive application framework and inversion of control container for the Java platform providing dependency injection, AOP, data access, transaction management, and web framework capabilities
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.
<!-- Spring AOP -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.39</version>
</dependency>
<!-- AspectJ for advanced AOP features -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<!-- Enable AspectJ support -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.39</version>
</dependency>// Core AOP interfaces
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.ThrowsAdvice;
// Pointcut and Advisor interfaces
import org.springframework.aop.Pointcut;
import org.springframework.aop.Advisor;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
// Proxy creation
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.framework.AopProxy;
import org.springframework.aop.aspectj.AspectJProxyFactory;
// AspectJ annotations
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
// Configuration
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.aop.config.AopConfigUtils;// Tag interface for all advice types
public interface Advice {
}
// Method interceptor that sits around method invocations
@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
// Advice invoked before method execution
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method method, Object[] args, Object target) throws Throwable;
}
// Advice invoked after successful method execution
public interface AfterReturningAdvice extends AfterAdvice {
void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
}
// Advice invoked after method throws exception
public interface ThrowsAdvice extends AfterAdvice {
// Marker interface - implementations should have methods like:
// public void afterThrowing(Exception ex)
// public void afterThrowing(Method method, Object[] args, Object target, Exception ex)
}// Core interface expressing where advice should be applied
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
Pointcut TRUE = TruePointcut.INSTANCE;
}
// Interface for matching classes
@FunctionalInterface
public interface ClassFilter {
boolean matches(Class<?> clazz);
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
// Interface for matching methods
public interface MethodMatcher {
boolean matches(Method method, Class<?> targetClass);
boolean isRuntime();
boolean matches(Method method, Class<?> targetClass, Object... args);
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
// Base interface holding advice and filter determining applicability
public interface Advisor {
Advice getAdvice();
boolean isPerInstance();
}
// Superinterface for all Advisors driven by Pointcut
public interface PointcutAdvisor extends Advisor {
Pointcut getPointcut();
}// Delegate interface for AOP proxy creation
public interface AopProxy {
Object getProxy();
Object getProxy(ClassLoader classLoader);
}
// Factory for AOP proxies for programmatic use
public class ProxyFactory extends AdvisedSupport {
public ProxyFactory();
public ProxyFactory(Object target);
public ProxyFactory(Class<?>... proxyInterfaces);
public ProxyFactory(Class<?> proxyInterface, Interceptor interceptor);
public Object getProxy();
public Object getProxy(ClassLoader classLoader);
// Static convenience methods
public static <T> T getProxy(Class<T> proxyInterface, Interceptor interceptor);
public static <T> T getProxy(Class<T> proxyInterface, TargetSource targetSource);
}
// AspectJ-based extension of ProxyFactory
public class AspectJProxyFactory extends ProxyCreatorSupport {
public AspectJProxyFactory();
public AspectJProxyFactory(Object target);
public void addAspect(Object aspectInstance);
public void addAspect(Class<?> aspectClass);
public <T> T getProxy();
public <T> T getProxy(ClassLoader classLoader);
}// Enables support for handling components marked with AspectJ's @Aspect annotation
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
// Configuration class enabling AspectJ auto proxy
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
// AspectJ aspects will be automatically detected and applied
}// Marks a class as an aspect
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Aspect {
String value() default "";
}
// Declares a pointcut
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Pointcut {
String value() default "";
String argNames() default "";
}
// Before advice
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Before {
String value();
String argNames() default "";
}
// After returning advice
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterReturning {
String value() default "";
String pointcut() default "";
String returning() default "";
String argNames() default "";
}
// After throwing advice
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterThrowing {
String value() default "";
String pointcut() default "";
String throwing() default "";
String argNames() default "";
}
// After (finally) advice
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface After {
String value();
String argNames() default "";
}
// Around advice
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Around {
String value();
String argNames() default "";
}// Runtime context for advice execution
public interface JoinPoint {
String toString();
String toShortString();
String toLongString();
Object getThis();
Object getTarget();
Object[] getArgs();
Signature getSignature();
SourceLocation getSourceLocation();
String getKind();
StaticPart getStaticPart();
}
// For around advice that can control method execution
public interface ProceedingJoinPoint extends JoinPoint {
Object proceed() throws Throwable;
Object proceed(Object[] args) throws Throwable;
}
// Signature information
public interface Signature {
String toString();
String toShortString();
String toLongString();
String getName();
int getModifiers();
Class<?> getDeclaringType();
String getDeclaringTypeName();
}
public interface MethodSignature extends CodeSignature {
Class<?> getReturnType();
Method getMethod();
}// Logging aspect
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
// Pointcut for all service methods
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
// Pointcut for all repository methods
@Pointcut("execution(* com.example.repository.*.*(..))")
public void repositoryLayer() {}
// Combined pointcut
@Pointcut("serviceLayer() || repositoryLayer()")
public void businessLayer() {}
// Before advice
@Before("businessLayer()")
public void logBefore(JoinPoint joinPoint) {
logger.info("Calling method: {}.{}() with args: {}",
joinPoint.getTarget().getClass().getSimpleName(),
joinPoint.getSignature().getName(),
Arrays.toString(joinPoint.getArgs()));
}
// After returning advice
@AfterReturning(pointcut = "businessLayer()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
logger.info("Method {}.{}() returned: {}",
joinPoint.getTarget().getClass().getSimpleName(),
joinPoint.getSignature().getName(),
result);
}
// After throwing advice
@AfterThrowing(pointcut = "businessLayer()", throwing = "exception")
public void logAfterThrowing(JoinPoint joinPoint, Exception exception) {
logger.error("Method {}.{}() threw exception: {}",
joinPoint.getTarget().getClass().getSimpleName(),
joinPoint.getSignature().getName(),
exception.getMessage(), exception);
}
}@Aspect
@Component
public class PerformanceAspect {
private static final Logger logger = LoggerFactory.getLogger(PerformanceAspect.class);
@Around("@annotation(Timed)")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
return result;
} finally {
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
logger.info("Method {}.{}() executed in {} ms",
joinPoint.getTarget().getClass().getSimpleName(),
joinPoint.getSignature().getName(),
executionTime);
}
}
@Around("execution(* com.example.service.*.*(..))")
public Object monitorServiceMethods(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
try {
Object result = joinPoint.proceed();
return result;
} finally {
stopWatch.stop();
logger.debug("Service method {} took {} ms", methodName, stopWatch.getTotalTimeMillis());
}
}
}
// Custom annotation for marking methods to be timed
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timed {
String value() default "";
}@Aspect
@Component
public class SecurityAspect {
@Autowired
private SecurityService securityService;
@Before("@annotation(requiresRole)")
public void checkRole(JoinPoint joinPoint, RequiresRole requiresRole) {
String[] requiredRoles = requiresRole.value();
if (!securityService.hasAnyRole(requiredRoles)) {
throw new SecurityException("Access denied. Required roles: " +
Arrays.toString(requiredRoles));
}
}
@Around("@annotation(requiresPermission)")
public Object checkPermission(ProceedingJoinPoint joinPoint, RequiresPermission requiresPermission) throws Throwable {
String permission = requiresPermission.value();
if (!securityService.hasPermission(permission)) {
throw new SecurityException("Access denied. Required permission: " + permission);
}
return joinPoint.proceed();
}
}
// Security annotations
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresRole {
String[] value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
String value();
}
// Usage in service classes
@Service
public class UserService {
@RequiresRole({"ADMIN", "USER_MANAGER"})
public User createUser(User user) {
// Create user logic
return user;
}
@RequiresPermission("user:delete")
public void deleteUser(Long userId) {
// Delete user logic
}
}@Aspect
@Component
public class CachingAspect {
private final CacheManager cacheManager;
public CachingAspect(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
@Around("@annotation(cacheable)")
public Object cache(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
String cacheName = cacheable.value();
String key = generateKey(joinPoint);
// Check cache first
Object cached = cacheManager.get(cacheName, key);
if (cached != null) {
return cached;
}
// Execute method and cache result
Object result = joinPoint.proceed();
cacheManager.put(cacheName, key, result);
return result;
}
@After("@annotation(cacheEvict)")
public void evictCache(JoinPoint joinPoint, CacheEvict cacheEvict) {
String cacheName = cacheEvict.value();
if (cacheEvict.allEntries()) {
cacheManager.clear(cacheName);
} else {
String key = generateKey(joinPoint);
cacheManager.evict(cacheName, key);
}
}
private String generateKey(JoinPoint joinPoint) {
StringBuilder keyBuilder = new StringBuilder();
keyBuilder.append(joinPoint.getSignature().getName());
for (Object arg : joinPoint.getArgs()) {
keyBuilder.append(":").append(arg != null ? arg.toString() : "null");
}
return keyBuilder.toString();
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable {
String value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheEvict {
String value();
boolean allEntries() default false;
}// Using ProxyFactory directly for programmatic AOP
public class AopExample {
public void demonstrateProxyFactory() {
// Create target object
UserService target = new UserServiceImpl();
// Create proxy factory
ProxyFactory proxyFactory = new ProxyFactory(target);
// Add advice
proxyFactory.addAdvice(new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Before method: " + invocation.getMethod().getName());
try {
Object result = invocation.proceed();
System.out.println("After method: " + invocation.getMethod().getName());
return result;
} catch (Exception e) {
System.out.println("Exception in method: " + invocation.getMethod().getName());
throw e;
}
}
});
// Get proxy
UserService proxy = (UserService) proxyFactory.getProxy();
// Use proxy - advice will be applied
User user = proxy.findById(1L);
}
}
// Method-specific interception
public class ValidationInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
Object[] args = invocation.getArguments();
// Validate arguments before method execution
for (Object arg : args) {
if (arg == null) {
throw new IllegalArgumentException("Null argument not allowed for method: " + method.getName());
}
}
// Proceed with method execution
return invocation.proceed();
}
}
// Using specific pointcuts
public class ServicePointcutAdvisor extends DefaultPointcutAdvisor {
public ServicePointcutAdvisor() {
// Create pointcut that matches all methods in service classes
Pointcut pointcut = new StaticMethodMatcherPointcut() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return targetClass.getSimpleName().endsWith("Service");
}
};
setPointcut(pointcut);
setAdvice(new LoggingInterceptor());
}
}@Aspect
@Component
public class AdvancedPointcutAspect {
// Method execution pointcuts
@Pointcut("execution(public * com.example..*(..))")
public void publicMethods() {}
@Pointcut("execution(* com.example.service.*.save*(..))")
public void saveMethods() {}
@Pointcut("execution(* com.example.repository.*Repository.find*(..))")
public void findMethods() {}
// Annotation-based pointcuts
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethods() {}
@Pointcut("@within(org.springframework.stereotype.Service)")
public void serviceClasses() {}
// Type-based pointcuts
@Pointcut("within(com.example.service..*)")
public void withinServicePackage() {}
@Pointcut("target(com.example.service.UserService)")
public void userServiceTarget() {}
// Argument-based pointcuts
@Pointcut("args(java.lang.String, ..)")
public void methodsWithStringFirstArg() {}
@Pointcut("args(userId) && execution(* com.example.service.*.*(..))")
public void serviceMethodsWithUserId(Long userId) {}
// Bean pointcuts
@Pointcut("bean(*Service)")
public void serviceBeans() {}
@Pointcut("bean(userService)")
public void userServiceBean() {}
// Complex combinations
@Around("(serviceClasses() || repositoryClasses()) && publicMethods() && !execution(* toString())")
public Object aroundBusinessMethods(ProceedingJoinPoint joinPoint) throws Throwable {
// Complex advice logic
return joinPoint.proceed();
}
// Pointcut with argument binding
@Before("execution(* com.example.service.*.update*(..)) && args(entity)")
public void beforeUpdate(Object entity) {
System.out.println("Updating entity: " + entity);
}
// Multiple argument binding
@Before("execution(* com.example.service.UserService.updateUser(..)) && args(userId, userData)")
public void beforeUserUpdate(Long userId, UserData userData) {
System.out.println("Updating user " + userId + " with data: " + userData);
}
}// AOP with Spring Boot auto-configuration
@SpringBootApplication
@EnableAspectJAutoProxy
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// Conditional aspects based on properties
@Aspect
@Component
@ConditionalOnProperty(name = "app.audit.enabled", havingValue = "true")
public class AuditAspect {
@AfterReturning(pointcut = "@annotation(Auditable)", returning = "result")
public void auditMethod(JoinPoint joinPoint, Object result) {
// Audit logic
}
}
// Profile-specific aspects
@Aspect
@Component
@Profile("development")
public class DebugAspect {
@Around("execution(* com.example..*(..))")
public Object debugMethod(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("DEBUG: Entering " + joinPoint.getSignature().getName());
Object result = joinPoint.proceed();
System.out.println("DEBUG: Exiting " + joinPoint.getSignature().getName());
return result;
}
}
// Integration with other Spring features
@Aspect
@Component
public class IntegrationAspect {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
private MeterRegistry meterRegistry;
@AfterReturning("execution(* com.example.service.OrderService.createOrder(..))")
public void afterOrderCreated(JoinPoint joinPoint) {
// Publish application event
eventPublisher.publishEvent(new OrderCreatedEvent(this, "Order created"));
// Update metrics
meterRegistry.counter("orders.created").increment();
}
}<!-- Enable AspectJ auto proxy in XML -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- Register aspect as bean -->
<bean id="loggingAspect" class="com.example.LoggingAspect"/>
<!-- Programmatic AOP configuration -->
<aop:config>
<aop:aspect ref="loggingAspect">
<aop:pointcut id="businessMethods"
expression="execution(* com.example.service.*.*(..))"/>
<aop:before method="logBefore" pointcut-ref="businessMethods"/>
<aop:after-returning method="logAfterReturning"
pointcut-ref="businessMethods"
returning="result"/>
</aop:aspect>
</aop:config>// Optimize pointcut expressions for better performance
@Aspect
@Component
public class OptimizedAspect {
// More specific pointcuts perform better
@Pointcut("execution(* com.example.service.UserService.*(..))") // Good
public void userServiceMethods() {}
@Pointcut("execution(* *.*(..))") // Avoid - too broad
public void allMethods() {}
// Use within() for better performance when targeting packages
@Pointcut("within(com.example.service..*)")
public void servicePackage() {}
// Combine pointcuts efficiently
@Pointcut("servicePackage() && execution(public * save*(..))")
public void publicSaveMethods() {}
}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.
Install with Tessl CLI
npx tessl i tessl/maven-org-springframework--spring