Java AOP面向切面编程的概念和实现方式

2025-10-01 01:50

本文主要是介绍Java AOP面向切面编程的概念和实现方式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概...

一、AOP 是什么?

AOP(ASPect-Oriented Programming),即面向切面编程。它是一种编程范式,与我们所熟悉的OOP(面向对象编程)是互补的关系,而不是替代。

  • OOP 的局限:OOP的核心是对象,它擅长将功能进行纵向的模块化划分(例如,UserService类负责用户相关所有逻辑)。但对于一些需要横向地散布 across 多个模块的功能,OOP就显得力不从心。
    • 例如:日志记录、性能监控、事务管理、安全校验。这些逻辑几乎需要出现在每一个业务方法中,但它们本质上又不属于核心业务逻辑。
  • AOP 的解决方案:AOP将这些散布在各处的横切关注点(Cross-Cutting Concerns)从核心业务逻辑中分离出来,封装成一个独立的可重用的模块,称为切面(Aspect)。然后,在程序运行的合适时机,AOP会自动地将这些切面代码“织入”到需要它们的方法中。
http://www.chinasem.cn

看下面的例子:

Java 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 = "家务活动";
        wphpaterUsageRecords.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升&nbsChina编程p;
使用15升水打扫房间 
=== 家务完成 === 总用水量: 25升

代码说明

  • 业务类:包含核心的家务逻辑(洗碗和打扫),每个类都有一个fillWater方法用于接水。
  • 切面类
    • 使用@Aspect注解标记为切面
    • 使用@Pointcut定义切入点,匹配所有接水操作
    • 使用@AfterReturning后置通知记录用水量
    • 维护用水记录并提供查询接口
  • 配置类:启用Spring AOP自动代理和组件扫描
  • 测试类:演示如何使用AOP记录家务活动的用水量

五、AOP 的常见应用场景

  • 日志记录:如上例,记录方法入参、出参、执行耗时,用于调试和监控。
  • 事务管理这是最经典的应用! Spring的 @Transactional 注解就是基于AOP实现的。它在方法开始时开启事务,在方法成功执行后提交事务,在抛出异常时回滚事务。
  • 权限校验和安全控制:在方法执行前,判断当前用户是否有权限执行此操作。例如使用 @PreAuthorize 注解。
  • 性能监控:统计方法的执行时间,上报给监控系统,用于发现性能瓶颈。
  • 异常处理与统一返回:捕获服务层抛出的异常,将其转换为友好的错误信息格式返回给前端。
  • 缓存:在方法执行前检查缓存中是否有数据,如果有则直接返回,否则执行方法并将结果放入缓存。

总结

  • AOP是什么:一种将横切关注点(日志、事务等)与核心业务逻辑分离的技术。
  • 如何实现:Spring AOP通过动态代理在运行时将切面“织入”到目标方法中。
  • 核心注解@Aspect, @Pointcut, @Around, @Before, @After等。
  • 为何需要:实现解耦、提高代码的可复用性可维护性,让开发者能更专注于核心业务逻辑。

通过AOP,我们可以以一种javascript非常优雅和非侵入式的方式,为应用程序添加强大的功能。

到此这篇关于java:AOP面向切面编程的文章就介绍到这了,更多相关java aop切面内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持China编程(www.cwww.chinasem.cnppcns.com)!

这篇关于Java AOP面向切面编程的概念和实现方式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1156125

相关文章

一篇文章彻底搞懂macOS如何决定java环境

《一篇文章彻底搞懂macOS如何决定java环境》MacOS作为一个功能强大的操作系统,为开发者提供了丰富的开发工具和框架,下面:本文主要介绍macOS如何决定java环境的相关资料,文中通过代码... 目录方法一:使用 which命令方法二:使用 Java_home工具(Apple 官方推荐)那问题来了,

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

Python实现字典转字符串的五种方法

《Python实现字典转字符串的五种方法》本文介绍了在Python中如何将字典数据结构转换为字符串格式的多种方法,首先可以通过内置的str()函数进行简单转换;其次利用ison.dumps()函数能够... 目录1、使用json模块的dumps方法:2、使用str方法:3、使用循环和字符串拼接:4、使用字符

Java中的.close()举例详解

《Java中的.close()举例详解》.close()方法只适用于通过window.open()打开的弹出窗口,对于浏览器的主窗口,如果没有得到用户允许是不能关闭的,:本文主要介绍Java中的.... 目录当你遇到以下三种情况时,一定要记得使用 .close():用法作用举例如何判断代码中的 input

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

Linux挂载linux/Windows共享目录实现方式

《Linux挂载linux/Windows共享目录实现方式》:本文主要介绍Linux挂载linux/Windows共享目录实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录文件共享协议linux环境作为服务端(NFS)在服务器端安装 NFS创建要共享的目录修改 NFS 配

通过React实现页面的无限滚动效果

《通过React实现页面的无限滚动效果》今天我们来聊聊无限滚动这个现代Web开发中不可或缺的技术,无论你是刷微博、逛知乎还是看脚本,无限滚动都已经渗透到我们日常的浏览体验中,那么,如何优雅地实现它呢?... 目录1. 早期的解决方案2. 交叉观察者:IntersectionObserver2.1 Inter

Spring Gateway动态路由实现方案

《SpringGateway动态路由实现方案》本文主要介绍了SpringGateway动态路由实现方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随... 目录前沿何为路由RouteDefinitionRouteLocator工作流程动态路由实现尾巴前沿S