Spirng篇-AOP 与 Spring 事务

4850字

AOP 与 Spring 事务 学习笔记

关于 AOP 和 Spring 事务,我理解如下:

什么是 AOP?

AOP,全称 Aspect - Oriented Programming,中文译为 “面向切面编程”。它是一种编程范式,旨在通过分离横切关注点 (Cross - Cutting Concerns) 来提高软件系统的模块化程度。

想象一下,在我们的应用程序中,有很多功能模块(比如用户管理、订单管理、商品管理等)。除了这些核心业务逻辑之外,还存在一些通用的、分散在各个模块中的功能,例如:

  • 日志记录 (Logging):记录方法的调用、参数、返回值等。
  • 权限校验 (Authorization):检查用户是否有权限执行某个操作。
  • 性能监控 (Performance Monitoring):记录方法的执行时间。
  • 事务管理 (Transaction Management):确保一组数据库操作要么全部成功,要么全部失败。

这些功能就像 “切面” 一样,横向地 “切割” 了我们纵向的业务模块。如果我们在每个业务模块中都重复编写这些代码,会导致代码冗余、难以维护。

AOP 的核心思想就是将这些横切关注点从业务逻辑中剥离出来,形成独立的模块(即 “切面”),然后通过某种方式(通常是动态代理)将这些切面 “织入” 到业务逻辑的特定连接点上。

核心概念:

  • 切面 (Aspect):一个模块,它封装了特定的横切关注点。例如,一个日志切面、一个事务切面。
  • 通知 (Advice):切面在特定连接点上执行的动作。常见的通知类型有:
    • @Before:在目标方法执行之前执行。
    • @After:在目标方法执行之后执行(无论成功还是失败)。
    • @AfterReturning:在目标方法成功执行之后执行。
    • @AfterThrowing:在目标方法抛出异常之后执行。
    • @Around:环绕目标方法执行,可以控制目标方法的执行。
  • 连接点 (Join Point):程序执行过程中的某个特定点,例如方法的调用、异常的抛出等。在 Spring AOP 中,连接点通常指方法的执行。
  • 切点 (Pointcut):一个表达式,用于匹配一个或多个连接点。它定义了通知应该在哪些连接点上执行。
  • 目标对象 (Target Object):被一个或多个切面所通知的对象。也就是包含主要业务逻辑的类。
  • 织入 (Weaving):将切面应用到目标对象并创建新的代理对象的过程。织入可以在编译时、加载时或运行时进行。Spring AOP 在运行时进行织入。
  • 代理 (Proxy):AOP 框架创建的对象,用于封装目标对象并实现切面逻辑。客户端直接与代理对象交互。

AOP 的优势:

  • 降低耦合度:将通用功能与业务逻辑分离,使代码更加清晰和易于维护。
  • 提高代码复用性:可以将通用的横切关注点封装成切面,在多个地方重复使用。
  • 增强模块化:每个模块专注于自身的核心功能。

你们项目中有没有使用到 AOP?

回答思路:

  1. 肯定回答:大概率是有的,因为 Spring 框架本身就大量使用了 AOP。
  2. 举例说明:结合项目实际情况,列举 1 - 2 个具体的应用场景,并给出简单的代码示例。
  3. 突出作用:说明 AOP 在这些场景中解决了什么问题,带来了什么好处。

回答范例:

“是的,面试官,我们项目中广泛地使用了 AOP

最典型的应用场景有两个:

  1. 统一日志记录:我们通过 AOP 实现了一个全局的日志切面。这个切面会拦截所有 Service 层方法的调用,在方法执行前后记录方法的入参、出参以及执行耗时。这样做的好处是,我们不需要在每个 Service 方法中手动编写日志记录代码,大大减少了代码冗余,并且使得日志格式和记录时机保持一致,方便后续的问题排查和性能分析。
    • 简单案例代码
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
package com.example.aop.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Arrays;

/**
 * 日志切面类
 */
