Day03:
AOP(面向切面编程):
(Aspect Oriented Programming)
概述:
使用【动态代理】技术,实现在【不修改java源代码】的情况下,运行时实现方法功能的【增强】
优势及使用场景:
- 非侵入性: 使用动态代理技术,在不修改源码的情况下对象已有的方法功能进行增强
- 高内聚: 集中处理明个关注点 方便维护
- 易移植: 可以方便的总价,删除,修改 某一个关注点的切入业务
AOP实现原理:
AOP的底层实现需要依赖于动态代理技术;
动态代理就是在程序运行期间,不修改源码对已有方法进行增强。
AOP相关术语:
-
Joinpoint(连接点):
在spring中,连接点指的都是方法(指的是那些要被增强功能的候选方法) -
Pointcut(切入点):
切入点是指我们要对哪些Joinpoint进行拦截的定义。 -
Advice(通知):
通知指的是拦截到Joinpoint之后要做的事情。即增强的功能
通知类型:前置通知、后置通知、异常通知、最终通知、环绕通知 -
Target(目标对象):
被代理的对象。 -
Proxy(代理对象):
一个类被AOP织入增强后,即产生一个结果代理类。 -
Weaving(织入):
织入指的是把增强用于目标对象。创建代理对象的过程 -
Aspect(切面):
切面指的是切入点和通知的结合`
基于XML的Spring-AOP:
属性 | 说明 |
---|---|
< aop:config > | 声明AOP |
<aop:aspect id=“唯一标识” ref=“通知bean的ID”> | 声明切面 |
<aop:pointcut id=“连接点ID” expression=“连接点表达式”>< /aop:pointcut > | 声明连接点 |
<aop:before method=“通知方法” pointcut-ref=“连接点ID”>< /aop:before > | 前置通知 |
<aop:after-returning method=“通知方法” pointcut-ref=“连接点ID”>< /aop:after-returning > | 后置通知 |
<aop:after-throwing method=“通知方法” pointcut-ref=“连接点ID”>< /aop:after-throwing > | 异常通知 |
<aop:after method=“通知方法” pointcut-ref=“连接点ID”>< /aop:after > | 最终通知 |
<aop:around method=“通知方法” pointcut-ref=“连接点ID”>< /aop:around > | 环绕通知 |
JoinPoint 对象:
JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象
方法名 | 功能 |
---|---|
Signature getSignature(); | 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,Class等信息 |
Object[] getArgs(); | 获取传入目标方法的参数对象 |
Object getTarget(); | 获取被代理的对象 |
Object getThis(); | 获取代理对象 |
切点表达式
【1】execution
表达式语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
【2】对象匹配
语法:
bean(accountService) //匹配名为accountService的bean
bean(*Service) //匹配Spring容器中所有以Service结尾的bean
通知类型
1、前置通知:
在目标方法执行前执行
2、后置正常通知:
在目标方法正常返回后执行。它和异常通知只能执行一个
3、异常通知:
在目标方法发生异常后执行。它和后置通知只能执行一个
4、最终通知:
无论目标方法正常返回,还是发生异常都会执行
5、环绕通知:
在目标方法执行前后执行该增强方法
XML && Annotation 配置对比:
xml | 注解 | 说明 |
---|---|---|
<aop:aspectj-autoproxy /> | @EnableAspectJAutoProxy | 开启注解AOP |
<aop:aspect > | @Aspect | 声明切面 |
<aop:before > | @Before | 前置通知 |
<aop:after-returning > | @AfterReturning | 后置正常通知 |
<aop:after-throwing > | @AfterThrowing | 后置异常通知 |
<aop:after > | @After | 最终通知 |
<aop:around > | @Around | 环绕通知 |
JDK 动态代理:
jdk动态代理:
必须基于接口
java.lang.reflect.Proxy:
Java动态代理机制的主类,提供了一组静态方法来为一组接口动态地生成代理类及其实例。
//方法1: 该方法用于获取指定动态代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy)
//方法2:该方法用于获取关联于指定类装载器和一组接口的动态代理对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
//方法3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl)
//方法4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理对象:1、类加载器 2、接口数组、调用处理器(增强部分的业务代码)
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
java.lang.reflect.InvocationHandler:
调用处理器接口,它自定义了一个invoke方法,用于集中处理在动态代理对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理对象时都需要指定一个实现了该接口的调用处理器对象。
InvocationHandler的核心方法:
//该方法负责集中处理动态代理类上的所有方法调用。
//第一个参数是代理对象,第二个参数是被调用的方法对象,第三个方法是调用参数。
//调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行。
Object invoke(Object proxy, Method method, Object[] args)
CGlib动态代理:
1、Cglib动态代理:基于类,无需实现接口;
2、被代理的目标类不能被final修饰
net.sf.cglib.proxy.Enhancer
Enhancer类是CGLib中的一个字节码增强器,作用用于生成代理对象,跟上一章所学的Proxy类相似,常用方式为:
//方法1:该方法用于为指定目标类、回调对象 1、类的类型,2、调用处理器
public static Object create(Class type, Callback callback)
net.sf.cglib.proxy.MethodInterceptor
//方法1:
Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
Spring事务管理:
事务的定义:
是数据库操作的最小单元;
事务是一组不可再分割的操作集合;这些操作作为整体向系统提交,要么都执行,要么都不执行
事务的ACID原则:
- 原子性: 一个事务已经是一个不可再分割的工作单位。事务中的全部操作要么都做;要么都不做
- 一致性: 事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定
- 隔离性:务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性 和完整性。同时,并行事务的修改必须与其他并行事务的修改相互独立。
- 持久性:一个事务一旦提交,它对数据库中数据的改变会永久存储起来。其他操作不会对它产生影响
事务隔离级别:
隔离级别解决的问题:
- 脏读: 在一个事务中读取到了另外一个事务修改的【未提交的数据】,而导致多次读取同一个数据返回的结果不一致 (必须要解决的)
- 不可重复读:在一个事务中读取到了另外一个事务修改的【已提交的数据】,而导致多次读取同一个数据返回的结果不一致
- 幻读: 一个事务读取了几行记录后,另一个事务插入一些记录,幻读就发生了。再后来的查询中,第一个事务就会发现有些原来没有的记录
隔离级别 | 说明 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
ISOLATION_DEFAULT | spring默认数据库的隔离级别 | – | – | – |
ISOLATION_READ_UNCOMMITTED | 读未提交 | √ | √ | √ |
ISOLATION_READ_COMMITTED | 读已提交 | × | √ | √ |
ISOLATION_REPEATABLE_READ | 可重复读 | × | × | √ |
ISOLATION_SERIALIZABLE | 序列化操作 | × | × | × |
隔离级别由低到高【读未提交】=>【读已提交】=>【可重复读】=>【序列化操作】
大多数数据库: READ_COMMITTED(读已提交)
MySQL 默认采用: REPEATABLE_READ(可重复读)
Spring的隔离级别默认数据库的隔离级别: ISOLATION_DEFALUT
事务的传播行为:
指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
传播行为 | 说明 |
---|---|
REQUIRED[默认] | 当前如果有事务,Spring就会使用该事务;否则会开始一个新事务(增、删、改) |
SUPPORTS | 当前如果有事务,Spring就会使用该事务;否则不会开始一个新事务(查询) |
MANDATORY | 当前如果有事务,Spring就会使用该事务;否则会抛出异常 |
REQUIRES_NEW | 当前如果有事务,把当前事务挂起,新建事务 |
NOT_SUPPORTED | 当前有事务,Spring也会在非事务环境下执行。如果当前有事务,则该事务挂起 |
NEVER | 当前有事务,Spring也会在非事务环境下执行。如果当前有事务,则抛出异常 |
NESTED | 当前有事务,则在嵌套事务中执行。如果没有,那么执行情况与REQUIRED一样 |
事务的超时时间:
int TIMEOUT_DEFAULT = -1
以秒为单位进行设置。如果设置为-1(默认值),表示没有超时限制。在企业项目中使用默认值即可
是否只读:
boolean isReadOnly(): true为只读 false为读写
Spring事务API:
- PlatformTransactionManager(接口):
[事务平台管理器]:是一个接口,定义了获取事务、提交事务、回滚事务的接口
public interface PlatformTransactionManager {
//根据事务定义TransactionDefinition,获取事务
TransactionStatus getTransaction(TransactionDefinition definition);
//提交事务
void commit(TransactionStatus status);
//回滚事务
void rollback(TransactionStatus status);
}
PlatformTransactionManager实现类:
PlatformTransactionManager的实现类 | 说明 |
---|---|
org.springframework.jdbc.datasource.DataSourceTransactionManager | DataSource 数据源的事务 |
org.springframework.orm.hibernateX.HibernateTransactionManager | Hibernate 事务管理器。 |
org.springframework.orm.jpa.JpaTransactionManager | JPA 事务管理器 |
org.springframework.transaction.jta.JtaTransactionManager | 多个数据源的全局事务管理器 |
org.springframework.orm.jdo.JdoTransactionManager | JDO 事务管理器 |
- TransactionDefinition (接口)
[事务定义信息]:是一个接口,定义了事务隔离级别、事务传播行为、事务超时时间、事务是否只读
public interface TransactionDefinition {
/**********************事务传播行为类型常量***********************************/
//事务传播行为类型:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
int PROPAGATION_REQUIRED = 0;
//事务传播行为类型:支持当前事务,如果当前没有事务,就以非事务方式执行。
int PROPAGATION_SUPPORTS = 1;
//事务传播行为类型:当前如果有事务,Spring就会使用该事务;否则会抛出异常
int PROPAGATION_MANDATORY = 2;
//事务传播行为类型:新建事务,如果当前存在事务,把当前事务挂起。
int PROPAGATION_REQUIRES_NEW = 3;
//事务传播行为类型:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
int PROPAGATION_NOT_SUPPORTED = 4;
//事务传播行为类型:即使当前有事务,Spring也会在非事务环境下执行。如果当前有事务,则抛出异常
int PROPAGATION_NEVER = 5;
//事务传播行为类型:如果当前存在事务,则在嵌套事务内执行。
int PROPAGATION_NESTED = 6;
/**********************事务隔离级别常量***********************************/
//MySQL默认采用ISOLATION_REPEATABLE_READ,Oracle采用READ__COMMITTED级别。)
//隔离级别:默认的隔离级别()
int ISOLATION_DEFAULT = -1;
//隔离级别:读未提交(最低)
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
//隔离级别:读提交
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
//隔离级别:可重复度
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
//隔离级别:序列化操作(最高)
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
//默认事务的超时时间
int TIMEOUT_DEFAULT = -1;
//获取事务的传播行为
int getPropagationBehavior();
//获取事务的隔离级别
int getIsolationLevel();
//获取超时时间
int getTimeout();
//是否只读
boolean isReadOnly();
//事务名称
String getName();
}
声明式事务:
建立在AOP之上的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明即可
- spring-tx 标签说明:
属性 | 说明 |
---|---|
<aop:advisor advice-ref=“txAdvice” pointcut-ref=“切点ID”></aop:advisor > | 建立事务通知与切点关系 |
<tx:advice id=“txAdvice” transaction-manager=“transactionManager”> | 配置通知 |
<tx:attributes > | 声明事务属性 |
<tx:method name=“transfer*” propagation=“REQUIRED” read-only=“false”/> | name:方法名称可以使用通配符* isolation:事务隔离级别 propagation:事务传播行为 read-only:是否只读事务 timeout:配置事务超时 rollback-for:发生异常时,回滚 no-rollback-for:发生异常时不回滚 |
导入spring-tx依赖
<!--spring tx 相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
基于XML配置 spring-tx配置
(1)配置一个事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入DataSource -->
<property name="dataSource" ref="druidDataSource"></property>
</bean>
(2)配置事务通知增强
<!--spring-tx的增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--tx:method标签:配置业务方法的名称规则,说明:
name:方法名称规则,可以使用通配符*
isolation:事务隔离级别,使用默认值即可
propagation:事务传播行为,增/删/改方法使用REQUIRED。查询方法使用SUPPORTS。
read-only:是否只读事务。增/删/改方法使用false。查询方法使用true。
timeout:配置事务超时。使用默认值-1即可。永不超时。
rollback-for:发生某种异常时,回滚;发生其它异常不回滚。没有默认值,任何异常都回滚
no-rollback-for:发生某种异常时不回滚;发生其它异常时回滚。没有默认值,任何异常都回滚。
-->
<tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
(3)配置AOP依赖(建立事务通知增强与切点方法之间关系)
<aop:pointcut id="txAdvicePointcut" expression="execution(* com.itheima.spring.service.impl.*.*(..))"></aop:pointcut>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txAdvicePointcut"></aop:advisor>
基于Annotation 配置
- @Transactional注解
使用范围: 接口 接口方法 类以及类方法(公共方法pubic)
当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性
同时,我们也可以在方法级别使用该标注来覆盖类级别的定义
开启事务:
注解 | xml | 说明 |
---|---|---|
@EnableTransactionManagement | < tx:annotation-driven></tx:annotation-driven > | 开始事务 |
属性介绍:
属性 | 类型 | 说明 |
---|---|---|
value | String | 可选的限定描述符,指定使用的事务管理器 |
propagation | enum: Propagation | 可选的事务传播行为设置 |
isolation | enum: Isolation | 可选的事务隔离级别设置 |
readOnly | boolean | 读写或只读事务,默认读写 |
timeout | int (in seconds granularity) | 事务超时时间设置 |
rollbackFor | Class对象数组,必须继承自Throwable | 导致事务回滚的异常类数组 |
rollbackForClassName | 类名数组,必须继承自Throwable | 导致事务回滚的异常类名字数组 |
noRollbackFor | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组 |
noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的异常类名字数组 |