当前位置: 首页 > news >正文

【Spring框架】通过JDK动态代理实现AOP

首先需要创建 maven java 项目,引入开发的坐标

<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency> </dependencies>
Account 类:存储账户信息(姓名、金额),对应数据库中的account表
public class Account { private int id; private String name; private double money; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
统一管理数据库连接和事务,确保同一事务中的所有数据库操作使用同一个连接,并提供事务的开启、提交、回滚等操作,最终保证要么所有操作都成功,要么都失败
这是控制事务的方式代码
事务管理工具类 PxUtils,每次直接调用就可以,不用再写事务管理得逻辑代码了
/** * 事务的工具类 */ public class PxUtils { private static DruidDataSource ds = null; // 使用ThreadLocal存储当前线程中的Connection对象 private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>(); // 在静态代码块中创建数据库连接池 static { try { // 通过代码创建C3P0数据库连接池 ds = new DruidDataSource(); ds.setDriverClassName("com.mysql.jdbc.Driver"); ds.setUrl("jdbc:mysql:///ssm"); ds.setUsername("root"); ds.setPassword("root"); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } /** * @Method: getConnection * @Description: 从数据源中获取数据库连接 */ public static Connection getConnection() throws SQLException { // 从当前线程中获取Connection Connection conn = threadLocal.get(); if (conn == null) { // 从数据源中获取数据库连接 conn = getDataSource().getConnection(); // 将conn绑定到当前线程 threadLocal.set(conn); } return conn; } /** * @Method: startTransaction * @Description: 开启事务 */ public static void startTransaction() { try { Connection conn = threadLocal.get(); if (conn == null) { conn = getConnection(); // 把 conn绑定到当前线程上 threadLocal.set(conn); } // 开启事务 conn.setAutoCommit(false); } catch (Exception e) { throw new RuntimeException(e); } } /** * @Method: rollback * @Description:回滚事务 */ public static void rollback() { try { // 从当前线程中获取Connection Connection conn = threadLocal.get(); if (conn != null) { // 回滚事务 conn.rollback(); } } catch (Exception e) { throw new RuntimeException(e); } } /** * @Method: commit * @Description:提交事务 */ public static void commit() { try { // 从当前线程中获取Connection Connection conn = threadLocal.get(); if (conn != null) { // 提交事务 conn.commit(); } } catch (Exception e) { throw new RuntimeException(e); } } /** * @Method: close * @Description:关闭数据库连接(注意,并不是真的关闭,而是把连接还给数据库连接池) */ public static void close() { try { // 从当前线程中获取Connection Connection conn = threadLocal.get(); if (conn != null) { conn.close(); // 解除当前线程上绑定conn threadLocal.remove(); } } catch (Exception e) { throw new RuntimeException(e); } } /** * @Method: getDataSource * @Description: 获取数据源 */ public static DataSource getDataSource() { // 从数据源中获取数据库连接 return ds; } }
为什么需要 ThreadLocal?
事务的核心要求是同一事务中的所有 SQL 必须用同一个数据库连接,否则无法保证原子性,ThreadLocal确保了一个线程中多次获取的连接是同一个,从而保证了事务的正确性。
事务的核心逻辑
close():释放连接(归还给连接池),并清除 ThreadLocal 中的连接
startTransaction():获取连接并关闭自动提交
若全部成功,commit():一次性提交所有 SQL,生效
若有失败,rollback():撤销所有 SQL,恢复到事务开始前的状态
持久层接口和实现类:定义数据操作方法save,用于将单个账户存入数据库
public interface AccountDao_px { void save(Account account) throws SQLException; }
public class AccountDaoPxImpl implements AccountDao_px { /** * 保存 * @param account */ public void save(Account account) throws SQLException { System.out.println("持久层:保存账户..."); // 把数据存储到数据库中 // 先获取到连接 Connection conn = PxUtils.getConnection(); // 编写 sql 语句 String sql = "insert into account values (null,?,?)"; // 预编译 SQL 语句 PreparedStatement stmt = conn.prepareStatement(sql); // 设置值 stmt.setString(1,account.getName()); stmt.setDouble(2,account.getMoney()); // 执行操作 stmt.executeUpdate(); // 关闭资源 ,conn 不能关闭 stmt.close(); } }
通过 PxUtils.getConnection() 获取数据库连接,这个连接由事务工具类统一管理,确保同一事务中使用同一个连接,从而让事务控制生效。
业务层接口和实现类:定义业务方法savaAll,用于保存两个账户
/** * 业务层接口 */ public interface AccountService_px { //保存两个账户 public void savaAll(Account account1, Account account2) throws SQLException; }
/** * 业务层实现类 */ public class AccountServicePxImpl implements AccountService_px { //DI依赖注入 private AccountDao_px accountDao_px; public void setAccountDao(AccountDao_px accountDao_px) { this.accountDao_px = accountDao_px; } public void savaAll(Account account1, Account account2) throws SQLException { //保存账号1 accountDao_px.save(account1); //显示除零错误 //int a=1/0; //保存账号2 accountDao_px.save(account2); System.out.println("保存成功"); } }
先保存第一个账户,再保存第二个账户,如果中间出现异常(打开int a=1/0),理论上第二个账户不应该保存成功,这就是事务要解决的问题。
动态代理工具类 JdkPoxy,通过 JDK 动态代理技术,在不修改原业务代码的前提下,为业务方法自动添加事务控制逻辑
/** * 生成代理对象 * 传入目标对象,生成该对象的代理对象,返回,在对目标对象的方法进行增强 */ public class JdkPoxy { /** * 获取代理对象的方法 返回代理对象 对目标对象的方法进行增强 */ public static Object getPoxy(final AccountService_px accountServicePx){ /** * 使用JDK动态代理生成代理对象 *第一个参数:类的加载器 *第二个参数:当前传入的对象实现了哪些接口要字节码对象 *第三个参数:回调函数 */ Object proxy = Proxy.newProxyInstance(JdkPoxy.class.getClassLoader(), accountServicePx.getClass().getInterfaces(), new InvocationHandler() { /** * 调用代理对象的方法invoke方法就会执行 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //System.out.println("你调用了代理对象的invoke方法了。。。。。。。"); //对目标对象的方法进行增强 Object result = null; try { //开启事务 PxUtils.startTransaction(); result = method.invoke(accountServicePx, args); //提交事务 PxUtils.commit(); } catch (Exception e) { e.printStackTrace(); //回滚事务 PxUtils.rollback(); }finally { PxUtils.close(); } return result; } }); return proxy; } }
为什么用动态代理?
动态代理的本质是创建一个和目标对象实现相同接口的代理对象,当调用代理对象的方法时,会先执行定义的增强逻辑再执行原方法,这样避免了在每个业务方法中重复写事务代码。
JDK 动态代理的要求:必须基于接口,代理对象只能转换为接口类型(如AccountService_px proxy = (AccountService_px) proxyobj)。
invoke 方法的执行时机:当通过代理对象调用任何方法(如proxy.savaAll(...))时,invoke方法会被自动触发,其中method是当前调用的方法,args是方法的参数,method.invoke(accountServicePx, args)即调用目标对象的原方法执行真正的保存逻辑。
与事务的关联:代理类本身不直接管理事务,而是通过调用PxUtils的方法(startTransaction/commit/rollback)来实现事务控制,是事务逻辑的触发者。
applicationContext_px.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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"> <bean id="accountDao_px" class="com.qcby.dao.impl.AccountDaoPxImpl"/> <bean id="accountServicePx" class="com.qcby.service.impl.AccountServicePxImpl"> <property name="accountDao" ref="accountDao_px"/> </bean> </beans>
测试方法
public class Text_px { @Test public void run1() throws SQLException { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext_px.xml"); // 获取 service 对象 AccountService_px accountService_px = (AccountService_px) ac.getBean("accountServicePx"); Account account1 = new Account(); account1.setName("熊大"); account1.setMoney(1000d); Account account2 = new Account(); account2.setName("美羊羊"); account2.setMoney(1000d); // 生成代理对象 Object proxyobj = JdkPoxy.getPoxy(accountService_px); // 强转 AccountService_px proxy = (AccountService_px) proxyobj; // 调用代理对象方法 proxy.savaAll(account1,account2); } }
整个执行逻辑是:测试类Text_px通过Spring容器加载配置文件applicationContext_px.xml,获取由Spring管理的AccountService_px实现类实例;接着调用JdkPoxy的getPoxy方法,基于JDK动态代理为该Service实例生成代理对象,代理对象会拦截目标方法的调用;当通过代理对象调用savaAll方法时,代理的invoke方法先触发PxUtils开启事务(绑定连接到当前线程),再执行目标Service的savaAll方法——该方法调用AccountDao_px的save方法,DAO层通过PxUtils获取同一线程连接执行SQL插入两个账户数据;若执行中无异常,PxUtils提交事务,若有异常则回滚事务,最终无论成败都会关闭连接(归还连接池并解除线程绑定),从而通过动态代理实现了事务的统一管理,确保两个账户的保存操作要么同时成功,要么同时失败。
实现效果:
AccountServicePxImpl 打开int a=1/0,即中间出现异常的执行效果

总结

  • JDK 动态代理是实现手段:代码中JdkPoxy类通过Proxy.newProxyInstance()方法,基于AccountService_px接口生成代理对象,这是 JDK 动态代理的典型实现,依赖接口,通过反射生成代理类。
  • 与 AOP 核心概念的精准对应:代码将事务管理这一横切关注点从业务代码中分离出来,代理对象的invoke()方法中,通过PxUtils实现了事务的开启、提交、回滚、关闭等操作(这相当于AOP中的通知 Advice);被代理的AccountService_px的saveAll()方法是核心业务逻辑(相当于AOP中的连接点 Joinpoint);最终通过代理机制,在不修改业务代码的前提下,将事务管理逻辑织入到业务方法的执行流程中。AOP 的本质是将横切关注点与业务逻辑分离,并通过特定方式织入业务流程,这段代码完全符合这一思想。
http://www.cnnetsun.cn/news/158762.html

相关文章:

  • 【Copula】考虑风光联合出力和相关性的Copula场景生成附Matlab代码
  • 火山引擎回应云大厂竞争:云处于重大变革期
  • 【毕业设计】基于springboot的智慧医疗管理系统(源码+文档+远程调试,全bao定制等)
  • 28nm以下工艺PMIC设计雷区:LOD、WPE、HKMG如何悄悄毁掉你的LDO?
  • Abaqus水力压裂模拟:基于Cohesive单元与XFEM的方法研究
  • 44、COMSOL模拟二维裂隙流压裂水平井裂缝性油藏离散裂缝网络模型COMSOL数值模拟案例
  • 今天咱们来聊聊ReliefF算法,一个在分类数据特征选择中相当实用的工具。废话不多说,直接上代码,边看边聊
  • MATLAB R2018A环境下的液相色谱信号自动调优降噪算法——交叉验证作为参数调节器
  • 计算机Java毕设实战-基于springboot的足球训练营系统的设计与实现设计与实现基于SpringBoot的青训足球综合运营平台设计与实现 【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • 2025年软件测试技术发展趋势与从业者应对策略
  • 电驱动(电机+电控)开发验证方法与技巧的高清视频教程,深入讲解精细技术,掌握实用技巧
  • 每天24小时的电价(元/kWh)
  • C#编程下的自定义控件与OpenCVSharp结合应用:卡尺测距功能实现
  • NGBoost-shap方法回归任务,由斯坦福吴恩达团队提出,属于集成模型的一种2019年提出的
  • Langchain-Chatchat Kubernetes集群部署策略
  • Langchain-Chatchat日志监控与性能分析最佳实践
  • Langchain-Chatchat模型微调指南:适配垂直领域任务
  • 如何配置IPv6静态路由?解决企业网络难题
  • 【Linux网络基础】详解 TCP 面向连接 vs UDP 无连接
  • Langchain-Chatchat如何评估问答质量?指标体系构建
  • springboot在线教育系统(11528)
  • 测了多款AI自动生成PPT工具,真正能用的不到一半
  • springboot星之语明星周边产品销售网站的设计与实现(11529)
  • 毕设救星:Spring Boot + Neo4j 打造“医疗知识问答”——基于知识图谱的智能导诊平台
  • 华为网络设备基本配置命令
  • 志同道合交友网站毕业论文+PPT(附源代码+演示视频)
  • 【Java 25 LTS六大核心特性】
  • Langchain-Chatchat助力医疗文档智能检索与问答
  • Langchain-Chatchat如何实现文档相似度比对?查重与去重依据
  • java学习--String和StringBuffer互转