@Aspect // 声明这是一个切面
@Component // 将这个切面交给Spring容器管理
public class LogAspect {

    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    /**
     * 定义一个切点表达式
     * execution(* com.example.aop.service..*.*(..))
     * - execution(): 表示执行方法时触发
     * - *: 返回任意类型
     * - com.example.aop.service..: 表示 com.example.aop.service 包及其所有子包
     * - *.: 表示包下的任意类
     * - *(..): 表示类中的任意方法名,(..) 表示任意参数
     */
    @Pointcut("execution(* com.example.aop.service..*.*(..))")
    public void serviceLayerPointcut() {
        // 这个方法本身只是一个标记,不需要具体实现
        // Pointcut 的名称就是方法名 "serviceLayerPointcut"
    }

    /**
     * 前置通知:在目标方法执行之前执行
     * @param joinPoint 连接点信息,可以获取方法名、参数等
     */
    @Before("serviceLayerPointcut()")
    public void logBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName(); // 获取方法名
        Object[] args = joinPoint.getArgs(); // 获取方法参数
        logger.info("===> @Before: Method [{}] execution starts, Args: {}", methodName, Arrays.toString(args));
    }

    /**
     * 后置通知:在目标方法执行之后执行(无论成功还是异常都会执行)
     * @param joinPoint 连接点信息
     */
    @After("serviceLayerPointcut()")
    public void logAfter(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        logger.info("===> @After: Method [{}] execution ends.", methodName);
    }

    /**
     * 返回通知:在目标方法成功执行并返回结果后执行
     * @param joinPoint 连接点信息
     * @param result 目标方法返回的结果
     */
    @AfterReturning(pointcut = "serviceLayerPointcut()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        logger.info("===> @AfterReturning: Method [{}] executed successfully, Result: {}", methodName, result);
    }

    /**
     * 异常通知:在目标方法抛出异常后执行
     * @param joinPoint 连接点信息
     * @param exception 抛出的异常对象
     */
    @AfterThrowing(pointcut = "serviceLayerPointcut()", throwing = "exception")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable exception) {
        String methodName = joinPoint.getSignature().getName();
        logger.error("===> @AfterThrowing: Method [{}] execution failed, Exception: {}", methodName, exception.getMessage());
    }

    /**
     * 环绕通知:可以控制目标方法的执行,功能最强大
     * @param proceedingJoinPoint 可执行的连接点,必须调用 proceed() 方法来执行目标方法
     * @return 目标方法的返回值
     * @throws Throwable 目标方法可能抛出的异常
     */
    @Around("serviceLayerPointcut()")
    public Object logAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        String methodName = proceedingJoinPoint.getSignature().getName();
        Object[] args = proceedingJoinPoint.getArgs();
        long startTime = System.currentTimeMillis();

        logger.info("====> @Around: Method [{}] execution starts with Args: {}", methodName, Arrays.toString(args));

        Object result = null;
        try {
            // 执行目标方法
            result = proceedingJoinPoint.proceed();
            long endTime = System.currentTimeMillis();
            logger.info("====> @Around: Method [{}] executed successfully, Result: {}, Execution time: {} ms", methodName, result, (endTime - startTime));
        } catch (Throwable throwable) {
            long endTime = System.currentTimeMillis();
            logger.error("====> @Around: Method [{}] execution failed, Exception: {}, Execution time: {} ms", methodName, throwable.getMessage(), (endTime - startTime));
            throw throwable; // 必须将异常抛出,否则外部调用者无法感知
        }
        return result;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.example.aop.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

/**
 * 示例服务类
 */
@Service // 将这个类标记为Spring的Service组件
public class MyService {

    private static final Logger logger = LoggerFactory.getLogger(MyService.class);

    public String performAction(String userName, int actionType) {
        logger.info("MyService.performAction: Executing business logic for user: {}, action: {}", userName, actionType);
        if (actionType < 0) {
            throw new IllegalArgumentException("Action type cannot be negative");
        }
        return "Action performed for " + userName + " with type " + actionType;
    }

    public void anotherSimpleMethod() {
        logger.info("MyService.anotherSimpleMethod: Executing another simple method.");
    }
}
  • 要使上述 AOP 生效,还需要在 Spring 配置类中开启 AOP 支持
