Java的AOP切面编程之快速入门案例(保姆级教程)

1. Java中的切面编程(AOP)概述

​ 切面编程(Aspect-Oriented Programming,AOP)是一种编程范式,旨在将那些贯穿于多个模块的横切关注点(如日志记录、安全检查、事务管理)与核心业务逻辑分离开来。通过AOP,我们可以提高代码的模块化程度,减少代码重复,并使代码更加可维护。

概念定义
切面(Aspect)切面是横切关注点的模块化实现,它包含多个通知(Advice)和切入点(Pointcut)。
通知(Advice)通知是在特定连接点(Join Point)上执行的操作。它可以在方法执行前、执行后、抛出异常后或包围整个方法的执行过程。
切入点(Pointcut)切入点是通过(execution)表达式定义的,用于指定在哪些连接点上应用通知。
连接点(Join Point)程序执行的具体点,如方法调用或异常抛出,切入点通常匹配这些连接点。
代理(Proxy)代理是AOP框架生成的对象,负责在目标对象方法调用前后插入通知逻辑。

类比:

切面(Aspect):影院的自动灯光系统控制电影放映时的灯光。

通知(Advice):当电影开始时,灯光变暗;电影结束时,灯光变亮。

切入点(Pointcut):灯光系统只在“电影开始”和“电影结束”这两个时刻进行操作。

连接点(Join Point):实际发生的时刻,比如电影开场的那一瞬间。

代理(Proxy):灯光系统自动帮你完成这些操作,你不需要亲自去调节灯光。

2. execution 表达式(定义切入点)

execution表达式用于定义切入点,它指定了在什么方法上应用通知。表达式的结构如下:

execution(修饰符模式? 返回类型模式 声明类型模式? 方法名模式(参数模式) 异常模式?)
部分说明
修饰符模式可选。方法的修饰符,如public、protected。
返回类型模式方法的返回类型,可以是具体类型或*(表示任意类型)。
声明类型模式可选。方法所在的类或接口。
方法名模式方法名称,可以是具体名称或*(表示任意方法)。
参数模式方法的参数列表,可以具体匹配参数类型,或使用..表示任意数量和类型的参数。
异常模式可选。方法可能抛出的异常。
?表示该部分是可选的,而不是必须要包含的。

2.1 匹配特定方法

execution(public void org.example.service.UserService.registerUser(String))

解释:这个表达式匹配UserService类中名为registerUser的方法。该方法必须是public修饰符,返回类型为void,且接受一个String类型的参数。

2.2 匹配包中的所有方法

execution(* org.example.service..*.*(..))

解释:这个表达式匹配com.example.service包及其所有子包中的所有类的所有方法。..表示递归地匹配包及其子包,*.*(..)表示所有类中的所有方法,不论返回类型和参数类型。

2.3 匹配特定类中的所有方法

execution(* org.example.service.UserService.*(..))

解释:这个表达式匹配UserService类中的所有方法,不论方法的返回类型、方法名或参数列表。具体来说,*表示任意返回类型,com.example.service.UserService.*表示UserService类中的所有方法,(..)表示任意数量和类型的参数。

2.4 匹配带有特定参数的方法

execution(* org.example.service.UserService.*(String, ..))

解释:这个表达式匹配UserService类中所有以String作为第一个参数的方法。*表示任意返回类型,UserService.*表示UserService类中的所有方法,(String, ..)表示方法的第一个参数是String类型,后面可以有任意数量和类型的其他参数。

2.5 匹配带有特定返回值的方法

execution(String org.example.service.*.*(..))

解释:这个表达式匹配com.example.service包下所有类中返回类型为String的所有方法。String指定了返回类型,*.*(..)表示匹配所有类中的所有方法,不论方法名和参数类型。

3. Advice(通知)

Advice是切面中定义的实际操作。Spring AOP提供了五种类型的Advice,用于在程序执行的不同阶段插入代码。

3.1 前置通知(Before Advice)

@Before("execution(* org.example.service.UserService.*(..))")
public void logBefore() {
    System.out.println("方法执行前: 准备注册用户");
}

解释:在目标方法执行前执行,用于日志记录、权限检查等。

3.2 后置通知(After Advice)

@After("execution(* org.example.service.UserService.*(..))")
public void logAfter() {
    System.out.println("方法执行后: 用户注册过程完成");
}

解释:在目标方法执行后执行,可以用于释放资源、记录日志等操作。

3.3 返回通知(After Returning Advice)

