在 Spring 面试中,"什么是 AOP?它有什么用?" 是一个高频且极具区分度的问题。很多同学只会背"面向切面编程",却说不清它到底解决了什么问题。
从一个真实业务流程出发,带你理解 AOP 的设计思想和实际价值
一、从业务视角看AOP
假设我们有一个电子商务网站,其中涉及到订单处理的服务。如果将整个业务流程垂直地来看,可以将其分为两类:
- 系统业务:包括权限认证🔒、事务管理、日志记录等通用功能。
- 应用业务:比如Controller层的具体逻辑,例如订单创建、库存更新等。
示例业务流程:
- 权限认证 - 确认用户是否有权限执行操作。
- 开启事务 - 保证数据一致性。
- 执行业务A - 比如扣款。
- 执行业务B - 发送电子发票。
- 结束事务或回滚 - 根据业务结果提交或撤销事务。
- 日志记录 - 记录此次操作的所有细节。
在这个例子中,权限认证、事务管理和日志记录都是贯穿整个业务流程的关键点,它们是典型的横切关注点。

🤔 二、为什么需要AOP?
如果我们不使用AOP,那么上述的每个业务方法都需要手动添加权限检查、事务控制和日志记录的代码。这样做会导致:
- 代码重复:相同的逻辑出现在多个地方。
- 耦合度高:业务逻辑与系统功能紧密耦合,难以维护。
通过AOP,我们可以将这些通用的系统功能 【横切关注点】提取出来,作为独立的模块进行管理,然后根据特定规则"织入"到业务逻辑中、
三、AOP 出场:把横切逻辑"织入"业务流程
AOP(Aspect-Oriented Programming,面向切面编程) 就是为了解决这个问题而生的!
它的核心思想是:
🎯 将横切关注点从业务逻辑中分离出来,通过"动态织入"的方式,在不修改源码的前提下,增强原有功能。
✅ AOP 核心概念:
AOP 的实现依赖于以下几个概念:
- 切面(Aspect):切面是横切关注点的模块化单元,它将通知和切点组合在一起,描述了在何处、何时和如何应用横切关注点。
- 切点(Pointcut):用于定义哪些连接点被切面关注,即切面要织入的具体位置。
- 连接点(Join Point):在程序执行过程中的某个特定点,例如方法调用、异常抛出等。
- 通知(Advice):切面在特定切点上执行的代码,包括在连接点之前、之后或周围执行的行为。
- 织入(Weaving):将切面应用到目标对象中的过程,可以在编译时、加载时或运行时进行。
🪄 常见通知类型:
@Before
:方法执行前@After
:方法执行后(无论是否异常)@AfterReturning
:方法成功返回后@AfterThrowing
:方法抛出异常后@Around
:环绕整个方法(最强大)
四、实战示例:用 AOP 记录用户操作日志
在 Spring 项目中,我们经常需要记录用户操作日志(如谁在什么时候执行了什么操作、耗时多久等)。如果在每个方法里手动写日志代码,会非常重复且难以维护。
这时,AOP(面向切面编程) 就派上用场了!下面是一个简化后的日志切面示例,清晰展示核心流程。
✅ 核心思路
- 使用自定义注解
@Log
标记需要记录日志的方法。 - 利用
@Around
环绕通知,在方法执行前后自动记录:- 执行时间
- 请求参数
- 返回结果
- 异常信息
- 异步保存日志,避免影响主流程性能。
简化版代码(保留核心)
定义日志注解
java
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/**
* 业务模块
*/
public BusinessModule businessModule();
/**
* 业务类型(新增、删除..)
*/
public ActionTypeEnum actionType();
/**
* 操作来源
*/
public RecordSource operatorSource() ;
/**
* 是否保存请求的参数
*/
public boolean isSaveRequestData();
}
定义切面
java
@Aspect
@Component
@Slf4j
public class LogAspect {
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
@Autowired
private LogApiRequestService logApiRequestService;
// 定义切入点:所有标注 @Log 的方法
@Pointcut("@annotation(com.xx.xxx.common.annotation.Log)")
public void logPointCut() {}
// 环绕通知:记录执行时间 & 日志
@Around("logPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = null;
Exception exception = null;
try {
result = joinPoint.proceed(); // 执行目标方法
return result;
} catch (Exception e) {
exception = e;
throw e;
} finally {
long costTime = System.currentTimeMillis() - startTime;
handleLog(joinPoint, exception, result, costTime);
}
}
// 处理日志保存
private void handleLog(JoinPoint joinPoint, Exception e, Object result, long costTime) {
try {
// 获取方法上的 @Log 注解
Log logAnnotation = getLogAnnotation(joinPoint);
if (logAnnotation == null) return;
// 模拟获取请求信息(IP、URL、参数等)
HttpServletRequest request = RequestUtil.getCurrentRequest();
String ip = ServletUtil.getClientIP(request);
String url = request.getRequestURL().toString();
String method = request.getMethod();
String params = getMethodParams(joinPoint, method);
String resultStr = JSON.toJSONString(result);
String errorMsg = e != null ? e.getMessage() : null;
int status = (e == null) ? 0 : 1;
// 构建日志对象
LogApiRequest log = new LogApiRequest();
log.setBusinessModule(logAnnotation.businessModule());
log.setActionType(logAnnotation.actionType());
log.setClassMethod(joinPoint.getSignature().toString());
log.setRequestMethod(method);
log.setOperationUrl(url);
log.setOperationIp(ip);
log.setOperationParam(params);
log.setJsonResult(resultStr);
log.setErrorMsg(errorMsg);
log.setOperationStatus(status);
log.setCostTime(costTime);
log.setCreateTime(new Date());
// 异步保存日志
taskExecutor.execute(() -> logApiRequestService.save(log));
} catch (Exception ex) {
log.error("记录日志时发生异常:", ex);
}
}
// 获取注解
private Log getLogAnnotation(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
return signature.getMethod().getAnnotation(Log.class);
}
// 获取参数(简化处理,过滤文件等敏感对象)
private String getMethodParams(JoinPoint joinPoint, String method) {
if ("GET".equals(method)) {
return RequestUtil.getCurrentRequest().getQueryString();
}
return Arrays.stream(joinPoint.getArgs())
.filter(arg -> !(arg instanceof HttpServletRequest)
&& !(arg instanceof MultipartFile))
.map(JSON::toJSONString)
.collect(Collectors.joining(" "));
}
}
使用方式
java
@Log(businessModule = BusinessModule.ORDER, actionType = ActionTypeEnum.CREATE)
@PostMapping("/order")
public Result createOrder(@RequestBody OrderForm form, SessMerchant user) {
// 业务逻辑
return orderService.create(form, user);
}
✅ 效果:只要加上 @Log
注解,日志就会自动记录 ,业务代码零侵入!
五、面试回答模板
当被问到:"什么是 AOP?它解决了什么问题?"
你可以这样回答:
AOP是面向切面编程,它的核心思想是将那些贯穿多个模块的通用系统功能(如日志、权限、事务)提取出来,定义成"切面",然后在不修改原有代码的情况下,动态地织入到业务功能【目标方法】的执行流程中。
比如,我们可以在每个 service 方法执行前后自动记录日志,而不需要在每个方法里写日志代码。
AOP的核心价值在于分离系统功能与业务逻辑,提升代码可维护性和扩展性。