手写spring事务框架, 揭秘AOP实现原理。
AOP面向切面编程:主要是通过切面类来提高代码的复用,降低业务代码的耦合性,从而提高开发效率。主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。
AOP实现原理:aop是通过cglib的动态代理实现的。
jdk动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
cglib动态代理:将代理对象类的class文件加载进来,通过ASM字节码技术修改其字节码生成子类来处理。
区别:JDK动态代理只能对实现了接口的类生成代理,而不能针对类 。CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 。因为是继承,所以该类或方法最好不要声明成final ,final可以阻止继承和多态。
一:AOP运行过程
项目结构
1.1 导入相关包
<dependencies>
<!-- 引入Spring-AOP等相关Jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_2</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
</dependencies>
1.2 配置包扫描和切面代理
<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" xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 扫描指定路径 -->
<context:component-scan base-package="com.wulei"/>
<!-- 开启切面代理 -->
<aop:aspectj-autoproxy /> <!-- 1. 数据源对象: C3P0连接池
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean> --> <!-- 2. JdbcTemplate工具类实例
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean> --> <!-- 3. 配置事务
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean> -->
</beans>
1.3 编写AOP切面
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component; @Component//注入到spring
@Aspect//申明切面类
public class AopLog { // aop 编程里面有几个通知: 前置通知 后置通知 运行通知 异常通知 环绕通知
@Before("execution(* com.wulei.service.UserService.add(..))")
public void before() {
System.out.println("前置通知: 在方法之前执行...");
} // 后置通知 在方法运行后执行
@After("execution(* com.wulei.service.UserService.add(..))")
public void after() {
System.out.println("后置通知: 在方法之后执行...");
} // 运行通知
@AfterReturning("execution(* com.wulei.service.UserService.add(..))")
public void returning() {
System.out.println("运行通知:");
} // 异常通知
@AfterThrowing("execution(* com.wulei.service.UserService.add(..))")
public void afterThrowing() {
System.out.println("异常通知: 异常抛出执行");// 异常被try()cacth{}捕捉到则不执行。
} // 环绕通知 在方法之前和之后处理事情
@Around("execution(* com.wulei.service.UserService.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
// 调用方法之前执行
System.out.println("环绕通知: 调用方法之前执行");
proceedingJoinPoint.proceed();
/* 代理调用方法 , 如果调用方法抛出异常就不会执行后面代码。
*
* 在使用spring事务的时候 service最好不要try, 将异常抛出给aop 异常通知处理回滚!
* 否则业务逻辑出错,而aop却正常执行,就会造成事务失效的情况。
*/ // 调用方法之后执行
System.out.println("环绕通知: 调用方法之后执行");
}
}
1.4 编写Service
@Service
public class UserService {
public void add() {
System.out.println("正在添加数据");
int i = 1/0;
// 如果出现异常就会触发AOP异常通知,如果异常被try()catch{}住,则会不触发异常通知继续走完环绕通知。
}
}
1.5 测试
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.add();
}
}
=================
【控制台输出】
前置通知: 在方法之前执行...
Exception in thread "main" 环绕通知: 调用方法之前执行
正在添加数据
后置通知: 在方法之后执行...
异常通知: 异常抛出执行
java.lang.ArithmeticException: / by zero
at com.wulei.service.UserService.add(UserService.java:16)
二:手写编程式事务
2.1 在spring.xml配置好自己的数据源。
2.2 编写dao层
/*
CREATE TABLE `t_users` (
`name` varchar(20) NOT NULL,
`age` int(5) DEFAULT NULL,
PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
*/
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate; public int add(String name, Integer age) {
String sql = "INSERT INTO t_users(NAME, age) VALUES(?,?);";
int result = jdbcTemplate.update(sql, name, age);
System.out.println("插入成功");
return result;
}
}
2.3 手写编程式事务具体逻辑
@Component
public class MyTransaction { // 获取数据源
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager; // 开启事务
public TransactionStatus begin() {
// getTransaction()这里的参数是用的事务默认的传播属性
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
System.out.println("开启事务");
// 得到事务状态
return transaction;
} // 提交事务
public void commit(TransactionStatus transaction) {
dataSourceTransactionManager.commit(transaction);
System.out.println("提交事务");
} // 回滚事务
public void rollback(TransactionStatus transaction) {
dataSourceTransactionManager.rollback(transaction);
System.out.println("事务回滚");
}
}
2.4 编写service,然后运行测试。
@Service
public class UserService { @Autowired
private UserDao userDao;
@Autowired
private MyTransaction myTransaction; public void add() {
TransactionStatus transactionStatus = null;
try {
//1. 开启事务
transactionStatus = myTransaction.begin();
userDao.add("test001", 20);
int i = 1 / 0;
userDao.add("test002", 21);
//2. 执行成功就提交事务
myTransaction.commit(transactionStatus);
} catch (Exception e) {
//3. 出现异常就回滚
myTransaction.rollback(transactionStatus);
}
}
}
========================
【控制台输出】 此时查看数据库可以发现,由于我们手动回滚所以没有插入数据。
前置通知: 在方法之前执行...
环绕通知: 调用方法之前执行
开启事务
插入成功
事务回滚
后置通知: 在方法之后执行...
环绕通知: 调用方法之后执行
运行通知:
三:AOP重构编程式事务
3.1 通过aop实现spring事务
@Component
@Aspect
// 基于AOP的环绕通知和异常通知实现Spring事务
public class AopTransaction { @Autowired
private MyTransaction myTransaction; // 环绕通知 在方法之前和之后处理事情
@Around("execution(* com.wulei.service.UserService.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 开启事务 调用方法之前执行
TransactionStatus transactionStatus = myTransaction.begin();
proceedingJoinPoint.proceed();// 代理调用方法 注意点: 如果调用方法抛出溢出不会执行后面代码
// 提交事务 调用方法之后执行
myTransaction.commit(transactionStatus);
}
// 异常通知
@AfterThrowing("execution(* com.wulei.service.UserService.add(..))")
public void afterThrowing() {
System.out.println("回滚当前事务");
// 获取当前事务 直接回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
3.2 编写service, 然后运行测试
@Service
public class UserService { @Autowired
private UserDao userDao; public void add() { userDao.add("test001", 20);
int i = 1 / 0;
userDao.add("test002", 21);//try {
// userDao.add("test001", 20);
// int i = 1 / 0;
// userDao.add("test002", 21);
//} catch (Exception e) {
// // 回滚当前事务。
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
//}
}
}
===================
【控制台输出】 此时查看数据库可以发现,触发异常通知回滚所以没有插入数据。
开启事务
插入成功
回滚当前事务
注意:在环绕通知中begin()开启了事务,如果程序出现了异常,环绕通知就不会commit()提交事务,若此时异常被try捕捉,异常通知又无法rollback()来回滚,若不手动回滚就会造成事务失效。
手写spring事务框架, 揭秘AOP实现原理。的更多相关文章
- 手写Spring事务框架
Spring事务基于AOP环绕通知和异常通知 编程事务 声明事务 Spring事务底层使用编程事务+AOP进行包装的 = 声明事务 AOP应用场景: 事务 权限 参数验证 什么是AOP技术 AO ...
- 手写spring事务框架-蚂蚁课堂
1.视频参加C:\Users\Administrator\Desktop\蚂蚁3期\[www.zxit8.com] 0017-(每特教育&每特学院&蚂蚁课堂)-3期-源码分析-手写Sp ...
- Spring事务原理分析--手写Spring事务
一.基本概念和原理 1.Spring事务 基于AOP环绕通知和异常通知的 2.Spring事务分为编程式事务.声明事务.编程事务包括注解方式和扫包方式(xml) Spring事务底层使用编程事务(自己 ...
- 手写Spring MVC框架(二) 实现访问拦截功能
前言 在上一篇文章中,我们手写了一个简单的mvc框架,今天我们要实现的功能点是:在Spring MVC框架基础上实现访问拦截功能. 先梳理一下需要实现的功能点: 搭建好Spring MVC基本框架: ...
- 手写Spring MVC框架(一) 实现简易版mvc框架
前言 前面几篇文章中,我们讲解了Spring MVC执⾏的⼤致原理及关键组件的源码解析,今天,我们来模仿它⼿写⾃⼰的mvc框架. 先梳理一下需要实现的功能点: tomcat加载配置文件web.xml: ...
- 从零开始手写 spring ioc 框架,深入学习 spring 源码
IoC Ioc 是一款 spring ioc 核心功能简化实现版本,便于学习和理解原理. 创作目的 使用 spring 很长时间,对于 spring 使用非常频繁,实际上对于源码一直没有静下心来学习过 ...
- 《四 spring源码》利用TransactionManager手写spring的aop
事务控制分类 编程式事务控制 自己手动控制事务,就叫做编程式事务控制. Jdbc代码: Conn.setAutoCommite(false); // 设置手动控制事务 Hibern ...
- -手写Spring注解版本&事务传播行为
视频参考C:\Users\Administrator\Desktop\蚂蚁3期\[www.zxit8.com] 0018-(每特教育&每特学院&蚂蚁课堂)-3期-源码分析-手写Spri ...
- Spring学习之——手写Spring源码V2.0(实现IOC、D、MVC、AOP)
前言 在上一篇<Spring学习之——手写Spring源码(V1.0)>中,我实现了一个Mini版本的Spring框架,在这几天,博主又看了不少关于Spring源码解析的视频,受益匪浅,也 ...
随机推荐
- vue学习-day03(动画,组件)
目录: 1.品牌列表-从数据库获取列表 2.品牌列表-完成添加功能 3.品牌列表-完成删除功能 4.品牌列表-全局配置数据接口的根域名 5.品牌列表-全局配置emulateJS ...
- Zookeeper简介及安装(一)
1 Zookeeper入门1.1 概述Zookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目. 1.2 特点 1.3 数据结构 1.4 应用场景提供的服务包括:统一命名服务 ...
- 【canvas学习笔记三】样式和颜色
上一节我们学习了如何用路径绘制各种形状,但我们只能用默认的颜色和线条.这节就来学习设置不同的颜色和线条样式. 颜色 设置颜色主要有两个属性: fillStyle = color 设置填充颜色 stro ...
- 随堂小测APP使用体验
随堂小测APP使用体验 先要去注册账号需要填写用户名.密码.手机号.学号/教师号.学校.专业.即可注册,注册成功后,即可登录APP进,登陆进去以后.会有两个界面,课堂和我的,注册.登录简单,通俗易懂, ...
- mysql语句错误
select * from order where id = 1; 同学问我这句话有什么问题,乍一看真看不出毛病,后来发现order是mysql关键字,这样写是不对的,所以要加一个双引号才会更好一点 ...
- IDEA集成Tomcat启动控制台乱码
解决方法: 在下图位置加上: -Dfile.encoding=UTF-8 然后安装下图设置: 如果上述方法重启tomcat还是乱码,那么: 进入idea的安装目录, 进入bin目录下.找到idea.e ...
- Linux_IPtables防火墙详解
目录 目录 Iptables Iptables结构 规则表 规则链 iptables指令用法详解 综合案例 SNAT 策略 DNAT 策略 Iptables规则的备份和还原 iptables 练习 I ...
- Delphi XE2 之 FireMonkey 入门(31) - 数据绑定: 绑定数据库
Delphi XE2 之 FireMonkey 入门(31) - 数据绑定: 绑定数据库 一.全设计时操作: 先在窗体上放置控件: DataSource1 : TDataSource; Clie ...
- 2018.03.29 python-pandas 数据透视pivot table / 交叉表crosstab
#透视表 pivot table #pd.pivot_table(data,values=None,index=None,columns=None, import numpy as np import ...
- 【Spring】的【Bean】管理(注解)【四个相同功能的注解】
[Spring]的[Bean]管理(注解)[四个相同功能的注解] 注解:代码里面特殊的标记,使用注解也可以完成一些相关的功能. 注解写法:@注解名称(属性名称=属性值) 注解使用在类.方法.属性上面 ...