语法:
@AfterReturning(pointcut = "execution(表达式)", returning = "变量名")
public void 方法名(变量类型 变量名) {
    // 使用返回值的逻辑
}
案例:
@AfterReturning(pointcut = "execution(* org.example.service.UserService.*(..))", returning = "result")
public void logAfterReturning(Object result) {
    System.out.println("方法返回: " + result);
}

解释:在目标方法成功返回结果后执行,可以用于处理返回值、记录返回值日志等操作。

3.4 异常通知(After Throwing Advice)

语法:
@AfterThrowing(pointcut = "execution(表达式)", throwing = "异常变量名")
public void 方法名(异常类型 异常变量名) {
    // 使用异常对象的逻辑
}
案例:
@AfterThrowing(pointcut = "execution(* org.example.service.UserService.*(..))", throwing = "error")
public void logAfterThrowing(Throwable error) {
    System.out.println("方法执行时发生异常: " + error.getMessage());
}

解释:在目标方法抛出异常后执行,可以用于记录异常信息、执行补偿逻辑等操作。

3.5 环绕通知(Around Advice)

@Around("execution(* org.example.service.UserService.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("环绕通知: 方法执行前");
    Object result;
    try {
        result = joinPoint.proceed();  // 执行目标方法
    } catch (Throwable throwable) {
        System.out.println("环绕通知: 方法执行时发生异常: " + throwable.getMessage());
        throw throwable;
    }
    System.out.println("环绕通知: 方法执行后");
    return result;
}

解释:包裹目标方法的执行,在方法执行前后都插入逻辑,甚至可以完全控制方法执行。

目标方法:指的是在你的业务代码中实际被AOP切面拦截的方法。**joinPoint.proceed() 是环绕通知中的核心,它决定了目标方法的执行。**如果你不调用 proceed(),目标方法将不会被执行。在调用 proceed() 之前和之后,你可以插入任何自定义的逻辑,这使得环绕通知非常灵活。

4. 简单代码案例

4.1 项目结构

在这里插入图片描述

4.1 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>aop</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>3.3.2</version>
        </dependency>
    </dependencies>
</project>

4.2 UserService.java (业务逻辑类)

package org.example.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    public String registerUser(String username) {
        if (username == null) {
            throw new IllegalArgumentException("用户名不能为空");
        }
        System.out.println("正在注册用户: " + username);
        return "用户 " + username + " 注册成功";
    }
}

4.3 LoggingAspect.java (切面类)

package org.example.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    /**
     * 前置通知 (Before Advice)
     * 这个方法会在目标方法 `registerUser(String)` 执行之前执行。
     * `execution` 表达式指定了要拦截的目标方法。
     */
    @Before("execution(public void org.example.service.UserService.registerUser(String))")
    public void logBefore() {
        System.out.println("方法执行前(Before): 准备注册用户");
    }

    /**
     * 后置通知 (After Advice)
     * 这个方法会在目标方法执行之后执行,无论方法是否成功完成或抛出异常。
     * 这里的 `execution` 表达式匹配 `org.example.service` 包及其子包中的所有方法。
     */
    @After("execution(* org.example.service..*.*(..))")
    public void logAfter() {
        System.out.println("方法执行后(After): 用户注册过程完成");
    }

    /**
     * 返回通知 (After Returning Advice)
     * 这个方法会在目标方法成功返回结果之后执行。
     * `returning` 属性指定了一个参数,用来接收目标方法的返回值。
     * `execution` 表达式匹配 `UserService` 类中的所有方法。
     *
     * @param result 目标方法的返回值
     */
    @AfterReturning(pointcut = "execution(* org.example.service.UserService.*(..))", returning = "result")
    public void logAfterReturning(Object result) {
        System.out.println("方法返回(AfterReturning): " + result);
    }

    /**
     * 异常通知 (After Throwing Advice)
     * 这个方法会在目标方法抛出异常时执行。
     * `throwing` 属性指定了一个参数,用来接收目标方法抛出的异常。
     * `execution` 表达式匹配 `UserService` 类中接受一个 `String` 参数的所有方法。
     *
     * @param error 目标方法抛出的异常
     */
    @AfterThrowing(pointcut = "execution(* org.example.service.UserService.*(String, ..))", throwing = "error")
    public void logAfterThrowing(Throwable error) {
        System.out.println("方法执行时发生异常(AfterThrowing): " + error.getMessage());
    }

    /**
     * 环绕通知 (Around Advice)
     * 这个方法会包裹住目标方法的执行,即在目标方法执行前后都可以插入逻辑。
     * `execution` 表达式匹配 `org.example.service` 包中的所有返回类型为 `String` 的方法。
     *
     * @param joinPoint 提供对目标方法的访问控制
     * @return 目标方法的返回值
     * @throws Throwable 如果目标方法抛出异常,则重新抛出该异常
     */
    @Around("execution(String org.example.service.*.*(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知(Around): 方法执行前");
        Object result;
        try {
            // 执行目标方法
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            System.out.println("环绕通知: 方法执行时发生异常: " + throwable.getMessage());
            throw throwable;  // 重新抛出异常
        }
        System.out.println("环绕通知(Around): 方法执行后");
        return result;  // 返回目标方法的返回值
    }
}

