java:AOP面向切面编程

一、AOP 是什么?

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

  • OOP 的局限:OOP的核心是对象,它擅长将功能进行纵向的模块化划分(例如,UserService类负责用户相关所有逻辑)。但对于一些需要横向地散布 across 多个模块的功能,OOP就显得力不从心。

    • 例如:日志记录、性能监控、事务管理、安全校验。这些逻辑几乎需要出现在每一个业务方法中,但它们本质上又不属于核心业务逻辑。

  • AOP 的解决方案:AOP将这些散布在各处的横切关注点(Cross-Cutting Concerns)从核心业务逻辑中分离出来,封装成一个独立的可重用的模块,称为切面(Aspect)。然后,在程序运行的合适时机,AOP会自动地将这些切面代码“织入”到需要它们的方法中。

看下面的例子:

面向对象编程OPP完成了穿衣、吃饭、洗碗等这些功能,但从另一个维度去控制这些功能,比如“查看穿衣服、吃饭的时间(性能监控)”、“保证吃饭、洗碗连在一起完成(事务管理)”、“记录洗碗、打扫消耗的用水量(日志记录)”这些就是面向切面编程AOP

  • 没有AOP,就得在每个功能上添加相应控制,比如在穿衣服写一遍记录时间的代码,然后再再吃饭处写一遍记录时间的代码。这显然不合理。AOP让核心业务(穿衣、吃饭)和通用功能(记录时间)得以解耦。


二、AOP 的核心概念与实现方式

核心概念
  1. Aspect(切面):封装横切关注点的模块。它是一个类,上面标注了 @Aspect 注解。例如:LoggingAspect(日志切面)、TransactionAspect(事务切面)。

  2. Advice(通知):切面中的具体方法。它定义了“做什么”以及“何时做”。

    • 何时做@Before(方法前)、@After(方法后)、@AfterReturning(成功返回后)、@AfterThrowing(抛出异常后)、@Around(环绕,最强大)。

  3. Pointcut(切点):一个表达式,定义了“在何处做”,即匹配哪些类的哪些方法需要被增强。它决定了Advice的应用位置。

  4. Join Point(连接点):程序执行过程中能够插入切面的一个点,例如方法调用、异常抛出等。在Spring AOP中,连接点总是代表一个方法的执行

  5. 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升

代码说明

  1. 业务类:包含核心的家务逻辑(洗碗和打扫),每个类都有一个fillWater方法用于接水。

  2. 切面类

    • 使用@Aspect注解标记为切面

    • 使用@Pointcut定义切入点,匹配所有接水操作

    • 使用@AfterReturning后置通知记录用水量

    • 维护用水记录并提供查询接口

  3. 配置类:启用Spring AOP自动代理和组件扫描

  4. 测试类:演示如何使用AOP记录家务活动的用水量


五、AOP 的常见应用场景

  1. 日志记录:如上例,记录方法入参、出参、执行耗时,用于调试和监控。

  2. 事务管理这是最经典的应用! Spring的 @Transactional 注解就是基于AOP实现的。它在方法开始时开启事务,在方法成功执行后提交事务,在抛出异常时回滚事务。

  3. 权限校验和安全控制:在方法执行前,判断当前用户是否有权限执行此操作。例如使用 @PreAuthorize 注解。

  4. 性能监控:统计方法的执行时间,上报给监控系统,用于发现性能瓶颈。

  5. 异常处理与统一返回:捕获服务层抛出的异常,将其转换为友好的错误信息格式返回给前端。

  6. 缓存:在方法执行前检查缓存中是否有数据,如果有则直接返回,否则执行方法并将结果放入缓存。

总结

  • AOP是什么:一种将横切关注点(日志、事务等)与核心业务逻辑分离的技术。

  • 如何实现:Spring AOP通过动态代理在运行时将切面“织入”到目标方法中。

  • 核心注解@Aspect, @Pointcut, @Around, @Before, @After等。

  • 为何需要:实现解耦、提高代码的可复用性可维护性,让开发者能更专注于核心业务逻辑。

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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值