2025年11月23日

[手写 MiniSpring 框架教程] 第 09 讲:AOP 基础概念

📖 本讲目标

  • 理解 AOP 的核心概念
  • 理解切面、切点、通知的概念
  • 学习 JDK 动态代理原理
  • 了解 AOP 的应用场景

🎯 什么是 AOP?

AOP 的定义

AOP(Aspect-Oriented Programming,面向切面编程) 是一种编程范式,它允许将横切关注点(cross-cutting concerns)从业务逻辑中分离出来。

横切关注点

横切关注点是指那些在多个模块中重复出现的功能,例如:

  • 日志记录:在多个方法中都需要记录日志
  • 事务管理:多个方法都需要事务控制
  • 性能监控:需要统计方法执行时间
  • 安全检查:需要验证权限
  • 异常处理:统一的异常处理逻辑

传统方式的问题

传统方式(代码耦合):

public class UserService {
    public void saveUser(User user) {
        // 日志记录
        System.out.println("开始保存用户: " + user.getName());

        try {
            // 业务逻辑
            userRepository.save(user);

            // 日志记录
            System.out.println("用户保存成功");
        } catch (Exception e) {
            // 异常处理
            System.err.println("保存用户失败: " + e.getMessage());
            throw e;
        }
    }
}

问题

  • 业务逻辑和横切关注点混合在一起
  • 代码重复,难以维护
  • 修改横切关注点需要修改所有相关方法

AOP 方式

AOP 方式(关注点分离):

// 业务代码
public class UserService {
    public void saveUser(User user) {
        userRepository.save(user);  // 只关注业务逻辑
    }
}

// 切面代码
@Aspect
@Component
public class LoggingAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore() {
        System.out.println("方法执行前...");
    }
}

优势

  • 业务逻辑清晰
  • 横切关注点集中管理
  • 易于维护和扩展

🎭 AOP 核心概念

1. 切面(Aspect)

切面是横切关注点的模块化。一个切面可以包含多个通知和切点。

@Aspect
@Component
public class LoggingAspect {
    // 切面包含多个通知
}

2. 切点(Pointcut)

切点是匹配连接点的表达式,用于确定在哪些方法上应用通知。

@Before("com.example.service.UserService.*")  // 切点表达式
public void logBefore() {
    // ...
}

3. 通知(Advice)

通知是在切点上执行的动作。常见的通知类型:

  • @Before:前置通知,在方法执行前执行
  • @After:后置通知,在方法执行后执行(无论是否抛出异常)
  • @AfterReturning:返回通知,在方法正常返回后执行
  • @AfterThrowing:异常通知,在方法抛出异常后执行
  • @Around:环绕通知,可以控制方法的执行

4. 连接点(Join Point)

连接点是程序执行过程中的一个点,例如方法调用、异常抛出等。

5. 目标对象(Target)

目标对象是被代理的原始对象。

6. 代理对象(Proxy)

代理对象是 AOP 框架创建的对象,用于拦截方法调用。

🔄 AOP 工作原理

代理模式

AOP 基于代理模式实现:

客户端调用
    ↓
代理对象(Proxy)
    ↓
前置通知(@Before)
    ↓
目标对象(Target)
    ↓
后置通知(@After)
    ↓
返回结果

JDK 动态代理

JDK 动态代理是 Java 提供的代理机制,它要求目标对象必须实现接口。

工作原理

  1. 创建代理类:在运行时动态创建代理类
  2. 实现接口:代理类实现目标对象的所有接口
  3. 拦截调用:通过 InvocationHandler 拦截方法调用
  4. 执行通知:在方法调用前后执行通知

代码示例

// 接口
public interface IUserService {
    void saveUser(User user);
}

// 实现类
public class UserService implements IUserService {
    public void saveUser(User user) {
        System.out.println("保存用户: " + user.getName());
    }
}

// 代理处理器
public class LoggingHandler implements InvocationHandler {
    private Object target;

    public LoggingHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置通知
        System.out.println("方法执行前: " + method.getName());

        // 调用目标方法
        Object result = method.invoke(target, args);

        // 后置通知
        System.out.println("方法执行后: " + method.getName());

        return result;
    }
}

// 创建代理对象
IUserService proxy = (IUserService) Proxy.newProxyInstance(
    UserService.class.getClassLoader(),
    new Class[]{IUserService.class},
    new LoggingHandler(new UserService())
);

// 使用代理对象
proxy.saveUser(new User("张三"));

CGLIB 代理

CGLIB 代理通过继承目标类创建子类来实现代理,不需要接口。

注意:本教程只实现 JDK 动态代理,不实现 CGLIB。

📋 AOP 应用场景

1. 日志记录

@Aspect
@Component
public class LoggingAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("调用方法: " + joinPoint.getSignature().getName());
    }
}

2. 性能监控

@Aspect
@Component
public class PerformanceAspect {
    @Around("execution(* com.example.service.*.*(..))")
    public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        System.out.println("方法执行时间: " + (end - start) + "ms");
        return result;
    }
}

3. 事务管理

@Aspect
@Component
public class TransactionAspect {
    @Around("@annotation(Transactional)")
    public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        // 开启事务
        beginTransaction();
        try {
            Object result = joinPoint.proceed();
            // 提交事务
            commitTransaction();
            return result;
        } catch (Exception e) {
            // 回滚事务
            rollbackTransaction();
            throw e;
        }
    }
}

4. 安全检查

@Aspect
@Component
public class SecurityAspect {
    @Before("@annotation(RequiresPermission)")
    public void checkPermission(JoinPoint joinPoint) {
        // 检查权限
        if (!hasPermission()) {
            throw new SecurityException("没有权限");
        }
    }
}

🎯 AOP 的优势

1. 关注点分离

业务逻辑和横切关注点分离,代码更清晰。

2. 代码复用

横切关注点可以在多个地方复用,避免代码重复。

3. 易于维护

修改横切关注点只需要修改切面代码。

4. 灵活配置

可以通过配置决定哪些方法应用切面。

⚠️ AOP 的局限性

1. 只能代理接口方法

JDK 动态代理只能代理接口方法,不能代理具体类的方法。

2. 性能开销

代理对象的方法调用有一定的性能开销。

3. 调试困难

代理对象的存在可能使调试变得困难。

📝 本讲总结

本讲我们学习了:

  1. ✅ AOP 的核心概念和定义
  2. ✅ 切面、切点、通知的概念
  3. ✅ JDK 动态代理的工作原理
  4. ✅ AOP 的应用场景
  5. ✅ AOP 的优势和局限性

🎯 关键点回顾

  1. AOP:面向切面编程,分离横切关注点
  2. 切面:横切关注点的模块化
  3. 切点:匹配连接点的表达式
  4. 通知:在切点上执行的动作
  5. 代理模式:AOP 的实现基础

🚀 下一讲预告

在下一讲中,我们将实现 AOP 功能,包括:

  • @Aspect 切面类
  • @Before/@After 通知
  • 切点表达式匹配
  • 代理对象创建

准备好了吗?让我们继续 第 10 讲:AOP 实现


思考题

  1. AOP 和 OOP 有什么区别和联系?
  2. 为什么 JDK 动态代理需要接口?
  3. 如何选择合适的通知类型?

“以书为舟,遨游尘世”,
最好的免费 kindle 电子书分享站:

You may also like...

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注


*