4.3 AopApplication.java(测试类)

package org.example;

import org.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AopApplication implements CommandLineRunner {

    // 自动注入 UserService 实例
    @Autowired
    private UserService userService;

    // 主方法,Spring Boot 应用的入口
    public static void main(String[] args) {
        SpringApplication.run(AopApplication.class, args);
    }

    // 在应用启动后执行的方法
    @Override
    public void run(String... args) {
        // 测试用例1:注册用户 "张三"
        try {
            System.out.println(userService.registerUser("张三"));
        } catch (Exception e) {
            e.printStackTrace();  // 打印异常堆栈信息
        }
        
        System.out.println("--------------------------------------------");

        // 测试用例2:尝试注册 null 用户,触发异常
        try {
            userService.registerUser(null);
        } catch (Exception e) {
            e.printStackTrace();  // 打印异常堆栈信息
        }
    }
}

4.4 测试

测试1:

前提准备:把这部分注释掉。

@Around("execution(String org.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知(Around): 方法执行前");
        Object result;
        try {
            // 执行目标方法
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            System.out.println("环绕通知: 方法执行时发生异常: " + throwable.getMessage());
            throw throwable;  // 重新抛出异常
        }
        System.out.println("环绕通知(Around): 方法执行后");
        return result;  // 返回目标方法的返回值        
}

运行结果:

在这里插入图片描述

测试2:

前提准备:把这部分注释掉。

@Before("execution(public void org.example.service.UserService.registerUser(String))")
public void logBefore() {
        System.out.println("方法执行前(Before): 准备注册用户");
    }
    
@After("execution(* org.example.service..*.*(..))")
public void logAfter() {
        System.out.println("方法执行后(After): 用户注册过程完成");
    }    
@AfterReturning(pointcut = "execution(* org.example.service.UserService.*(..))", returning = "result")
public void logAfterReturning(Object result) {
        System.out.println("方法返回(AfterReturning): " + result);
    }
    
@AfterThrowing(pointcut = "execution(* org.example.service.UserService.*(String, ..))", throwing = "error")
public void logAfterThrowing(Throwable error) {
        System.out.println("方法执行时发生异常(AfterThrowing): " + error.getMessage());
}

运行结果:
在这里插入图片描述

5. 总结

环绕通知 VS 其他四种通知

通知类型适用场景不适用场景
环绕通知(Around)需要在方法执行前后都执行逻辑,例如记录日志或性能监控。
需要决定是否执行目标方法。
需要统一捕获并处理异常。
需要修改方法的返回值。
只需在方法前或方法后做简单的操作时,使用环绕通知会显得过于复杂。
前置通知(Before)仅在方法执行前执行逻辑,如参数验证、日志记录、权限检查等。需要在方法执行后、处理返回值或捕获异常的场景。
后置通知(After)仅在方法执行后执行逻辑,如资源清理、记录状态等。需要处理返回值或捕获异常,或在方法执行前执行逻辑的场景。
返回通知(After Returning)仅在方法成功返回结果后执行逻辑,如记录返回值或对返回值进行处理。需要捕获异常,或在方法执行前/后执行逻辑的场景。
异常通知(After Throwing)仅在方法抛出异常时执行逻辑,如记录异常信息、发送报警或执行补偿逻辑。需要在方法执行前、执行后或成功返回时执行逻辑的场景。

解释:

  • 环绕通知适用于对方法的执行有全面控制的需求,包括方法执行前、后,异常处理,以及返回值的修改。
  • 前置通知适用于只需要在方法执行前执行逻辑的场景。
  • 后置通知适用于只需要在方法执行后执行逻辑的场景。
  • 返回通知适用于只关心方法成功返回后的处理逻辑。
  • 异常通知适用于只处理方法抛出异常的情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值