Spring 事务 是基于 AOP 环绕通知和异常通知实现的。
Spring 事务 分为:编程式事务 和 声明式事务。
Spring 事务底层使用 编程式事务
+ AOP
技术进行包装 = 声明式事务
1 SpringAOP
原理
1.1 AOP
编程技术
面向切面编程,解决代码复用问题
核心点:在方法之前或之后处理事情
底层实现原理:代理设计模式
1.1.1 什么是 AOP
编程
AOP
(Aspect Oriented Programming):面向切面编程。
面向切面编程
(也叫面向方面):是目前软件开发中的一个热点。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP 是 OOP 的延续,是(Aspect Oriented Programming)的缩写,意思是面向切面(方面)编程。
主要的功能是:日志记录
、性能统计
、安全控制
、事务处理
、异常处理
等等。
主要的意图是:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
可以通过 预编译方式
和 运行期动态代理
实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP 实际是 GoF 设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP 可以说也是这种目标的一种实现。
假设把应用程序想成一个立体结构的话,OOP 的利刃是纵向切入系统,把系统划分为很多个模块(如:用户模块,文章模块等等),而 AOP 的利刃是横向切入系统,提取各个模块可能都要重复操作的部分(如:权限检查,日志记录等等)。由此可见,AOP 是 OOP 的一个有效补充。
注意:AOP 不是一种技术,实际上是编程思想。凡是符合 AOP 思想的技术,都可以看成是 AOP 的实现。
功能:让关注点代码与业务代码分离!
1.1.2 关注点
重复代码
就叫做关注点
1.1.3 切面
关注点形成的类
,就叫切面(类)!
面向切面编程,就是指 对很多功能都有的重复的代码抽取
,再在运行的时候往业务方法上动态植入“切面类代码”。
1.1.4 切入点
执行目标对象
方法,动态植入切面代码。
可以通过切入点表达式,指定拦截哪些类的哪些方法; 给指定的类在运行的时候植入切面类代码。
1.1.5 AOP 底层实现原理 - 代理设计模式
1⃣️ 什么是代理模式
通过代理控制对象的访问,可以详细访问某个对象的方法,在这个方法调用处理,或调用后处理。既( AOP 微实现) ,AOP 核心技术面向切面编程。
2⃣️ 代理模式应用场景
可以隐蔽真实角色
SpringAOP
事物原理
日志打印
权限控制
远程调用
安全代理
3⃣️ 代理的分类
静态代理
(静态定义代理类)动态代理
(动态生成代理类)Jdk自带动态代理
Cglib
、javaassist
(字节码操作库)
4⃣️ 静态代理
什么是静态代理
由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是 在程序运行前就已经存在代理类的字节码文件
,代理类和委托类的关系在运行前就确定了
。
静态代理代码
public interface IUserDao {
void save();
}
public class UserDao implements IUserDao {
public void save() {
System.out.println("已经保存数据...");
}
}
// 代理类
public class UserDaoProxy implements IUserDao {
private IUserDao target;
public UserDaoProxy(IUserDao iUserDao) {
this.target = iUserDao;
}
public void save() {
System.out.println("开启事物...");
target.save();
System.out.println("关闭事物...");
}
}
- 静态代理需要生产目标代理对象 -
不推荐
- 动态代理
不需要
生产目标代理对象
5⃣️ 动态代理
什么是动态代理
- 代理对象,
不需要实现接口
- 代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
- 动态代理也叫做:JDK代理,接口代理
- JDK 动态代理:反射虚拟生产代理类
需要接口,JDK 动态代理 只需要子类实现,基于反射实现。 - CGLIB 动态代理:基于 ASM 字节码技术虚拟生成代理类
CGLIB 是基于 ASM 字节码包装的一个类库。
JDK 动态代理
-
原理:是
根据类加载器和接口创建代理类
(此代理类是接口的实现类,所以必须使用接口 面向接口生成代理,位于java.lang.reflect
包下) -
实现方式:
- 通过实现
InvocationHandler
接口创建自己的调用处理器IvocationHandler handler = new InvocationHandlerImpl(…);
- 通过为
Proxy
类指定ClassLoader
对象和一组 interface 创建动态代理类Class clazz = Proxy.getProxyClass(classLoader,new Class[]{…});
- 通过
反射机制
获取动态代理类的构造函数,其参数类型是调用处理器接口类型Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
- 通过
构造函数
创建代理类实例,此时需将调用处理器对象作为参数被传入Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
- 通过实现
缺点:jdk 动态代理,
必须是面向接口,目标业务类必须实现接口
// 每次生成动态代理类对象时,实现了InvocationHandler接口的调用处理器对象
public class InvocationHandlerImpl implements InvocationHandler {
// 这其实业务实现类对象,用来调用具体的业务方法
private Object target;
// 通过构造函数传入目标对象
public InvocationHandlerImpl(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
System.out.println("调用开始处理");
result = method.invoke(target, args);
System.out.println("调用结束处理");
return result;
}
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// 被代理对象
IUserDao userDao = new UserDao();
InvocationHandlerImpl invocationHandlerImpl = new InvocationHandlerImpl(userDao);
ClassLoader loader = userDao.getClass().getClassLoader();
Class<?>[] interfaces = userDao.getClass().getInterfaces();
// 主要装载器、一组接口及调用处理动态代理实例
IUserDao newProxyInstance = (IUserDao) Proxy.newProxyInstance(loader, interfaces, invocationHandlerImpl);
newProxyInstance.save();
}
}
CGLIB 动态代理
原理:利用 asm
开源包,对代理对象类的 class
文件加载进来,通过修改其字节码生成子类来处理。
6⃣️ 什么是 CGLIB
动态代理
使用 cglib[Code Generation Library] 实现动态代理,并不要求委托类必须实现接口,底层采用 asm 字节码生成框架生成代理类的字节码。
7⃣️ CGLIB
动态代理相关代码
public class CglibProxy implements MethodInterceptor {
private Object targetObject;
// 这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理
public Object getInstance(Object target) {
// 设置需要创建子类的类
this.targetObject = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开启事物");
Object result = proxy.invoke(targetObject, args);
System.out.println("关闭事物");
// 返回代理对象
return result;
}
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
UserDao userDao = (UserDao) cglibProxy.getInstance(new UserDao());
userDao.save();
}
}
8⃣️ CGLIB
动态代理与 JDK
动态区别
java 动态代理是 利用反射机制生成一个实现代理接口的匿名类
,在调用具体方法前调用 InvokeHandler
来处理。
而 cglib动态代理是利用asm开源包
,对代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处理。
Spring 中:
- 如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP
- 如果目标对象实现了接口,可以强制使用 CGLIB 实现 AOP
- 如果目标对象没有实现了接口,必须采用 CGLIB 库,spring 会自动在 JDK 动态代理和 CGLIB 之间转换
JDK 动态代理只能对实现了接口的类生成代理,而不能针对类 。
CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 。
因为是继承,所以该类或方法最好不要声明成 final ,final 可以阻止继承和多态。
1.1.6 AOP
编程使用
1⃣️ 注解
版本实现 AOP
需要在 Spring 的配置文件中配置:
<!-- 扫包范围 -->
<context:component-scan base-package="cn.ys"></context:component-scan>
<!-- 开启事物注解 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@Pointcut("execution(* cn.ys.service.UserService.add(..))") 指定切入点表达式
@Before("pointCut_()") 前置通知: 目标方法之前执行
@After("pointCut_()") 后置通知:目标方法之后执行(始终执行)
@AfterReturning("pointCut_()") 返回后通知: 执行方法结束前执行(异常不执行)
@AfterThrowing("pointCut_()") 异常通知: 出现异常时候执行
@Around("pointCut_()") 环绕通知: 环绕目标方法执行
@Component // 将类加入到 spring 容器中
@Aspect // 指定一个类为切面类
public class AopLog {
// aop 编程里面有几个通知: 前置通知 后置通知 运行通知 异常通知 环绕通知
@Before("execution(* cn.ys.service.UserService.add(..))")
public void before() {
System.out.println("前置通知 在方法之前执行...");
}
// 后置通知 在方法运行后执行
@After("execution(* cn.ys.service.UserService.add(..))")
public void after() {
System.out.println("后置通知 在方法之后执行...");
}
// 运行通知
@AfterReturning("execution(* cn.ys.service.UserService.add(..))")
public void returning() {
System.out.println("运行通知...");
}
// 异常通知
@AfterThrowing("execution(* cn.ys.service.UserService.add(..))")
public void afterThrowing() {
System.out.println("异常通知...");
}
// 环绕通知 在方法之前和之后处理事情
@Around("execution(* cn.ys.service.UserService.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 调用方法之前执行
System.out.println("环绕通知 调用方法之前执行");
// 代理调用方法 注意点: 如果调用方法抛出溢出不会执行后面代码
proceedingJoinPoint.proceed();
// 调用方法之后执行
System.out.println("环绕通知 调用方法之后执行");
}
}
控制台打印:
前置通知 在方法之前执行...
环绕通知 调用方法之前执行
################
后置通知 在方法之后执行...
环绕通知 调用方法之后执行
运行通知...
2⃣️ XML
方式实现 AOP
XML
方式实现 AOPXml 实现 aop 编程:
- 引入 jar 文件 【aop 相关jar, 4个】
- 引入 aop 名称空间
- aop 配置
- 配置切面类 (重复执行代码形成的类)
- aop 配置
拦截哪些方法 / 拦截到方法后应用通知代码
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- dao 实例 -->
<bean id="userService" class="cn.ys.service.UserService"></bean>
<!-- 切面类 -->
<bean id="aop" class="com.itmayiedu.aop2.AopLog2"></bean>
<!-- Aop配置 -->
<aop:config>
<!-- 定义一个切入点表达式: 拦截哪些方法 -->
<aop:pointcut expression="execution(* cn.ys.service.UserService.*(..))"
id="pt" />
<!-- 切面 -->
<aop:aspect ref="aop">
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="pt" />
<!-- 前置通知: 在目标方法调用前执行 -->
<aop:before method="begin" pointcut-ref="pt" />
<!-- 后置通知: -->
<aop:after method="after" pointcut-ref="pt" />
<!-- 返回后通知 -->
<aop:after-returning method="afterReturning"
pointcut-ref="pt" />
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowing"
pointcut-ref="pt" />
</aop:aspect>
</aop:config>
</beans>
public class AopLog2 {
// 前置通知
public void begin() {
System.out.println("前置通知");
}
//
// 后置通知
public void commit() {
System.out.println("后置通知");
}
// 运行通知
public void returning() {
System.out.println("运行通知");
}
// 异常通知
public void afterThrowing() {
System.out.println("异常通知");
}
// 环绕通知
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知开始");
proceedingJoinPoint.proceed();
System.out.println("环绕通知结束");
}
}
1.1.7 AOP
编程应用场景
日志记录
事务
性能监控
权限
参数验证
安全控制
事务处理
异常处理
1.2 Spring 事务使用
1.2.1 事务基本特性
⑴ 原子性
(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
⑵ 一致性
(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
⑶ 隔离性
(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
⑷ 持久性
(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
1.3 事务控制分类
1⃣️ 编程式事务控制
自己手动控制事务,就叫做编程式事务控制。
Jdbc代码:
Conn.setAutoCommite(false); // 设置手动控制事务
Hibernate代码:
Session.beginTransaction(); // 开启一个事务
【细粒度的事务控制: 可以对指定的方法、指定的方法的某几行添加事务控制】
(比较灵活,但开发起来比较繁琐: 每次都要开启、提交、回滚)
2⃣️ 声明式事务控制
Spring提供了对事务的管理, 这个就叫声明式事务管理。
Spring提供了对事务控制的实现。用户如果想用Spring的声明式事务管理,只需要在配置文件中配置即可; 不想使用时直接移除配置。这个实现了对事务控制的最大程度的解耦。
Spring声明式事务管理,核心实现就是基于Aop。
【粗粒度的事务控制: 只能给整个方法应用事务,不可以对方法的某几行应用事务。】
(因为aop拦截的是方法。)
Spring 声明式事务管理器类:
Jdbc 技术:DataSourceTransactionManager
Hibernate 技术:HibernateTransactionManager