Spring AOP
1、介绍:
AOP:Aspect Oriented Programming,面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。
OOP引入封装、继承、多态等概念来建立一种对象层次结构
AOP则利用一种称为“横切”的技术,剖开对象内部,并将公共行为封装到可重用模块,从而减少重复代码,降低耦合。
- JDK动态代理
Spring默认使用JDK的动态代理实现AOP,类如果实现了接口,Spring就会使用这种方式实现动态代理。
JDK实现动态代理需要两个组件,首先第一个就是InvocationHandler接口。
我们在使用JDK的动态代理时,需要编写一个类,去实现这个接口,然后重写invoke方法,这个方法其实就是我们提供的代理方法。
然后JDK动态代理需要使用的第二个组件就是Proxy这个类,我们可以通过这个类的newProxyInstance方法,返回一个代理对象。
生成的代理类实现了原来那个类的所有接口,并对接口的方法进行了代理,我们通过代理对象调用这些方法时,底层将通过反射,调用我们实现的invoke方法。
- JDK动态代理优缺点
优点:
- 简单易用:使用Java标准库提供的动态代理 API,可以方便地创建代理对象,无需手动编写大量的代理代码。
2. 可扩展性:通过实现InvocationHandler接口,可以自定义代理对象的行为,增加额外的逻辑。
3. 松耦合:代理对象和被代理对象之间的关系是松耦合的,可以动态地改变代理对象,无需修改被代理对象的源代码。
4. 高效性:JDK动态代理基于接口实现,生成的代理类是通过字节码生成技术生成的,执行效率往往较高。
缺点:
1. 只能代理接口:JDK动态代理只能代理接口,无法代理类或者静态方法,如果需要代理类或者静态方法,就需要使用其他代理方式,如CGLIB。
2. 需要接口:被代理的对象必须实现接口,否则无法使用JDK动态代理。
3. 限制于public方法:JDK动态代理只能代理目标对象中的公共方法,对于private、protected或者默认访问权限的方法无法代理。
4. 创建代理对象开销较大:JDK动态代理在运行时使用反射机制生成代理类,相比静态代理来说,创建代理对象的开销较大。
- AOP 实现比较
2、SpringAOP使用
(1)AOP术语:
- 通知(Advice):定义了切点处所要执行的程序代码以及执行时机
- 连接点(Join point):在应用执行过程中满足切点范围的具体的点
- 切点(Poincut):定义切面插入在哪些方法上,确定切面使用范围
- 切面(Aspect):指封装横切到系统功能的类,包含通知和切点
- 织入(Weaving):把切面插入到目标对象上
Spring配置文件的头部:
Spring配置文件中加入AOP切面配置:
说明:
- 定义通知类bean
- 通过aop:config标签配置所有的切面
- 通过aop:pointcut标签配置切点,使用切点表达式来指定范围
- 通过aop:aspect定义切面,指定引用的通知以及通知的类型和切点
- 通知根据通知的时机区分有五种类型:
- 前置通知,使用aop:before标签,在方法之前执行
- 后置通知,使用aop:after标签,在方法之后执行,无论方法内部是否抛出异常
- 后置返回通知,使用aop:after-returning标签,在方法之后执行并且方法内部不能抛出异常
- 后置异常通知,使用aop:after-throwing标签,在方法内部抛出异常时执行
- 环绕通知,使用aop:around标签,在方法之前和之后都执行
- 前四种通知的方法里面我们可以通过JoinPoint参数获取连接点信息,比如参数信息等,方便进行处理
- 环绕通知的方法里我们通过ProceedingJoinPoint参数来获取连接点信息
(2)AOP的应用场景
- 日志记录
- 事务管理
- 权限验证
- 性能监测
(3)Spring AOP三种使用方式
1、定义普通业务组件
2、定义切入点,一个切入点可能横切多个业务组件
3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。
3、注解方式使用AOP
- 在spring配置文件中开启aop注解方式 aop:aspectj-autoproxy/aop:aspectj-autoproxy
- 使用@Aspect定义切面,使用@Pointcut定义切点,使用@Before、@After、@Around、@AfterReturning、@AfterThrowing定义通知的应用时机
Spring AOP 所支持的 AspectJ 切点指示器
在spring中尝试使用AspectJ其他指示器时,将会抛IllegalArgumentException异常。
当我们查看上面展示的这些spring支持的指示器时,注意只有execution指示器是唯一的执行匹配,而其他的指示器都是用于限制匹配的。这说明execution指示器是我们在编写切点定义时最主要使用的指示器,在此基础上,我们使用其他指示器来限制所匹配的切点。
下图的切点表达式表示当Instrument的play方法执行时会触发通知。
我们使用execution指示器选择Instrument的play方法,方法表达式以 * 号开始,标识我们不关心方法的返回值类型。然后我们指定了全限定类名和方法名。对于方法参数列表,我们使用 .. 标识切点选择任意的play方法,无论该方法的入参是什么。
多个匹配之间我们可以使用链接符 &&、||、!来表示 “且”、“或”、“非”的关系。但是在使用 XML 文件配置时,这些符号有特殊的含义,所以我们使用 “and”、“or”、“not”来表示。
Spring事务管理
1、Spring事务管理概述
什么是Spring的事务管理?
在实际开发中,操作数据库时都会涉及到事务管理问题,为此Spring提供了专门用于事务处理的API。Spring的事务管理简化了传统的事务管理流程,并且在一定程度上减少了开发者的工作量。
事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作就是:
- 将小明的余额减少 1000 元。
- 将小红的余额增加 1000 元。
万一在这两个操作之间突然出现错误比如银行系统崩溃或者网络故障,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
事务有四大特性(ACID)
1.原子性(Atomicity)事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
2.一致性(Consistency)事务在完成时,必须是所有的数据都保持一致状态。
3.隔离性(Isolation)并发事务执行之间无影响,在一个事务内部的操作对其他事务是不产生影响,这需要事务隔离级别来指定隔离性。
4.持久性(Durability)一旦事务完成,数据库的改变必须是持久化的。
在企业级应用中,多用户访问数据库是常见的场景,这就是所谓的事务的并发。
事务并发所可能存在的问题:
1.脏读:一个事务读到另一个事务未提交的更新数据。
2.不可重复读:一个事务两次读同一行数据,可是这两次读到的数据不一样。
3.幻读:一个事务执行两次查询,但第二次查询比第一次查询多出了一些数据行。
4.丢失更新:撤消一个事务时,把其它事务已提交的更新的数据覆盖了。
2、事务管理的核心接口
在Spring的所有JAR包中,包含一个名为spring-tx-5.3.9的JAR包,该包就是Spring提供的用于事务管理的依赖包。在该JAR包的org.springframework.transaction包中,有3个接口文件PlatformTransactionManager、TransactionDefinition和TransactionStatus
1.Platform TransactionManager
PlatformTransactionManager接口是Spring提供的平台事务管理器,主要用于管理事务。该接口中提供了三个事务操作的方法,具体如下:
TransactionStatus getTransaction(TransactionDefinition definition);(用于获取事务以及状态信息)
- void commit(TransactionStatus status);(用于提交事务)
- void rollback(TransactionStatus status);(用于回滚事务)
PlatformTransactionManager接口只是代表事务管理的接口,并不知道底层是如何管理事务的,具体如何管理事务则由它的实现类来完成。该接口常见的几个实现类如下:
- org.springframework.jdbc.datasource.DataSourceTransactionManager(用于配置JDBC数据源的事务管理器)
- org.springframework.orm.hibernate4.HibernateTransactionManager(用于配置Hibernate的事务管理器)
- org.springframework.transaction.jta.JtaTransactionManager(用于配置全局事务管理器)
当底层采用不同的持久层技术时,系统只需使用不同PlatformTransactionManager实现类即可。
2.TransactionDefinition
TransactionDefinition接口是事务定义(描述)的对象,该对象中定义了事务基本属性,并提供了获取事务基本属性的方法,具体如下:
- String getName( ); (获取事务对象名称)
- int getIsolationLevel( );(获取事务的隔离级别)
- int getPropagationBehavior( );(获取事务的传播行为)
- int getTimeout( );(获取事务的超时时间)
- boolean isReadOnly( );(获取事务是否只读)
DefaultTransactionDefinition是Spring提供的TransactionDefinition接口的默认实现类,该类定义的事务规则如下:
public class DefaultTransactionDefinition implements TransactionDefinition, Serializable {
private int propagationBehavior = PROPAGATION_REQUIRED;
private int isolationLevel = ISOLATION_DEFAULT;
private int timeout = TIMEOUT_DEFAULT;
private boolean readOnly = false;
//略
}
也可以通过该类对象的set方法为事务设置其他的属性值。
3.TransactionStatus
TransactionStatus接口是事务的状态,它描述了某一时间点上事务的状态信息。该接口中包含6个方法,具体如下:
- void flush(); (刷新事务)
- boolean hasSavepoint();(获取是否存在保存点)
- boolean isCompleted(); (获取事务是否完成)
- boolean isNewTransaction();(获取是否为新事务)
- boolean isRollbackOnly(); (获取事务是否回滚)
- void setRollbackOnly();(设置事务回滚)
- 事务的基本事务属性
- 传播行为(propagation behavior):当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。Spring定义了七种传播行为
- 隔离级别(isolation level):定义了一个事务可能受其他并发事务的影响程度。多个事务并发运行,经常会操作相同的数据来完成各自的任务,可能会出现脏读,不可重复读和幻读的问题。隔离级别有四种
- 是否只读(isReadOnly):如果一个方法内都是对数据库的select操作,那么可以设置方法事务为只读,数据库也会对该事务进行特定的优化。只读事务内不能有insert、update、delete的操作
- 事务超时(timeout):事务可能设计对后端数据库的锁定,所以长时间的事务运行会不必要的占用数据库资源,设置事务超时时间可以及时释放资源
Spring事务的传播属性
Spring事务的隔离级别
- 事务管理的方式
- 编程式事务管理
通过编写代码实现的事务管理,包括定义事务的开始、正常执行后的事务提交和异常时的事务回滚
步骤1:添加事务管理器组件
在applicationContext.xml中配置一个事务管理器组件,提供对事务处理的全面支持和统一管理
步骤2:在业务逻辑类中使用事务管理器
步骤3:在方法中编程实现事务管理
编程式事务管理缺点
编程式事务管理必须要在业务逻辑中包含额外的事务管理代码。和业务逻辑代码产生了耦合,产生了代码冗余,不方便代码的维护和扩展。
声明式事务管理最大的优点在于开发者无需通过编程的方式来管理事务,只需在配置文件中进行相关的事务规则声明,就可以将事务应用到业务逻辑中。这使得开发人员可以更加专注于核心业务逻辑代码的编写,在一定程度上减少了工作量,提高了开发效率,所以在实际开发中,通常都推荐使用声明式事务管理。
- 声明式事务管理
声明式事务管理的原理是什么?
通过AOP技术实现的事务管理,主要思想是将事务作为一个“切面”代码单独编写,然后通过AOP技术将事务管理的“切面”植入到业务目标类中
如何实现Spring的声明式事务管理?
Spring的声明式事务管理可以通过两种方式来实现,一种是基于XML的方式,另一种是基于注解的方式。
- 基于XML方式的声明式事务
基于XML方式的声明式事务是在配置文件中通过<tx:advice>元素配置事务规则来实现的,然后通过使用<aop:config>编写的AOP配置,让Spring自动对目标生成代理。<tx:advice>元素及其子元素如下图所示:
配置<tx:advice>元素的重点是配置<tx:method>子元素,上图中使用灰色标注的几个属性是<tx:method>元素中的常用属性。其属性描述具体如下:
步骤1:添加事务管理器组件
在applicationContext.xml中配置一个事务管理器组件,提供对事务处理的全面支持和统一管理
<!--定义事务管理器 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
步骤2:使用<tx:advice>标签配置事务规则
步骤3:使用<aop:config>配置事务切面
- 基于注解方式的声明式事务
- 在Spring容器中注册事务注解驱动
<tx:annotation-driven transaction-manager=“txManager"/>
- 在需要事务管理的类或方法上使用@Transactional注解
如果将注解添加在Bean类上,则表示事务的设置对整个Bean类的所有方法都起作用;
如果将注解添加在Bean类中的某个方法上,则表示事务的设置只对该方法有效。
使用@Transactional注解时,可以通过参数配置事务详情:
步骤1:配置事务管理器
在applicationContext.xml中配置一个事务管理器组件,提供对事务处理的全面支持和统一管理
<!--定义事务管理器 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
步骤2:注册事务注解驱动
在applicationContext.xml中添加对注解配置事务的支持
步骤3:注册事务注解驱动
在需要事务管理的类或方法上使用@Transactional注解
使用 @Transactional 注解的方法,需要是 public 的。如果加在 protected、private 或者 package 可见的方法上,不会生效,也不会报错