手写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()来回滚,若不手动回滚就会造成事务失效。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步