Spring AOP高级应用与源码剖析

Spring AOP 高级应用

AOP的本质:在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往是权限校验代码,日志代码,事务控制代码,性能监测代码。

AOP相关术语

|----------------|-------------------------------------------|
| 名词 | 解释 |
| JoinPoint(连接点) | 可以用于把增强代码加入到业务主线中的点 |
| Pointcut(切入点) | 已经把增强代码加入到业务主线进来之后的连接点 |
| Adice(通知/增强) | 切面类中用于提供增强功能的方法 |
| Target(目标对象) | 代理的目标对象。即被代理对象 |
| Proxy(代理) | 一个类被AOP植入增强后,产生的代理类 |
| Weaving(织入) | 把增强应用到目标对象来创建新的代理对象的过程 |
| Aspect(切面) | 指定增强的代码所关注的方面,把这些相关的增强代码定义到一个类中,这个类就是切面类。 |

Spring中AOP的代理选择

AOP思想使用的是动态代理。默认情况下Spring会根据代理对象是否实现接口来选择使用JDK还是CGLIB。当代理对象没有实现任何接口时,Spring会选择CGLIB。

Spring中AOP实现XML形式

maven坐标

复制代码
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.1.12.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>

Aop核心配置

复制代码
<!--
 Spring基于XML的AOP配置前期准备:
 在spring的配置⽂件中加⼊aop的约束
 xmlns:aop="http://www.springframework.org/schema/aop"
 http://www.springframework.org/schema/aop 
https://www.springframework.org/schema/aop/spring-aop.xsd 
 
 Spring基于XML的AOP配置步骤、
 第⼀步:把通知Bean交给Spring管理
 第⼆步:使⽤aop:config开始aop的配置
 第三步:使⽤aop:aspect配置切⾯
 第四步:使⽤对应的标签配置通知的类型
 ⼊⻔案例采⽤前置通知,标签为aop:before
-->
<!--把通知bean交给spring来管理-->
<bean id="logUtil" class="com.lagou.utils.LogUtil"></bean>
<!--开始aop的配置-->
<aop:config>
<!--配置切⾯-->
 <aop:aspect id="logAdvice" ref="logUtil">
 <!--配置前置通知-->
 <aop:before method="printLog" pointcut="execution(public *
com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou
.pojo.Account))"></aop:before>
 </aop:aspect>
</aop:config>

切入表达式,也称之为Aspectj切入点表达式,指的是遵循特定语法结构的字符串,其作用是用于对符合语法格式的连接点进行增强。

改变代理方式的配置:

我们知道只要不是final修饰的类都可以是cglib提供的方式来创建代理对象,spring提供了两种配置方式:

  • 使用aop:config标签配置

    <aop:config proxy-target-class="true">

  • 使用aop:aspectj-autoproxy标签配置

    <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectjautoproxy>

5中AOP通知类型:

1. 前置通知

  • 配置方式:<aop:before method="方法名" pointcut-ref="切入点引用"/>
  • 出现位置:仅在<aop:aspect>标签内
  • 执行时机:切入方法(业务核心方法)执行之前(必定执行)
  • 细节:可获取切入方法的参数并增强

2. 正常执行时通知(后置返回通知)

  • 配置方式:<aop:after-returning method="方法名" pointcut-ref="切入点引用"/>
  • 出现位置:仅在<aop:aspect>标签内
  • 执行时机:切入方法正常执行完成后(若方法抛异常则不执行)

3. 异常通知

  • 配置方式:<aop:after-throwing method="方法名" pointcut-ref="切入点引用"/>
  • 出现位置:仅在<aop:aspect>标签内
  • 执行时机:切入方法产生异常之后(无异常则不执行)
  • 细节:可获取切入方法的参数 + 异常信息

4. 最终通知

  • 配置方式:<aop:after method="方法名" pointcut-ref="切入点引用"/>
  • 出现位置:仅在<aop:aspect>标签内
  • 执行时机:切入方法执行完成后、返回之前(无论是否抛异常,必定执行)
  • 细节:可获取参数,常用于清理操作

5. 环绕通知

  • 配置方式:<aop:around method="方法名" pointcut-ref="切入点引用"/>
  • 出现位置:仅在<aop:aspect>标签内
  • 特殊说明:是 Spring 的 "特殊通知",可通过编程控制通知执行时机(区别于前四种 "指定时机" 的通知);依赖ProceedingJoinPoint接口,实现手动触发切入方法的调用

Spring中AOP实现XML+注解形式

|------------------------------------------------------------|-----------------|
| XML配置 | 注解配置 |
| <aop:before method="方法名" pointcut-ref="切入点引用"/> | @Before |
| <aop:after-returning method="方法名" pointcut-ref="切入点引用"/> | @AfterReturning |
| <aop:after-throwing method="方法名" pointcut-ref="切入点引用"/> | @AfterThrowing |
| <aop:after method="方法名" pointcut-ref="切入点引用"/> | @After |
| <aop:around method="方法名" pointcut-ref="切入点引用"/> | @Around |