1
2
3
4
5
6
7
8
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy // 开启AspectJ自动代理支持
public class AppConfig {
    // 其他配置...
}
  1. 声明式事务管理:Spring 框架提供的声明式事务管理本身就是基于 AOP 实现的。我们在 Service 层的方法上使用@Transactional注解,Spring 就会自动为这些方法应用事务管理。当方法开始执行时,AOP 会开启事务;当方法执行完毕时,根据执行结果(成功或异常)自动提交或回滚事务。这使得我们的业务代码不再需要关注事务的开启、提交和回滚等细节,更加专注于业务逻辑本身,提高了开发效率和代码的可读性
    • 概念性代码说明 (非完整运行示例,重点在 AOP 思想)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.example.aop.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; // 导入注解
// 假设有一个 UserDao 用于数据库操作
// import com.example.aop.dao.UserDao;

@Service
public class OrderService {

    // @Autowired
    // private UserDao userDao; // 假设依赖一个DAO

    /**
     * 当调用这个方法时,Spring AOP会创建一个代理。
     * 代理会在方法执行前启动事务(类似于 @Before 通知)。
     * 如果方法成功完成,代理会在方法执行后提交事务(类似于 @AfterReturning 通知)。
     * 如果方法抛出运行时异常(或其他配置的异常),代理会回滚事务(类似于 @AfterThrowing 通知)。
     */
    @Transactional // 声明此方法需要事务管理
    public void createOrder(String product, int quantity) {
        System.out.println("OrderService: Attempting to create order for " + product + " with quantity " + quantity);

        // 1. 假设这里有数据库操作:检查库存
        // userDao.checkStock(product, quantity);
        System.out.println("OrderService: Stock checked (simulated).");

        // 2. 假设这里有数据库操作:创建订单记录
        // userDao.createOrderRecord(product, quantity);
        System.out.println("OrderService: Order record created (simulated).");

        if (quantity > 100) { // 模拟一个业务异常条件
            throw new RuntimeException("Cannot order more than 100 items in a single transaction.");
        }

        // 3. 假设这里有数据库操作:扣减库存
        // userDao.decreaseStock(product, quantity);
        System.out.println("OrderService: Stock decreased (simulated).");

        System.out.println("OrderService: Order created successfully for " + product);
    }
}
  • 在这个例子中,@Transactional注解就是一个 “切点”,告诉 Spring AOP 在哪里应用事务管理的 “切面”。事务的开启、提交、回滚等逻辑就是 “通知”。

除此之外,我们可能还在权限校验缓存处理等方面考虑过或使用了 AOP 的思想,将这些非业务核心但又普遍存在的功能进行模块化管理。”

Spring 中的事务是如何实现的?

Spring 中的事务管理,尤其是声明式事务,是基于 AOP 和 ThreadLocal 实现的。

核心原理:

  1. AOP 代理
    • 当一个 Bean 被声明为需要事务管理时(例如,通过@Transactional注解),Spring 容器在创建这个 Bean 的时候,并不会直接返回原始的 Bean 实例,而是会为它创建一个代理对象 (Proxy)
    • 这个代理对象包裹 (wraps) 了原始的 Bean 实例。
    • 当客户端调用 Bean 中被@Transactional注解标记的方法时,实际上是调用了代理对象的相应方法。
  2. 事务拦截器 (TransactionInterceptor)
    • 代理对象的方法内部会包含一个事务拦截器 (例如 TransactionInterceptor)。这个拦截器就是 AOP 中的通知 (Advice)
    • 在目标业务方法执行之前 (类似@Before通知),事务拦截器会介入。
    • 它会检查当前是否存在事务,如果不存在,则根据@Transactional注解的配置(如传播行为、隔离级别、只读属性等)开启一个新的数据库事务
  3. ThreadLocal 存储数据库连接 (Connection)
    • 为了保证同一个事务中的所有数据库操作都使用同一个数据库连接,Spring 会将当前事务对应的数据库连接 (Connection) 存储在 ThreadLocal 变量中
    • ThreadLocal 为每个线程都提供了一个独立的变量副本,这意味着在一个线程的执行过程中,无论调用多少个方法,只要它们在同一个事务内,就能获取到同一个数据库连接。这解决了多线程环境下事务管理的问题,保证了事务的线程安全性。
  4. 业务方法执行
    • 开启事务后,代理对象会调用原始 Bean 实例的业务方法,执行实际的业务逻辑和数据库操作。
    • 由于数据库连接是从ThreadLocal中获取的,所以业务方法中的所有 DAO 操作都会在同一个事务上下文中执行。
  5. 事务提交或回滚
    • 当业务方法执行完毕后,事务拦截器会再次介入 (类似@AfterReturning 或 @AfterThrowing通知)。
    • 如果业务方法正常执行完成,没有抛出任何(未被捕获的、符合回滚规则的)异常,事务拦截器会提交事务
    • 如果业务方法抛出了异常 (通常是RuntimeException或其子类,或者在@Transactional中配置了rollbackFor指定的异常),事务拦截器会回滚事务
  6. 关闭连接与清理
    • 事务提交或回滚之后,事务拦截器会负责关闭数据库连接 (通常是将其归还给连接池),并清理 ThreadLocal 中存储的连接信息

