手撸一个IOC容器
IoC
什么是IoC?
IoC是Inversion of Control(控制反转)的简称,注意它是一个技术思想。描述的是对象创建、管理的事情。
传统开发方式:比如类A依赖类B,往往会在类A里面new一个B的对象。
IoC开发方式:我们不用去new对象,由IoC容器帮我们实例化对象并进行管理。我们需要B对象,就问IoC容器要即可。
控制反转就是说将对象创建、管理的权力交给了外部环境(IoC容器)。
IoC的作用:解决了对象之间的耦合问题。
什么是DI?
DI是Dependancy Injection(依赖注入)的简称,指容器会把对象依赖的其他对象注入。比如A对象里声明了一个B的属性,那么就需要容器把B对象注入给A。
什么是AOP?
AOP是 Aspect oriented Programming(⾯向切⾯编程)的简称。
在上面的代码中,多个方法都出现了相同的代码(可以称之为横切逻辑代码)。这部分代码不仅重复,而且跟业务逻辑没有关系但是混杂在一起。这时AOP出现了,它提供了横向抽取机制,将这部分横切代码和业务逻辑代码分开。
AOP的作用:在不改变原有业务逻辑的情况下,增强横切逻辑代码,解耦合。
手写IOC
首先我们看一下在没有Spring之前,我们是怎么开发一个web程序的呢?
那么针对上面的两个问题,我们如何进行解决呢?
- 我们除了用new实例化对象外,还可以用反射的技术。
- 另外项目中往往有很多对象需要实例化,那么可以使用工厂模式来进行优化。
综上,我们可以用工厂模式+反射技术把对象都实例化好,放在一个map里面,如果需要某个对象,就可以直接从这个map里面取。
除此之外,我们还需要一个xml文件,里面来定义对象的全类名(反射需要),如果有依赖,还需要定义类与类之间的依赖关系。
<beans>
<bean id="accountDao" class="com.mmc.ioc.dao.impl.AccountDaoImpl"></bean>
<bean id="transferService" class="com.mmc.ioc.service.impl.TransferServiceImpl">
<!--这里的name默认为set+name就是方法名-->
<property name="AccountDao" ref="accountDao"></property>
</bean>
</beans>
核心代码:
public class BeanFactory {
private static Map<String,Object> beanMap=new HashMap<>();
static {
InputStream inputStream=BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
SAXReader saxReader=new SAXReader();
try {
Document document = saxReader.read(inputStream);
Element rootElement = document.getRootElement();
List<Element> beans = rootElement.selectNodes("//bean");
for (Element element:beans){
String id = element.attributeValue("id");
String clazz = element.attributeValue("class");
Object instance = Class.forName(clazz).newInstance();
beanMap.put(id,instance);
}
//实例完后填充对象的依赖
List<Element> propertys = rootElement.selectNodes("//property");
for (Element element:propertys){
String name = element.attributeValue("name");
String ref = element.attributeValue("ref");
Element parent = element.getParent();
String parentId = parent.attributeValue("id");
Object instance = beanMap.get(parentId);
Object refInstance = beanMap.get(ref);
Method setMethod = instance.getClass().getDeclaredMethod("set" + name,refInstance.getClass().getInterfaces());
setMethod.invoke(instance,beanMap.get(ref));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static Object getBean(String name){
return beanMap.get(name);
}
}
那么接下来我们想要使用对象的时候,就不用new了,而是从beanFactory里面去拿。
这样一个简易的AOP就完成了。
手写AOP实现
我们解决了上面的问题1,那么问题2事务控制如何解决呢?
分析:数据库事务归根结底是Connection的事务,connection.commit()提交事务,connection.rollback()回滚事务。
- 我们是想保证service里的方法里面执行的众多数据库操作要么都成功,要么都失败。
- 同一个service方法里面的dao层必须要使用的是同一个connection,也就是说同一个线程内要是同一个connection,所以可以使用ThreadLocal实现
改造完成:
@Override
public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {
//关闭自动提交
connectionUtils.getThreadConn().setAutoCommit(false);
try {
Account from = accountDao.queryAccountByCardNo(fromCardNo);
Account to = accountDao.queryAccountByCardNo(toCardNo);
from.setMoney(from.getMoney()-money);
to.setMoney(to.getMoney()+money);
accountDao.updateAccountByCardNo(to);
int i=10/0;
accountDao.updateAccountByCardNo(from);
//提交事务
connectionUtils.getThreadConn().commit();
} catch (Exception e) {
//回滚事务
connectionUtils.getThreadConn().rollback();
throw e;
}
}
在两次update语句中间手动加了个异常,可以发现数据库两条数据都没变,说明事务控制成功。
但是如果多个方法都需要加事务控制的话,我们需要给多个方法加上下面这一套重复的代码
connectionUtils.getThreadConn().setAutoCommit(false);
try {
//省略部分代码
// -----
//提交事务
connectionUtils.getThreadConn().commit();
} catch (Exception e) {
//回滚事务
connectionUtils.getThreadConn().rollback();
throw e;
}
怎么解决呢?
我们可以通过代理模式给每个方法代理
代码如下:
public class ProxyFactory {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
public Object getJdkProxy(Object object){
return Proxy.newProxyInstance(ProxyFactory.class.getClassLoader(), object.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//关闭自动提交
connectionUtils.getThreadConn().setAutoCommit(false);
Object result;
try {
result= method.invoke(object,args);
//提交事务
connectionUtils.getThreadConn().commit();
} catch (Exception e) {
//回滚事务
connectionUtils.getThreadConn().rollback();
throw e;
}
return result;
}
});
}
}
每个需要加事务的对象,只要调用getJdkProxy方法获取到代理对象,再使用代理对象执行方法,就能实现事务控制了。
使用方法如下:
private ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
private TransferService transferService= (TransferService) proxyFactory.getJdkProxy(BeanFactory.getBean("transferService"));
这样就相当于把横切逻辑代码提取出来了,如果把这套机制抽出来就是AOP的实现了。
手撸一个IOC容器的更多相关文章
- 五分钟,手撸一个Spring容器!
大家好,我是老三,Spring是我们最常用的开源框架,经过多年发展,Spring已经发展成枝繁叶茂的大树,让我们难以窥其全貌. 这节,我们回归Spring的本质,五分钟手撸一个Spring容器,揭开S ...
- 手写一个IOC容器
链接:https://pan.baidu.com/s/1MhKJYamBY1ejjjhz3BKoWQ 提取码:e8on 明白什么是IOC容器: IOC(Inversion of Control,控制反 ...
- 曹工说Tomcat4:利用 Digester 手撸一个轻量的 Spring IOC容器
一.前言 一共8个类,撸一个IOC容器.当然,我们是很轻量级的,但能够满足基本需求.想想典型的 Spring 项目,是不是就是各种Service/DAO/Controller,大家互相注入,就组装成了 ...
- 通过 Netty、ZooKeeper 手撸一个 RPC 服务
说明 项目链接 微服务框架都包括什么? 如何实现 RPC 远程调用? 开源 RPC 框架 限定语言 跨语言 RPC 框架 本地 Docker 搭建 ZooKeeper 下载镜像 启动容器 查看容器日志 ...
- 手撸一个SpringBoot-Starter
1. 简介 通过了解SpringBoot的原理后,我们可以手撸一个spring-boot-starter来加深理解. 1.1 什么是starter spring官网解释 starters是一组方便的依 ...
- 使用Java Socket手撸一个http服务器
原文连接:使用Java Socket手撸一个http服务器 作为一个java后端,提供http服务可以说是基本技能之一了,但是你真的了解http协议么?你知道知道如何手撸一个http服务器么?tomc ...
- 【手撸一个ORM】MyOrm的使用说明
[手撸一个ORM]第一步.约定和实体描述 [手撸一个ORM]第二步.封装实体描述和实体属性描述 [手撸一个ORM]第三步.SQL语句构造器和SqlParameter封装 [手撸一个ORM]第四步.Ex ...
- 第二篇-用Flutter手撸一个抖音国内版,看看有多炫
前言 继上一篇使用Flutter开发的抖音国际版 后再次撸一个国内版抖音,大部分功能已完成,主要是Flutter开发APP速度很爽, 先看下图 项目主要结构介绍 这次主要的改动在api.dart 及 ...
- C#基于Mongo的官方驱动手撸一个Super简易版MongoDB-ORM框架
C#基于Mongo的官方驱动手撸一个简易版MongoDB-ORM框架 如题,在GitHub上找了一圈想找一个MongoDB的的ORM框架,未偿所愿,就去翻了翻官网(https://docs.mongo ...
随机推荐
- LR11可打开网页,但录制为空
环境:WIN7+LR11+360安全浏览器9.0 LR11可打开网页,但录制为空解决方案:(3步) 1. tools-Recording Options -->Network-->Port ...
- P5325-[模板]Min_25筛
正题 题目链接:https://www.luogu.com.cn/problem/P5325 题目大意 定义一个积性函数满足\(f(p^k)=p^k(p^k-1)\) 求\(\sum_{i=1}^nf ...
- p3c 插件,是怎么检查出你那屎山的代码?
作者:小傅哥 博客:https://bugstack.cn 原文:https://mp.weixin.qq.com/s/RwzprbY2AhdgslY8tbVL-A 一.前言 你会对你用到都技术,好奇 ...
- Dapr + .NET Core实战(九)本地调试
前几节开发Dapr应用程序时,我们使用 dapr cli 来启动dapr服务,就像这样: dapr run --dapr-http-port 3501 --app-port 5001 --app-id ...
- 踩坑系列《七》解决VMware安装完成之后,不能联网的问题
成功安装CentOS 6.5 好之后,它是默认并不能联网,这时候得需要对root用户进行网络设置 1.先登录root账户 2.命令行输入以下命令,修改配置文件 vim /etc/sysconfig/n ...
- 使用Python写词云数据可视化
词云的应用场景 会议记录 海报制作 PPT制作 生日表白 数据挖掘 情感分析 用户画像 微信聊天记录分析 微博情感分析 Bilibili弹幕情感分析 年终总结 安装本课程所需的Python第三方模块 ...
- appium操作安卓应用所需要的数据准备
操作系统.系统版本如下所示: desired_caps={} desired_caps["platformName"]="Android" desired_ca ...
- JVM详解(四)——运行时数据区-堆
一.堆 1.介绍 Java运行程序对应一个进程,一个进程就对应一个JVM实例.一个JVM实例就有一个运行时数据区(Runtime),Runtime里面,就只有一个堆,一个方法区.这里也阐述了,方法区和 ...
- kettle使用
Kettle的安装及简单使用 目录 Kettle的安装及简单使用 一.kettle概述 二.kettle安装部署和使用 Windows下安装 案例1:MySQL to MySQL 案例2:使用作业执行 ...
- django 中的hello word 开心,通过申请博客了,,发个随笔庆祝一下~~~~~~~
django 中的hello word! 准备:[pymsql,pycharm,django3.0.7] >>>终端中:django-admin.py startproject [项 ...