Spring中AOP实现注解形式

在我们使用注解开发时候要用注解替换掉配置文件

复制代码
<!--开启spring对注解aop的⽀持-->
<aop:aspectj-autoproxy/>

需要在启动类上面添加

复制代码
@EnableAspectJAutoProxy //开启spring对注解AOP的⽀持

Spring声明式事务的支持

编程式事务:在业务代码中添加事务控制代码,这样的事务控制机制叫做编程式事务。

声明式事务:通过xml或者注解配置的方式达到事务控制的目的,叫做声明式事务。

事务的四大特性:

  1. 原子性:原子性式指事务是一个不可分割的工作单位,事务中的操作要么全部发生,要么全部不发生。
  2. 一致性:事务必须使数据库从一个一致性状态变换成另一个一致性状态。
  3. 隔离性:事务的隔离性式多个用户并发访问数据库时,数据库未每一个用户开启的事务,每个事务不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
  4. 持久性:持久性时指一个事务一旦被提交,它对数据中数据的改变就是永久性的,接下来即使数据库发送故障也不应该对其有任何影响。

不考虑事务隔离级别会出现的问题:

  1. 脏读:一个线程中的事务读到另一个线程中未提交的数据。
  2. 不可重复读:一个线程的事务读到另一个线程中已经提交的update的数据(前后内容不一样)。
  3. 幻读:一个线程中的事务读取到了另一个线程中已经提交的insert或者delete的数据(前后条数不一样)。

数据库中定义的四种隔离级别(从高到低):

  1. Serializable(串行化):避免脏读,不可重复读,虚读情况的发生。
  2. Repeatable read(可重复读):可避免脏读,不可重复读情况发生。
  3. Read committed(读已提交):可避免脏读情况发生。
  4. Read uncommitted(读未提交):以上情况都没有保证。

Mysql默认的事务隔离级别是:读已提交。

查询当前使用的隔离级别:SELECT @@session.transaction_isolation;

设置Mysql事务的隔离级别:(仅仅是当前会话的隔离级别)SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

|-----------------|------|---------------------------------------------------------|------------------|
| 传播行为常量 | 中文名称 | 核心规则 | 适用场景 |
| REQUIRED (默认) | 必需的 | 如果当前有事务,加入该事务;如果无事务,新建事务。 | 绝大多数业务(如订单 + 库存) |
| SUPPORTS | 支持的 | 如果当前有事务,加入;无事务,则以非事务方式执行。 | 查询类方法(可选事务) |
| MANDATORY | 强制的 | 必须在已有事务中执行;无事务则抛异常(IllegalTransactionStateException)。 | 核心子操作(必须依赖外层事务) |
| REQUIRES_NEW | 新建的 | 无论当前是否有事务,都新建独立事务;原有事务暂停,新事务执行完后恢复。 | 日志记录、消息发送(独立事务) |
| NOT_SUPPORTED | 不支持的 | 以非事务方式执行;若当前有事务,暂停原有事务。 | 纯查询(无需事务,提升性能) |
| NEVER | 从不 | 必须以非事务方式执行;若当前有事务,抛异常。 | 绝对禁止事务的操作 |
| NESTED | 嵌套的 | 基于当前事务创建嵌套子事务(Savepoint 实现);子事务回滚不影响外层,外层回滚则全滚。 | 批量操作(部分失败可回滚) |

纯xml方式

复制代码
<!-- 配置事务管理器(基于JDBC数据源) -->
<bean id="transactionManager" 
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/> <!-- 引用已配置的数据源 -->
</bean>

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- 全局默认配置:所有方法默认非只读,传播行为REQUIRED -->
        <tx:method name="*" read-only="false" 
                   propagation="REQUIRED" isolation="DEFAULT" timeout="-1"/>
        
        <!-- 针对查询方法的单独配置:只读,传播行为SUPPORTS -->
        <tx:method name="query*" read-only="true" propagation="SUPPORTS"/>
    </tx:attributes>
</tx:advice>

<aop:config>
    <!-- 切入点:匹配TransferServiceImpl类的所有方法 -->
    <aop:advisor advice-ref="txAdvice" 
                 pointcut="execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))"/>
</aop:config>

xml+注解方式

复制代码
<!-- 开启Spring注解事务的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)

纯注解方式

需要在启动类上加入@EnableTransactionManagement

相关推荐
奋进的芋圆30 分钟前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin1 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model20051 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉1 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国1 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_941882482 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈2 小时前
两天开发完成智能体平台
java·spring·go
alonewolf_992 小时前
Spring MVC重点功能底层源码深度解析
java·spring·mvc
沛沛老爹2 小时前
Java泛型擦除:原理、实践与应对策略
java·开发语言·人工智能·企业开发·发展趋势·技术原理
专注_每天进步一点点2 小时前
【java开发】写接口文档的札记
java·开发语言