Master Spring AOP: Core Concepts, Detailed Walkthrough, and Real‑World Logging
This article explains Spring AOP fundamentals—including aspects, advice types, pointcuts, join points, and advice parameters—provides detailed code examples for each component, outlines the AOP workflow, showcases common use cases, and demonstrates a complete logging implementation with practical code snippets.
Interview Answer
Aspect : An aspect modularizes cross‑cutting concerns and consists of Advice and Pointcut, which are the basic units of AOP.
Advice : Advice defines the action to execute at a specific join point. Spring supports five advice types: @Before, @After, @AfterReturning, @AfterThrowing, and @Around.
Pointcut : A pointcut is an expression that matches join points, determining where advice runs. Spring uses AspectJ expression language to define pointcuts.
JoinPoint : A join point is a point in program execution where an aspect can be inserted, such as method invocation or exception throwing. In Spring AOP, join points are always method execution points.
Advice parameters : By using the JoinPoint object, an advice method can obtain method signatures, arguments, target objects, etc., and can also bind parameters directly via pointcut expressions or annotations.
AOP’s core concept revolves around "where (Pointcut) to execute what (Advice)" and encapsulates these in an Aspect . Through JoinPoint and parameter binding, developers can access method context, enhancing modularity and maintainability.
Detailed Analysis
1. Aspect
An aspect is the core modular unit of AOP, encapsulating cross‑cutting concerns across multiple classes.
In Spring, an aspect is defined with the @Aspect annotation and contains the logic to be enhanced.
package com.qy.aop;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
// Here you would define Pointcut and Advice
}The responsibility of an aspect is to combine a Pointcut (where to cut in) with an Advice (what to do).
2. Advice
Advice defines the action that an aspect performs at a specific join point. Spring supports five types:
package com.qy.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
// Before advice: executed before the method
@Before("execution(* com.qy.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("前置通知:准备执行方法 " + joinPoint.getSignature().getName());
}
// After advice: executed after the method (regardless of outcome)
@After("execution(* com.qy.service.*.*(..))")
public void afterAdvice(JoinPoint joinPoint) {
System.out.println("后置通知:方法 " + joinPoint.getSignature().getName() + " 已执行完毕");
}
// After returning advice: executed after normal return
@AfterReturning(pointcut = "execution(* com.qy.service.*.*(..))", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
System.out.println("返回通知:方法 " + joinPoint.getSignature().getName() + " 返回值: " + result);
}
// After throwing advice: executed after an exception is thrown
@AfterThrowing(pointcut = "execution(* com.qy.service.*.*(..))", throwing = "ex")
public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
System.out.println("异常通知:方法 " + joinPoint.getSignature().getName() + " 抛出异常: " + ex.getMessage());
}
// Around advice: fully controls method execution
@Around("execution(* com.qy.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知开始:准备调用方法 " + joinPoint.getSignature().getName());
long startTime = System.currentTimeMillis();
Object result = null;
try {
result = joinPoint.proceed();
} catch (Exception e) {
System.out.println("环绕通知捕获异常: " + e.getMessage());
throw e;
} finally {
long endTime = System.currentTimeMillis();
System.out.println("环绕通知结束:方法执行耗时 " + (endTime - startTime) + "ms");
}
return result;
}
}@Before : suitable for parameter validation, permission checks, etc.
@After : suitable for resource release or mandatory cleanup.
@AfterReturning : suitable for processing or logging return values.
@AfterThrowing : suitable for exception handling and logging.
@Around : most powerful; can implement performance monitoring, transaction control, etc.
3. Pointcut
A pointcut is an expression that matches join points, deciding where advice runs. Spring uses AspectJ expression language.
package com.qy.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class SystemArchitecture {
// All methods in the service package
@Pointcut("execution(* com.qy.service.*.*(..))")
public void serviceLayer() {}
// All methods in the dao package
@Pointcut("execution(* com.qy.dao.*.*(..))")
public void dataAccessLayer() {}
// Combined pointcut: methods in service or dao packages
@Pointcut("serviceLayer() || dataAccessLayer()")
public void businessLogicLayer() {}
// Methods annotated with @Transactional
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethods() {}
}Common pointcut expressions: execution(* com.qy.service.*.*(..)): matches all methods in the service package. @annotation(com.qy.annotation.LogExecutionTime): matches methods annotated with a specific annotation. within(com.qy.service.*): matches all classes in the service package. this(com.qy.service.UserService): matches proxy objects implementing UserService. target(com.qy.service.UserService): matches target objects implementing UserService. args(java.lang.String,..): matches methods whose first argument is a String.
4. JoinPoint
A join point is a point in program execution where an aspect can be inserted. In Spring AOP, join points are always method execution points. The JoinPoint object provides several useful methods:
package com.qy.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
@Aspect
@Component
public class JoinPointDemoAspect {
@Before("execution(* com.qy.service.*.*(..))")
public void demonstrateJoinPoint(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Object target = joinPoint.getTarget();
Object[] args = joinPoint.getArgs();
System.out.println("目标类: " + target.getClass().getName());
System.out.println("方法名: " + method.getName());
System.out.println("参数列表: " + Arrays.toString(args));
System.out.println("方法修饰符: " + method.getModifiers());
System.out.println("方法返回类型: " + method.getReturnType().getName());
}
} getSignature(): obtain method signature. getTarget(): obtain the target object. getArgs(): obtain method arguments. getThis(): obtain the proxy object. getKind(): obtain the type of join point.
In around advice, ProceedingJoinPoint extends JoinPoint and adds the proceed() method to execute the original method.
5. Advice parameters
The advice method can receive parameters to increase flexibility:
package com.qy.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class ParameterDemoAspect {
// Get parameters via JoinPoint
@Before("execution(* com.qy.service.UserService.findById(Long))")
public void beforeFindById(JoinPoint joinPoint) {
Long userId = (Long) joinPoint.getArgs()[0];
System.out.println("查询用户ID: " + userId);
}
// Directly bind parameter
@Before("execution(* com.qy.service.UserService.findById(Long)) && args(userId)")
public void beforeFindByIdWithParam(Long userId) {
System.out.println("准备查询用户ID: " + userId);
}
// Bind annotation parameter
@Before("@annotation(audit)")
public void auditMethod(JoinPoint joinPoint, com.qy.annotation.Audit audit) {
System.out.println("审计记录: " + audit.operation());
System.out.println("操作方法: " + joinPoint.getSignature().getName());
}
}Parameter binding methods:
Obtain parameters through the JoinPoint object.
Bind parameters via args() in the pointcut expression.
Bind parameters via annotation attributes.
AOP Workflow
Define an aspect class containing pointcuts and advice.
During Spring container startup, beans annotated with @Aspect are identified.
Spring matches target bean methods according to pointcut expressions and creates proxy objects.
When a target method is invoked, the proxy intercepts the call and executes the relevant advice in order.
Practical Use Cases
Logging : record method calls, parameters, execution time, etc.
Transaction Management : declarative transaction control.
Security : permission checks and authentication.
Performance Monitoring : measure method execution duration.
Cache Handling : cache method results.
Exception Handling : unified exception processing and logging.
Retry Mechanism : automatic retry on failure.
Implement Logging with AOP
Below is a complete example of using Spring AOP to implement logging:
package com.qy.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component
public class LoggingAspect {
// Define pointcut: intercept all methods in the service package
@Pointcut("execution(* com.qy.service.*.*(..))")
public void serviceLog() {}
// Around advice: log before and after method execution
@Around("serviceLog()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Class<?> targetClass = joinPoint.getTarget().getClass();
Logger logger = LoggerFactory.getLogger(targetClass);
String methodName = signature.getName();
String className = targetClass.getSimpleName();
Object[] args = joinPoint.getArgs();
logger.info("开始执行: {}.{}, 参数: {}", className, methodName, Arrays.toString(args));
long startTime = System.currentTimeMillis();
Object result = null;
try {
result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
logger.info("方法执行成功: {}.{}, 耗时: {}ms, 返回值: {}", className, methodName, (endTime - startTime), result);
return result;
} catch (Exception e) {
long endTime = System.currentTimeMillis();
logger.error("方法执行异常: {}.{}, 耗时: {}ms, 异常信息: {}", className, methodName, (endTime - startTime), e.getMessage());
throw e;
}
}
}Service class:
package com.qy.service;
import com.qy.model.User;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public User getUserById(Long id) {
if (id <= 0) {
throw new IllegalArgumentException("用户ID必须大于0");
}
User user = new User();
user.setId(id);
user.setUsername("用户" + id);
user.setEmail("user" + id + "@qq.com");
return user;
}
public boolean updateUser(User user) {
System.out.println("更新用户信息: " + user);
return true;
}
}Entity class:
package com.qy.model;
public class User {
private Long id;
private String username;
private String email;
// getters and setters omitted
@Override
public String toString() {
return "User{id=" + id + ", username='" + username + "', email='" + email + "'}";
}
}Main application class:
package com.qy;
import com.qy.model.User;
import com.qy.service.UserService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class LoggingAopApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(LoggingAopApplication.class, args);
UserService userService = context.getBean(UserService.class);
try {
// Normal case
User user = userService.getUserById(1L);
System.out.println("获取到用户: " + user);
// Modify and update
user.setUsername("修改后的用户名");
userService.updateUser(user);
// Exception case
userService.getUserById(-1L);
} catch (Exception e) {
System.out.println("捕获到异常: " + e.getMessage());
}
}
}Spring configuration class:
package com.qy.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.qy")
public class AppConfig {}Sample log output when the application runs:
com.qy.service.UserService - 开始执行: UserService.getUserById,参数: [1]
com.qy.service.UserService - 方法执行成功: UserService.getUserById,耗时: 3ms,返回值: User{id=1, username='用户1', email='[email protected]'}
获取到用户: User{id=1, username='用户1', email='[email protected]'}
com.qy.service.UserService - 开始执行: UserService.updateUser,参数: [User{id=1, username='修改后的用户名', email='[email protected]'}]
更新用户信息: User{id=1, username='修改后的用户名', email='[email protected]'}
com.qy.service.UserService - 方法执行成功: UserService.updateUser,耗时: 1ms,返回值: true
com.qy.service.UserService - 开始执行: UserService.getUserById,参数: [-1]
com.qy.service.UserService - 方法执行异常: UserService.getUserById,耗时: 1ms,异常信息: 用户ID必须大于0
捕获到异常: 用户ID必须大于0Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Xuanwu Backend Tech Stack
Primarily covers fundamental Java concepts, mainstream frameworks, deep dives into underlying principles, and JVM internals.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
