通过AOP和IOC改造银行转账案例
1 银行转账案例界面
2 表结构
3 代码调用关系
4 关键代码
(1)TransferServlet
@WebServlet(name = "transferServlet", urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {// 1. 实例化service层对象(需要被改造)private TransferService transferService = new TransferServiceImpl();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponseresp) throws ServletException, IOException {// 设置请求体的字符编码req.setCharacterEncoding("UTF-8");String fromCardNo = req.getParameter("fromCardNo");String toCardNo = req.getParameter("toCardNo");String moneyStr = req.getParameter("money");int money = Integer.parseInt(moneyStr);Result result = new Result();try {// 2. 调⽤service层⽅法transferService.transfer(fromCardNo, toCardNo, money);result.setStatus("200");} catch (Exception e) {e.printStackTrace();result.setStatus("201");result.setMessage(e.toString());}// 响应resp.setContentType("application/json;charset=utf-8");resp.getWriter().print(JsonUtils.object2Json(result));}
}
(2)TransferService 的实现类
public class TransferServiceImpl implements TransferService {// 需要被改造,当 AccountDao 的实现类发生了切换,这里的代码也要跟着改动了 private AccountDao accountDao = new JdbcAccountDaoImpl();/*** 没有加事务控制*/@Overridepublic void transfer(String fromCardNo, String toCardNo, int money)throws Exception {Account from = accountDao.queryAccountByCardNo(fromCardNo);Account to = accountDao.queryAccountByCardNo(toCardNo);from.setMoney(from.getMoney()-money);to.setMoney(to.getMoney()+money);accountDao.updateAccountByCardNo(from);accountDao.updateAccountByCardNo(to);}
}
(3)AccountDao 基于 JDBC 的实现类
public class JdbcAccountDaoImpl implements AccountDao {@Overridepublic Account queryAccountByCardNo(String cardNo) throws Exception {//从连接池获取连接Connection con = DruidUtils.getInstance().getConnection();String sql = "select * from account where cardNo=?";PreparedStatement preparedStatement = con.prepareStatement(sql);preparedStatement.setString(1,cardNo);ResultSet resultSet = preparedStatement.executeQuery();Account account = new Account();while(resultSet.next()) {account.setCardNo(resultSet.getString("cardNo"));account.setName(resultSet.getString("name"));account.setMoney(resultSet.getInt("money"));}resultSet.close();preparedStatement.close();con.close();return account;}@Overridepublic int updateAccountByCardNo(Account account) throws Exception {//从连接池获取连接Connection con = DruidUtils.getInstance().getConnection();String sql = "update account set money=? where cardNo=?";PreparedStatement preparedStatement = con.prepareStatement(sql);preparedStatement.setInt(1,account.getMoney());preparedStatement.setString(2,account.getCardNo());int i = preparedStatement.executeUpdate();preparedStatement.close();con.close();return i;}
}
5 问题分析
问题⼀:
在上述案例实现中,service 层实现类在使⽤ dao 层对象时,直接在 TransferServiceImpl 中通过 AccountDao accountDao = new JdbcAccountDaoImpl() 获得了 dao层对象,然⽽⼀个 new 关键字却将 TransferServiceImpl 和 dao 层具体的⼀个实现类 JdbcAccountDaoImpl 耦合在了⼀起,如果技术架构发⽣变动,dao 层的实现要使⽤其它技术, ⽐如 Mybatis,思考切换起来的成本?每⼀个 new 的地⽅都需要修改源代码,重新编译,⾯向接⼝开发的意义将⼤打折扣。
问题⼆:service 层代码没有进⾏事务控制 !如果转账过程中出现异常,将可能导致数据库数据错乱,后果可能会很严重,尤其在⾦融业务。
6 问题解决思路
6.1 问题一解决思路
(3)进一步优化:代码中能否只声明所需实例的接⼝类型,不出现 new 也不出现⼯⼚类的字眼?能的!声明⼀个变量并提供 set ⽅法,在反射的时候通过 set 方法注入所需要的对象。
6.2 问题二解决思路
service 层没有添加事务控制,出现异常会导致数据错乱。
分析:数据库事务归根结底是 Connection 的事务,Connection 默认开启了事务自动提交。
connection.commit()
connection.rollback()
(1)两次 update 使用了两个数据库连接 Connection,导致不属于同一个事务控制。
(2)事务控制在 Dao 层,没有控制在 Service 层
解决思路:
(1)让两次 update 使用同一个数据库连接 Connection。因为两次 update 属于同一个线程内的执行调用,可以给当前线程绑定一个 Connection,和当前线程有关的数据库操作都使用该 Connection。
(2)把事务控制添加在 service 层
7 代码改造
7.1 针对问题一的代码改造
(1)beans.xml
<beans><bean id="transferService" class="com.service.impl.TransferServiceImpl"><property name="AccountDao" ref="accountDao"></property></bean><bean id="accountDao" class="com.dao.impl.JdbcAccountDaoImpl"></bean>
</beans>
/*** 工厂类,生产对象(使用反射技术)*/
public class BeanFactory {/*** 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)* 任务二:对外提供获取实例对象的接口(根据id获取)*/private static Map<String,Object> map = new HashMap<>(); // 存储对象static {// 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)// 加载xml,以字节流的形式加载到内存InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");// 解析xmlSAXReader saxReader = new SAXReader();try {Document document = saxReader.read(resourceAsStream);Element rootElement = document.getRootElement();List<Element> beanList = rootElement.selectNodes("//bean");for (int i = 0; i < beanList.size(); i++) {Element element = beanList.get(i);// 处理每个bean元素,获取到该元素的 id 和 class 属性String id = element.attributeValue("id"); String clazz = element.attributeValue("class"); // 通过反射技术实例化对象Class<?> aClass = Class.forName(clazz);Object o = aClass.newInstance(); // 实例化之后的对象// 存储到map中待用map.put(id,o);}// 实例化完成之后维护对象的依赖关系,检查哪些对象需要传值进入,根据它的配置,我们传入相应的值// 有 property 子元素的 bean 就有传值需求List<Element> propertyList = rootElement.selectNodes("//property");// 解析 property,获取父元素for (int i = 0; i < propertyList.size(); i++) {//<property name="AccountDao" ref="accountDao"></property>Element element = propertyList.get(i); String name = element.attributeValue("name");String ref = element.attributeValue("ref");// 找到当前需要被处理依赖关系的beanElement parent = element.getParent();// 调用父元素对象的反射功能String parentId = parent.attributeValue("id");Object parentObject = map.get(parentId);// 遍历父对象中的所有方法,找到 "set" + nameMethod[] methods = parentObject.getClass().getMethods();for (int j = 0; j < methods.length; j++) {Method method = methods[j];// 该方法就是 setAccountDao(AccountDao accountDao)if(method.getName().equalsIgnoreCase("set" + name)) { method.invoke(parentObject,map.get(ref));}}// 把处理之后的parentObject重新放到map中map.put(parentId,parentObject);}} catch (Exception e) {e.printStackTrace();} }// 任务二:对外提供获取实例对象的接口(根据id获取)public static Object getBean(String id) {return map.get(id);}}
(3)改造 TransferServlet
//private TransferService transferService = new TransferServiceImpl();
private TransferService transferService = (TransferService) BeanFactory.getBean("transferService");
(4)改造 TransferServiceImpl
// private AccountDao accountDao = new JdbcAccountDaoImpl();
// private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao");private AccountDao accountDao;
// 构造函数传值/set方法传值
public void setAccountDao(AccountDao accountDao) {this.accountDao = accountDao;
}
7.2 针对问题二的代码改造
7.2.1 改造一
(1)Dao 层,从当前线程获取绑定的 connection 连接
public int updateAccountByCardNo(Account account) throws Exception {// 从连接池获取连接 Connection con = DruidUtils.getInstance().getConnection();// 改造为:从当前线程当中获取绑定的 connection 连接Connection con = ConnectionUtils.getInstance().getCurrentThreadConn();String sql = "update account set money=? where cardNo=?";PreparedStatement preparedStatement = con.prepareStatement(sql);preparedStatement.setInt(1,account.getMoney());preparedStatement.setString(2,account.getCardNo());int i = preparedStatement.executeUpdate();preparedStatement.close();//不用关闭了,不然别的方法用不了了 //con.close();return i;
}
(2)ConnectionUtils 保证一个线程只有一个连接
public class ConnectionUtils {private ConnectionUtils() {}// 单例模式,构造函数私有化private static ConnectionUtils connectionUtils = new ConnectionUtils();public static ConnectionUtils getInstance() {return connectionUtils;}// 存储当前线程的连接private ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); /*** 从当前线程获取连接*/public Connection getCurrentThreadConn() throws SQLException {/*** 判断当前线程中是否已经绑定连接,如果没有绑定,需要从连接池获取一个连接绑定到当前线程*/Connection connection = threadLocal.get();if(connection == null) {// 从连接池拿连接并绑定到线程connection = DruidUtils.getInstance().getConnection();// 绑定到当前线程threadLocal.set(connection);}return connection;}
}
(3)service 层添加事务
public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {//Connection connection = ConnectionUtils.getInstance().getCurrentThreadConn();try{// 开启事务(关闭自动提交事务)//connection.setAutoCommit(false);TransactionManager.getInstance().beginTransaction();Account from = accountDao.queryAccountByCardNo(fromCardNo);Account to = accountDao.queryAccountByCardNo(toCardNo);from.setMoney(from.getMoney()-money);to.setMoney(to.getMoney()+money);accountDao.updateAccountByCardNo(to);int c = 1/0;accountDao.updateAccountByCardNo(from);// 提交事务(正常执行)//connection.commit();TransactionManager.getInstance().commit();}catch (Exception e) {e.printStackTrace();// 回滚事务(发生异常)//connection.rollback();TransactionManager.getInstance().rollback();// 抛出异常便于上层servlet捕获throw e;}
}
(4)增加 TransactionManager 事务管理器类,把事务的管理放在一个类里
/**** 事务管理器类:负责手动事务的开启、提交、回滚*/
public class TransactionManager {private ConnectionUtils connectionUtils;public void setConnectionUtils(ConnectionUtils connectionUtils) {this.connectionUtils = connectionUtils;}// 单例模式private TransactionManager(){}private static TransactionManager transactionManager = new TransactionManager();public static TransactionManager getInstance() {return transactionManager;}// 开启手动事务控制public void beginTransaction() throws SQLException {connectionUtils.getCurrentThreadConn().setAutoCommit(false);}// 提交事务public void commit() throws SQLException {connectionUtils.getCurrentThreadConn().commit();}// 回滚事务public void rollback() throws SQLException {connectionUtils.getCurrentThreadConn().rollback();}
}
思考:如果有 service层的上千百万个类的上千百万个方法都要加事务,咋办?用动态代理,把事务逻辑代码和业务逻辑代码分离。
7.2.2 改造二
采用动态代理改造 Service 层事务控制
磨人的动态代理
(1)代理对象工厂 ProxyFactory
/*** 代理对象工厂:生成代理对象的*/public class ProxyFactory {private ProxyFactory(){}private static ProxyFactory proxyFactory = new ProxyFactory();public static ProxyFactory getInstance() {return proxyFactory;}/*** Jdk动态代理* @param obj 委托对象* @return 代理对象*/public Object getJdkProxy(Object obj) {// 获取代理对象return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result = null;try{// 开启事务(关闭事务的自动提交)transactionManager.beginTransaction();result = method.invoke(obj,args);// 提交事务transactionManager.commit();}catch (Exception e) {e.printStackTrace();// 回滚事务transactionManager.rollback();// 抛出异常便于上层servlet捕获throw e;}return result;}});}/*** 使用cglib动态代理生成代理对象* @param obj 委托对象* @return*/public Object getCglibProxy(Object obj) {return Enhancer.create(obj.getClass(), new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {Object result = null;try{// 开启事务(关闭事务的自动提交)transactionManager.beginTransaction();result = method.invoke(obj,objects);// 提交事务transactionManager.commit();}catch (Exception e) {e.printStackTrace();// 回滚事务transactionManager.rollback();// 抛出异常便于上层servlet捕获throw e;}return result;}});}
}
(2)TransferServlet 改为依赖代理类
// 1. 实例化service层对象
//private TransferService transferService = new TransferServiceImpl();
//private TransferService transferService = (TransferService) BeanFactory.getBean("transferService");// 从工厂中获取代理service的对象
private TransferService transferService = (TransferService) ProxyFactory.getInstance().getJdkProxy(BeanFactory.getBean("transferService")) ;
7.2.3 改造三
把依赖都配置到xml,把实例化交给 BeanFactory
<!--配置新增的三个Bean-->
<bean id="connectionUtils" class="com.utils.ConnectionUtils"></bean><!--事务管理器-->
<bean id="transactionManager" class="com.utils.TransactionManager"><property name="ConnectionUtils" ref="connectionUtils"/>
</bean><!--代理对象工厂-->
<bean id="proxyFactory" class="com.factory.ProxyFactory"><property name="TransactionManager" ref="transactionManager"/>
</bean>
通过AOP和IOC改造银行转账案例相关推荐
- Spring之银行转账案例(七)
文章目录 (一)案例中添加转账方法并演示事务问题 (二)分析事务的问题并编写ConnectionUtils (三)编写事务管理工具类并分析连接和线程解绑 (四)编写业务层和持久层事务控制代码并配置sp ...
- Spring-学习笔记07【银行转账案例】
Java后端 学习路线 笔记汇总表[黑马程序员] Spring-学习笔记01[Spring框架简介][day01] Spring-学习笔记02[程序间耦合] Spring-学习笔记03[Spring的 ...
- AOP和IOC的作用
IOC:控制反转,是一种设计模式.一层含义是控制权的转移:由传统的在程序中控制依赖转移到由容器来控制:第二层是依赖注入:将相互依赖的对象分离,在spring配置文件中描述他们的依赖关系.他们的依赖关系 ...
- 某大型数据中心离心式冷水机组 控制板UPS电源改造项目案例分享
摘 要 离心式冷水机组作为大部分数据中心的核心设备,其是否稳定.安全运行决定着数据中心制冷系统的安全运行.本文分析了某大型数据中心离心式冷水机组控制板UPS电源改造创新案例,分析了改造项目过程中可 ...
- Spring的AOP和IOC是什么?使用场景有哪些?Spring事务与数据库事务,传播行为,数据库隔离级别
Spring的AOP和IOC是什么?使用场景有哪些?Spring事务与数据库事务,传播行为,数据库隔离级别 AOP:面向切面编程. 即在一个功能模块中新增其他功能,比方说你要下楼取个快递,你同事对你说 ...
- Cookie和Session-学习笔记02【Cookie案例、JSP改造Cookie案例】
Java后端 学习路线 笔记汇总表[黑马程序员] Cookie和Session-学习笔记01[Cookie_快速入门.Cookie_细节] Cookie和Session-学习笔记02[Cookie案例 ...
- AOP和IOC的区别
AOP和IOC的区别 二者没有联系.ioc是控制反转,aop是面向事务. IOC就是一个生产和管理bean的容器就行了,原来需要在调用类中new的东西,现在都是通过容器生成,同时,要是产生的是单例的b ...
- spring的AOP和IOC的原理
目录 一.spring的ioc与aop原理 二.代理模式: 三.静态代理 四.动态代理 五.实际的操作 六.动态代理的实现: 七.什么是AOP 八.主流的AOP框架: 九.术语: 十.通知的五种类型: ...
- 低成本 汉朔 墨水屏 msp430g2553主控 改造完整案例
低成本 汉朔 2.13寸墨水屏 msp430g2553主控 改造完整案例 无需额外开发版 首先是原料购买 我们需要用到串口烧录器,CH340G或者CP2102: 注意:如果购买的话尽量买有DTR,RT ...
最新文章
- 这 4 款 MySQL 调优工具 yyds
- outlook地址薄导入到FOXMAIL
- 检查字符串首字母的性能
- 查询数据库占用磁盘大小
- python symbol函数展开_QGIS表达式中的函数
- Nginx服务学习(6)-日志模块
- bean交个spring和new比较区别
- Sharepoint页面里添加.net托管代码
- Linux查看系统cpu个数、核心书、线程数
- php html 目录列表,PHP获取文件目录列表
- asp.net GridView控件的列属性
- 网络安全 Python 编程指南
- 计算机分析桁架受力,结构力学教学中桁架的概念分析与实践
- 九针串口定义(公口、母口图解)
- SpringCloud 第十二章 Gateway新一代网关
- photo技能大全,想学ps的来看看
- project下build.gradle文件和module下buil.gradle
- RoboCupRescue心得
- 通过两个小例子,更快了解-Xms -Xmx
- JavaScript-----JSON和数组对象
热门文章
- 第十六篇 Computed 计算属性的应用
- ThinkPHP3.2 -1146:Table 'lianxi_1.think_from' doesn't exist [ SQL语句 ] : SHOW COLUMNS FROM `think_fro
- ppt矩形里面的图片怎么放大缩小_图形的放大与缩小(公开课 ppt课件).ppt
- 串口硬件流量控制说明
- 小黄鸡 java_Java版本小黄鸡
- 最佳 WordPress 静态缓存插件 WP Super Cache 安装和使用(转)
- [iOS] 文字描边方法
- java实现qq抢红包_Java实现抢红包功能
- 天相投顾这么看新钢钒
- 如何优雅的使用GMS,为此我做了个食用Google服务的magisk microG懒人模块。