总结 Spring 事务实现的关键点:

  • 基于 AOP:通过动态代理和拦截器机制,在业务代码执行前后动态地添加事务管理逻辑。
  • @Transactional 注解:提供了一种声明式的方式来定义事务的边界和属性,简化了事务管理。
  • PlatformTransactionManager 接口:Spring 事务管理的核心接口,定义了事务操作(获取事务、提交、回滚)的规范。具体的实现类(如 DataSourceTransactionManager 对应 JDBC,JpaTransactionManager 对应 JPA)负责与底层数据访问技术交互。
  • TransactionDefinition 接口:定义事务的属性,如传播行为 (Propagation Behavior)、隔离级别 (Isolation Level)、超时时间 (Timeout)、只读状态 (Read - only) 等。@Transactional注解的属性最终会转换为TransactionDefinition的配置。
  • TransactionStatus 接口:表示当前事务的状态,可以用来控制事务的提交和回滚。
  • ThreadLocal:确保了在单个线程中,事务操作使用的是同一个数据库连接,从而保证了事务的原子性和一致性。

简单案例思路 (伪代码):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 业务接口
public interface UserService {
    void A();
    void B();
}

// 业务实现类
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao; // 假设UserDao用于数据库操作

    @Override
    @Transactional // 声明式事务
    public void A() {
        userDao.updateA(); // 操作1
      
 //... 其他业务逻辑...
        // int i = 1/0; // 如果这里发生RuntimeException,事务会回滚
        userDao.updateB(); // 操作2
    }

    @Override
    public void B() { // 没有@Transactional,不使用Spring事务管理(除非被A方法调用且传播行为允许)
       userDao.updateC();
    }
}

Spring AOP 和事务管理大致流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

// 1. 客户端调用 userService.A()

// 2. 实际上调用的是UserService的代理对象 Proxy$UserService.A()

Proxy$UserService.A() {
    TransactionInterceptor interceptor =...;
    Connection conn = null;
    TransactionStatus status = null;
    try {
        // (Before Advice) 开启事务
        status = platformTransactionManager.getTransaction(transactionDefinition); // 获取事务状态,可能开启新事务
        conn = dataSource.getConnection(); // 获取连接
        conn.setAutoCommit(false); // 关闭自动提交
        ThreadLocal<Connection> currentConnection.set(conn); // 将连接绑定到当前线程

        // 调用目标方法
        targetUserServiceImpl.A(); // 真正执行业务逻辑

        // (AfterReturning Advice) 提交事务
        platformTransactionManager.commit(status); // 提交
        conn.commit();

    } catch (RuntimeException e) {
        // (AfterThrowing Advice) 回滚事务
        if (status!= null) {
            platformTransactionManager.rollback(status); // 回滚
            if (conn!= null) conn.rollback();
        }
        throw e; // 抛出异常
    } finally {
        // 清理资源
        if (conn!= null) {
            conn.setAutoCommit(true); // 恢复自动提交(如果需要)
            conn.close(); // 关闭连接(归还连接池)
        }
        ThreadLocal<Connection> currentConnection.remove(); // 清理ThreadLocal
    }
}
如对内容有异议,请联系关邮箱2285786274@qq.com修改