概述
AOP(Aspect-Oriented Programming) 面向切面编程是一种编程范式,它将横切关注点(如日志记录、事务管理、权限检查等)从业务逻辑中分离出来,通过动态代理的方式在运行时将这些关注点织入到目标对象中。
核心特性
- 横切关注点分离:将系统级功能与业务逻辑解耦
- 动态代理:运行时动态创建代理对象
- 非侵入式:无需修改原有代码即可添加功能
- 声明式编程:通过配置或注解定义切面行为
应用场景
| 场景 | 描述 | 示例 |
|---|---|---|
| 日志记录 | 记录方法调用日志 | 记录用户操作、系统日志 |
| 事务管理 | 声明式事务控制 | 数据库事务、分布式事务 |
| 权限检查 | 方法级权限验证 | 用户认证、角色授权 |
| 性能监控 | 方法执行时间统计 | 性能分析、瓶颈识别 |
| 异常处理 | 统一异常处理 | 错误记录、异常转换 |
💡 提示: AOP 是 Spring 框架的核心特性之一,与 IoC 容器配合使用,可以构建松耦合的企业级应用。
AOP 核心概念
基本术语
| 术语 | 英文 | 描述 | 示例 |
|---|---|---|---|
| 切面 | Aspect | 横切关注点的模块化,包含通知和切点 | LogAspect、TransactionAspect |
| 连接点 | Joinpoint | 程序执行的特定点,通常是方法调用 | 方法执行前、后、异常时 |
| 切点 | Pointcut | 连接点的集合,定义在哪里应用通知 | 所有 Service 层方法 |
| 通知 | Advice | 在特定连接点执行的代码 | 前置通知、后置通知 |
| 目标对象 | Target Object | 被一个或多个切面通知的对象 | UserService、OrderService |
| 代理对象 | Proxy | AOP 创建的对象,包含目标对象和通知 | UserServiceProxy |
| 织入 | Weaving | 将切面应用到目标对象的过程 | 编译时、类加载时、运行时 |
AOP 工作原理
目标对象 (Target) + 切面 (Aspect) = 代理对象 (Proxy)
客户端调用 → 代理对象 → 执行通知 → 目标对象 → 返回结果
代理模式基础
静态代理示例
代理模式是 AOP 的基础,让我们先通过一个生活化的例子理解代理的概念:
业务场景:代购服务
// 被代理对象:韩国商店
public class KoreanShop {
public void sell() {
System.out.println("韩国商店卖化妆品100元");
}
}
// 代理对象:代购
public class ShopProxy {
private KoreanShop shop;
public ShopProxy(KoreanShop shop) {
this.shop = shop;
}
public void sell() {
// 前置增强:收取服务费
System.out.println("代购收取10元服务费");
// 调用目标对象方法
shop.sell();
// 后置增强:提供额外服务
System.out.println("代购提供包装和快递服务");
System.out.println("最终价格:110元");
}
}
// 客户端使用
public class Client {
public static void main(String[] args) {
KoreanShop shop = new KoreanShop();
ShopProxy proxy = new ShopProxy(shop);
proxy.sell();
}
}
输出结果:
代购收取10元服务费
韩国商店卖化妆品100元
代购提供包装和快递服务
最终价格:110元
代理模式优势
- 功能增强:在不修改原有代码的基础上添加新功能
- 职责分离:将核心业务逻辑与辅助功能分离
- 灵活性:可以根据需要动态添加或移除功能
JDK 动态代理
基本原理
JDK 动态代理基于反射机制,为接口创建代理对象。代理对象实现了目标接口,通过 InvocationHandler 处理方法调用。
实现步骤
1. 定义接口和实现类
// 业务接口
public interface UserService {
void save(String username);
void update(String username);
void delete(String username);
}
// 业务实现类
public class UserServiceImpl implements UserService {
@Override
public void save(String username) {
System.out.println("保存用户:" + username);
}
@Override
public void update(String username) {
System.out.println("更新用户:" + username);
}
@Override
public void delete(String username) {
System.out.println("删除用户:" + username);
}
}
2. 创建切面类
// 切面类:提供横切关注点功能
public class LogAspect {
// 前置通知
public void beforeAdvice(String methodName) {
System.out.println("[前置通知] 调用方法:" + methodName + " - " +
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
// 后置通知
public void afterAdvice(String methodName) {
System.out.println("[后置通知] 方法执行完成:" + methodName);
}
// 异常通知
public void exceptionAdvice(String methodName, Exception e) {
System.out.println("[异常通知] 方法执行异常:" + methodName + ", 异常信息:" + e.getMessage());
}
}
3. 实现 InvocationHandler
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkProxyFactory implements InvocationHandler {
private Object target; // 目标对象
private LogAspect logAspect; // 切面对象
public JdkProxyFactory(Object target) {
this.target = target;
this.logAspect = new LogAspect();
}
// 创建代理对象
public Object createProxy() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
// 处理方法调用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Object result = null;
try {
// 前置通知
logAspect.beforeAdvice(methodName);
// 调用目标方法
result = method.invoke(target, args);
// 后置通知
logAspect.afterAdvice(methodName);
} catch (Exception e) {
// 异常通知
logAspect.exceptionAdvice(methodName, e);
throw e;
}
return result;
}
}
4. 测试代理功能
public class JdkProxyTest {
public static void main(String[] args) {
// 创建目标对象
UserService target = new UserServiceImpl();
// 创建代理工厂
JdkProxyFactory factory = new JdkProxyFactory(target);
// 创建代理对象
UserService proxy = (UserService) factory.createProxy();
// 通过代理对象调用方法
proxy.save("张三");
proxy.update("李四");
proxy.delete("王五");
}
}
输出结果:
[前置通知] 调用方法:save - 2025-07-09 19:10:00
保存用户:张三
[后置通知] 方法执行完成:save
[前置通知] 调用方法:update - 2025-07-09 19:10:00
更新用户:李四
[后置通知] 方法执行完成:update
[前置通知] 调用方法:delete - 2025-07-09 19:10:00
删除用户:王五
[后置通知] 方法执行完成:delete
CGLIB 动态代理
基本原理
CGLIB(Code Generation Library)通过字节码生成技术,为没有实现接口的类创建子类代理。
实现步骤
1. 添加 CGLIB 依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
2. 创建目标类(无需接口)
// 目标类:不需要实现接口
public class OrderService {
public void createOrder(String orderInfo) {
System.out.println("创建订单:" + orderInfo);
}
public void updateOrder(String orderId) {
System.out.println("更新订单:" + orderId);
}
public void cancelOrder(String orderId) {
System.out.println("取消订单:" + orderId);
}
}
3. 创建 CGLIB 代理工厂
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxyFactory implements MethodInterceptor {
private Object target;
private LogAspect logAspect;
public CglibProxyFactory(Object target) {
this.target = target;
this.logAspect = new LogAspect();
}
// 创建代理对象
public Object createProxy() {
// 创建 Enhancer 对象
Enhancer enhancer = new Enhancer();
// 设置父类(目标类)
enhancer.setSuperclass(target.getClass());
// 设置回调函数
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
// 方法拦截
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String methodName = method.getName();
Object result = null;
try {
// 前置通知
logAspect.beforeAdvice(methodName);
// 调用目标方法
result = proxy.invokeSuper(obj, args);
// 后置通知
logAspect.afterAdvice(methodName);
} catch (Exception e) {
// 异常通知
logAspect.exceptionAdvice(methodName, e);
throw e;
}
return result;
}
}
4. 测试 CGLIB 代理
public class CglibProxyTest {
public static void main(String[] args) {
// 创建目标对象
OrderService target = new OrderService();
// 创建代理工厂
CglibProxyFactory factory = new CglibProxyFactory(target);
// 创建代理对象
OrderService proxy = (OrderService) factory.createProxy();
// 通过代理对象调用方法
proxy.createOrder("iPhone 15 Pro");
proxy.updateOrder("ORDER-12345");
proxy.cancelOrder("ORDER-12345");
}
}
Spring AOP 实现
XML 配置方式
1. 添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.21</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.21</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9</version>
</dependency>
</dependencies>
2. 创建切面类
// 切面类
public class LoggingAspect {
// 前置通知
public void beforeAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
System.out.println("[前置通知] " + className + "." + methodName + "() 开始执行");
}
// 后置通知
public void afterAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
System.out.println("[后置通知] " + className + "." + methodName + "() 执行完成");
}
// 环绕通知
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
long startTime = System.currentTimeMillis();
System.out.println("[环绕通知] 方法开始执行:" + methodName);
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println("[环绕通知] 方法执行完成:" + methodName +
",耗时:" + (endTime - startTime) + "ms");
return result;
}
// 异常通知
public void exceptionAdvice(JoinPoint joinPoint, Exception e) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[异常通知] 方法执行异常:" + methodName +
",异常信息:" + e.getMessage());
}
}
3. Spring 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置目标对象 -->
<bean id="userService" class="com.example.service.UserServiceImpl"/>
<!-- 配置切面 -->
<bean id="loggingAspect" class="com.example.aspect.LoggingAspect"/>
<!-- AOP 配置 -->
<aop:config>
<!-- 定义切点 -->
<aop:pointcut id="servicePointcut"
expression="execution(* com.example.service.*.*(..))"/>
<!-- 配置切面 -->
<aop:aspect ref="loggingAspect">
<!-- 前置通知 -->
<aop:before method="beforeAdvice" pointcut-ref="servicePointcut"/>
<!-- 后置通知 -->
<aop:after method="afterAdvice" pointcut-ref="servicePointcut"/>
<!-- 环绕通知 -->
<aop:around method="aroundAdvice" pointcut-ref="servicePointcut"/>
<!-- 异常通知 -->
<aop:after-throwing method="exceptionAdvice"
pointcut-ref="servicePointcut"
throwing="e"/>
</aop:aspect>
</aop:config>
</beans>
注解配置方式
1. 启用 AOP 注解
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.example")
public class AppConfig {
// 配置类
}
2. 创建注解切面
@Aspect
@Component
public class LoggingAspect {
// 定义切点
@Pointcut("execution(* com.example.service.*.*(..))")
public void servicePointcut() {}
// 前置通知
@Before("servicePointcut()")
public void beforeAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
System.out.println("[前置通知] " + className + "." + methodName + "() 开始执行");
}
// 后置通知
@After("servicePointcut()")
public void afterAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
System.out.println("[后置通知] " + className + "." + methodName + "() 执行完成");
}
// 环绕通知
@Around("servicePointcut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
long startTime = System.currentTimeMillis();
System.out.println("[环绕通知] 方法开始执行:" + methodName);
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println("[环绕通知] 方法执行完成:" + methodName +
",耗时:" + (endTime - startTime) + "ms");
return result;
}
// 异常通知
@AfterThrowing(pointcut = "servicePointcut()", throwing = "e")
public void exceptionAdvice(JoinPoint joinPoint, Exception e) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[异常通知] 方法执行异常:" + methodName +
",异常信息:" + e.getMessage());
}
}
AspectJ 集成
AspectJ 与 Spring AOP 对比
| 特性 | Spring AOP | AspectJ |
|---|---|---|
| 编译方式 | 运行时代理 | 编译时织入 |
| 性能 | 较低 | 高 |
| 功能完整性 | 基础 AOP 功能 | 完整 AOP 功能 |
| 学习成本 | 低 | 高 |
| 集成难度 | 简单 | 复杂 |
AspectJ 注解详解
通知类型
@Aspect
@Component
public class AspectJExample {
// 前置通知
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
// 方法执行前的逻辑
}
// 后置通知(无论是否异常都执行)
@After("execution(* com.example.service.*.*(..))")
public void afterAdvice(JoinPoint joinPoint) {
// 方法执行后的逻辑
}
// 返回通知(正常返回时执行)
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
// 方法正常返回后的逻辑
}
// 异常通知
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "e")
public void afterThrowingAdvice(JoinPoint joinPoint, Exception e) {
// 方法抛出异常时的逻辑
}
// 环绕通知
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 方法执行前的逻辑
Object result = joinPoint.proceed();
// 方法执行后的逻辑
return result;
}
}
通知类型详解
执行顺序
对于同一个方法,各种通知的执行顺序:
正常执行:
@Around 前半部分 → @Before → 目标方法 → @AfterReturning → @After → @Around 后半部分
异常执行:
@Around 前半部分 → @Before → 目标方法 → @AfterThrowing → @After
获取方法信息
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
// 获取方法签名
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
String className = signature.getDeclaringTypeName();
// 获取方法参数
Object[] args = joinPoint.getArgs();
// 获取目标对象
Object target = joinPoint.getTarget();
System.out.println("方法:" + className + "." + methodName);
System.out.println("参数:" + Arrays.toString(args));
System.out.println("目标对象:" + target.getClass().getSimpleName());
}
切点表达式
基本语法
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?
name-pattern(param-pattern) throws-pattern?)
常用表达式
// 匹配所有方法
@Pointcut("execution(* *(..))")
// 匹配指定包下的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
// 匹配指定类的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
// 匹配指定方法名
@Pointcut("execution(* com.example.service.*.save*(..))")
// 匹配指定参数类型
@Pointcut("execution(* com.example.service.*.*(String, ..))")
// 匹配指定返回类型
@Pointcut("execution(public String com.example.service.*.*(..))")
// 匹配注解标记的方法
@Pointcut("@annotation(com.example.annotation.Log)")
// 匹配类级别注解
@Pointcut("@within(org.springframework.stereotype.Service)")
// 组合表达式
@Pointcut("execution(* com.example.service.*.*(..)) && @annotation(com.example.annotation.Log)")
实战示例
日志记录切面
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
@Pointcut("@annotation(com.example.annotation.Log)")
public void logPointcut() {}
@Around("logPointcut()")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
long startTime = System.currentTimeMillis();
try {
logger.info("开始执行方法: {}.{}", className, methodName);
Object result = joinPoint.proceed();
logger.info("方法执行成功: {}.{}", className, methodName);
return result;
} catch (Exception e) {
logger.error("方法执行失败: {}.{}, 异常: {}", className, methodName, e.getMessage());
throw e;
} finally {
long endTime = System.currentTimeMillis();
logger.info("方法执行耗时: {}ms", endTime - startTime);
}
}
}
事务管理切面
@Aspect
@Component
public class TransactionAspect {
@Autowired
private DataSourceTransactionManager transactionManager;
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object handleTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
Object result = joinPoint.proceed();
transactionManager.commit(status);
return result;
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
权限检查切面
@Aspect
@Component
public class SecurityAspect {
@Before("@annotation(com.example.annotation.RequireRole)")
public void checkPermission(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
RequireRole requireRole = signature.getMethod().getAnnotation(RequireRole.class);
String[] roles = requireRole.value();
String currentUserRole = getCurrentUserRole();
boolean hasPermission = Arrays.asList(roles).contains(currentUserRole);
if (!hasPermission) {
throw new SecurityException("用户无权限访问该方法");
}
}
private String getCurrentUserRole() {
// 获取当前用户角色的逻辑
return "USER";
}
}
性能优化
1. 选择合适的代理方式
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制使用 CGLIB
public class AopConfig {
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
}
2. 优化切点表达式
// 精确匹配,避免过度匹配
@Pointcut("execution(* com.example.service.UserService.save(..))")
// 使用注解方式,性能更好
@Pointcut("@annotation(com.example.annotation.Log)")
3. 异步处理
@Aspect
@Component
public class AsyncLoggingAspect {
@Async
@After("execution(* com.example.service.*.*(..))")
public void asyncLogging(JoinPoint joinPoint) {
// 异步记录日志,不影响主流程性能
String methodName = joinPoint.getSignature().getName();
System.out.println("异步记录日志: " + methodName);
}
}
常见问题
问题1:AOP 不生效
现象: 切面代码没有执行 原因:
- 没有启用 AOP 注解
- 切点表达式错误
- 目标对象没有被 Spring 管理
解决方案:
// 确保启用 AOP
@EnableAspectJAutoProxy
// 检查切点表达式
@Pointcut("execution(* com.example.service.*.*(..))")
// 确保目标类被 Spring 管理
@Service
public class UserService {
// ...
}
问题2:切面执行顺序问题
现象: 多个切面执行顺序不正确 解决方案:
@Aspect
@Component
@Order(1) // 数字越小优先级越高
public class SecurityAspect {
// ...
}
@Aspect
@Component
@Order(2)
public class LoggingAspect {
// ...
}
问题3:内部方法调用切面失效
现象: 同一个类内部方法调用时切面不生效 原因: Spring AOP 基于代理,内部调用绕过了代理 解决方案:
@Service
public class UserService {
@Autowired
private UserService self; // 注入自己的代理对象
public void method1() {
self.method2(); // 通过代理调用
}
@Log
public void method2() {
// ...
}
}
问题4:切面中获取不到参数
现象: 在切面中无法获取方法参数 解决方案:
@Before("execution(* com.example.service.*.*(..)) && args(param)")
public void beforeAdvice(JoinPoint joinPoint, String param) {
System.out.println("方法参数: " + param);
}
⚠️ 注意: 在生产环境中使用 AOP 时,要注意性能影响,避免在高频调用的方法上使用复杂的切面逻辑。
相关文章
前后章节导航
- 上一章:第二章:Spring中的Bean
- 下一章:第三章:从Spring的角度去实现代理
Spring基础系列
- Spring的基本应用 - Spring入门
- 依赖注入详解 - DI机制
- Spring中的Bean - Bean管理
- Spring代理实现 - 代理模式
- Spring数据库开发 - 数据访问层
- Spring事务管理 - 声明式事务
Spring进阶指南
- Spring Bean管理完整指南 - Bean管理深入
- Spring事务管理完整指南 - 事务管理详解
MyBatis持久层框架
- 初识MyBatis - MyBatis入门
- MyBatis核心配置 - 配置详解
- Spring和MyBatis整合 - 框架集成
Spring MVC Web开发
- Spring MVC核心架构 - Web开发基础
- Spring MVC拦截器完整指南 - 拦截器机制
SpringBoot现代化开发
- SpringBoot项目结构 - SpringBoot基础
- SpringBoot整合技术栈 - 现代化Spring应用
总结
Spring AOP 是 Spring 框架的核心特性,通过面向切面编程实现了横切关注点的模块化管理。本指南涵盖了:
🎯 核心概念
- AOP 基本概念:切面、连接点、切点、通知等
- 代理模式:静态代理、JDK 动态代理、CGLIB 代理
- Spring AOP:XML 配置、注解配置、AspectJ 集成
🛠️ 实现方式
- JDK 动态代理:基于接口的代理实现
- CGLIB 代理:基于继承的代理实现
- Spring AOP:简化的企业级 AOP 解决方案
📚 实践应用
- 日志记录:方法执行日志和性能监控
- 事务管理:声明式事务控制
- 权限检查:方法级别的安全控制