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或者注解配置的方式达到事务控制的目的,叫做声明式事务。
事务的四大特性:
- 原子性:原子性式指事务是一个不可分割的工作单位,事务中的操作要么全部发生,要么全部不发生。
- 一致性:事务必须使数据库从一个一致性状态变换成另一个一致性状态。
- 隔离性:事务的隔离性式多个用户并发访问数据库时,数据库未每一个用户开启的事务,每个事务不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
- 持久性:持久性时指一个事务一旦被提交,它对数据中数据的改变就是永久性的,接下来即使数据库发送故障也不应该对其有任何影响。
不考虑事务隔离级别会出现的问题:
- 脏读:一个线程中的事务读到另一个线程中未提交的数据。
- 不可重复读:一个线程的事务读到另一个线程中已经提交的update的数据(前后内容不一样)。
- 幻读:一个线程中的事务读取到了另一个线程中已经提交的insert或者delete的数据(前后条数不一样)。
数据库中定义的四种隔离级别(从高到低):
- Serializable(串行化):避免脏读,不可重复读,虚读情况的发生。
- Repeatable read(可重复读):可避免脏读,不可重复读情况发生。
- Read committed(读已提交):可避免脏读情况发生。
- 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