一、aop 是什么?
aop(aspect-oriented programming),即面向切面编程。它是一种编程范式,与我们所熟悉的oop(面向对象编程)是互补的关系,而不是替代。
- oop 的局限:oop的核心是对象和类,它擅长将功能进行纵向的模块化划分(例如,
userservice
类负责用户相关所有逻辑)。但对于一些需要横向地散布 across 多个模块的功能,oop就显得力不从心。- 例如:日志记录、性能监控、事务管理、安全校验。这些逻辑几乎需要出现在每一个业务方法中,但它们本质上又不属于核心业务逻辑。
- aop 的解决方案:aop将这些散布在各处的横切关注点(cross-cutting concerns)从核心业务逻辑中分离出来,封装成一个独立的可重用的模块,称为切面(aspect)。然后,在程序运行的合适时机,aop会自动地将这些切面代码“织入”到需要它们的方法中。
看下面的例子:
面向对象编程opp完成了穿衣、吃饭、洗碗等这些功能,但从另一个维度去控制这些功能,比如“查看穿衣服、吃饭的时间(性能监控)”、“保证吃饭、洗碗连在一起完成(事务管理)”、“记录洗碗、打扫消耗的用水量(日志记录)”这些就是面向切面编程aop
- 没有aop,就得在每个功能上添加相应控制,比如在穿衣服写一遍记录时间的代码,然后再再吃饭处写一遍记录时间的代码。这显然不合理。aop让核心业务(穿衣、吃饭)和通用功能(记录时间)得以解耦。
二、aop 的核心概念与实现方式
核心概念
- aspect(切面):封装横切关注点的模块。它是一个类,上面标注了
@aspect
注解。例如:loggingaspect
(日志切面)、transactionaspect
(事务切面)。 - advice(通知):切面中的具体方法。它定义了“做什么”以及“何时做”。
- 何时做:
@before
(方法前)、@after
(方法后)、@afterreturning
(成功返回后)、@afterthrowing
(抛出异常后)、@around
(环绕,最强大)。 - pointcut(切点):一个表达式,定义了“在何处做”,即匹配哪些类的哪些方法需要被增强。它决定了advice的应用位置。
- join point(连接点):程序执行过程中能够插入切面的一个点,例如方法调用、异常抛出等。在spring aop中,连接点总是代表一个方法的执行。
- weaving(织入):将切面代码应用到目标对象,从而创建代理对象的过程。spring aop在运行时通过动态代理完成织入。
实现方式
spring aop 的底层就是基于我们之前讨论过的动态代理:
- 如果目标对象实现了接口,默认使用 jdk 动态代理。
- 如果目标对象没有实现接口,则使用 cglib 库生成子类进行代理。
三、spring aop 的关键注解
注解 | 说明 |
---|---|
@aspect | 声明一个类是切面。 |
@pointcut | 声明一个切点表达式,可被通知方法引用,避免重复书写。 |
@before | 前置通知:在目标方法执行之前执行。 |
@afterreturning | 返回通知:在目标方法成功执行并返回后执行。 |
@afterthrowing | 异常通知:在目标方法抛出异常后执行。 |
@after | 后置通知:在目标方法执行之后执行(无论成功还是异常,类似于finally )。 |
@around | 环绕通知:最强大的通知类型,可以手动控制目标方法的执行时机,可以在方法执行前后添加自定义行为。 |
四、实际场景与代码示例
场景一:记录洗碗和打扫的用水量
1. 业务类(核心方法)
washdish.java - 洗碗类
@component public class washdish { private int water; public int fillwater(int amount) { this.water += amount; system.out.println("洗碗接水: " + amount + "升"); return amount; } public void wash() { system.out.println("使用" + water + "升水洗碗"); water = 0; } }
cleanroom.java - 打扫类
@component public class cleanroom { private int water; public int fillwater(int amount) { this.water += amount; system.out.println("打扫接水: " + amount + "升"); return amount; } public void clean() { system.out.println("使用" + water + "升水打扫房间"); water = 0; } }
2. 切面类(aop实现)
waterusageloggeraspect.java - 用水量记录切面
/** * 切面类 */ @aspect @component public class waterusageloggeraspect { private map<string, integer> waterusagerecords = new hashmap<>(); /** * 切入点:规定哪些类被控制 */ @pointcut("execution(* washdish.fillwater(..)) || execution(* cleanroom.fillwater(..))") public void waterusagepointcut() {} /** * 通知 */ @afterreturning(pointcut = "waterusagepointcut()", returning = "wateramount") public void logwaterusage(int wateramount) { string activity = "家务活动"; waterusagerecords.put(activity, waterusagerecords.getordefault(activity, 0) + wateramount); system.out.println("记录用水: " + wateramount + "升,当前总用水量: " + waterusagerecords.get(activity) + "升"); } public int gettotalwaterusage() { return waterusagerecords.values().stream().maptoint(integer::intvalue).sum(); } }
3. 配置类(spring配置)
appconfig.java - 应用配置
@configuration @enableaspectjautoproxy @componentscan("com.example.aop") public class appconfig { }
4. 测试类(演示代码)
homechorestest.java - 家务测试
public class homechorestest { public static void main(string[] args) { annotationconfigapplicationcontext context = new annotationconfigapplicationcontext(appconfig.class); washdish washdish = context.getbean(washdish.class); cleanroom cleanroom = context.getbean(cleanroom.class); waterusageloggeraspect waterlogger = context.getbean(waterusageloggeraspect.class); system.out.println("=== 开始家务活动 ==="); // 洗碗活动 system.out.println("\n--- 洗碗 ---"); washdish.fillwater(10); washdish.wash(); // 打扫活动 system.out.println("\n--- 打扫 ---"); cleanroom.fillwater(15); cleanroom.clean(); system.out.println("\n=== 家务完成 ==="); system.out.println("总用水量: " + waterlogger.gettotalwaterusage() + "升"); context.close(); } }
5. 预期输出
运行测试类后,预期输出如下:
=== 开始家务活动 ===
--- 洗碗 ---
洗碗接水: 10升
记录用水: 10升,当前总用水量: 10升
使用10升水洗碗
--- 打扫 ---
打扫接水: 15升
记录用水: 15升,当前总用水量: 25升
使用15升水打扫房间
=== 家务完成 === 总用水量: 25升
代码说明
- 业务类:包含核心的家务逻辑(洗碗和打扫),每个类都有一个
fillwater
方法用于接水。 - 切面类:
- 使用
@aspect
注解标记为切面 - 使用
@pointcut
定义切入点,匹配所有接水操作 - 使用
@afterreturning
后置通知记录用水量 - 维护用水记录并提供查询接口
- 使用
- 配置类:启用spring aop自动代理和组件扫描
- 测试类:演示如何使用aop记录家务活动的用水量
五、aop 的常见应用场景
- 日志记录:如上例,记录方法入参、出参、执行耗时,用于调试和监控。
- 事务管理:这是最经典的应用! spring的
@transactional
注解就是基于aop实现的。它在方法开始时开启事务,在方法成功执行后提交事务,在抛出异常时回滚事务。 - 权限校验和安全控制:在方法执行前,判断当前用户是否有权限执行此操作。例如使用
@preauthorize
注解。 - 性能监控:统计方法的执行时间,上报给监控系统,用于发现性能瓶颈。
- 异常处理与统一返回:捕获服务层抛出的异常,将其转换为友好的错误信息格式返回给前端。
- 缓存:在方法执行前检查缓存中是否有数据,如果有则直接返回,否则执行方法并将结果放入缓存。
总结
- aop是什么:一种将横切关注点(日志、事务等)与核心业务逻辑分离的技术。
- 如何实现:spring aop通过动态代理在运行时将切面“织入”到目标方法中。
- 核心注解:
@aspect
,@pointcut
,@around
,@before
,@after
等。 - 为何需要:实现解耦、提高代码的可复用性和可维护性,让开发者能更专注于核心业务逻辑。
通过aop,我们可以以一种非常优雅和非侵入式的方式,为应用程序添加强大的功能。
到此这篇关于java:aop面向切面编程的文章就介绍到这了,更多相关java aop切面内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论