自己动手实现一个简单的 IOC,牛皮!!

Java技术栈 2020-11-09 13:13:41
简单 Ioc 实现 一个 动手


我们将分为几步来编写简易 IOC,首先设计组件,再设计接口,然后关注实现。

1. 设计组件。

我们还记得Spring中最重要的有哪些组件吗?BeanFactory 容器,BeanDefinition Bean的基本数据结构,当然还需要加载Bean的资源加载器。大概最后最重要的就是这几个组件。

容器用来存放初始化好的Bean,BeanDefinition 就是Bean的基本数据结构,比如Bean的名称,Bean的属性 PropertyValue,Bean的方法,是否延迟加载,依赖关系等。资源加载器就简单了,就是一个读取XML配置文件的类,读取每个标签并解析。

2. 设计接口

首先肯定需要一个BeanFactory,就是Bean容器,容器接口至少有2个最简单的方法,一个是获取Bean,一个注册Bean.

/**
* 需要一个beanFactory 定义ioc 容器的一些行为 比如根据名称获取bean, 比如注册bean,参数为bean的名称,bean的定义
*
* @author stateis0
* @version 1.0.0
* @Date 2017/11/30
*/public interface BeanFactory {
/**
* 根据bean的名称从容器中获取bean对象
*
* @param name bean 名称
* @return bean实例
* @throws Exception 异常
*/
Object getBean(String name) throws Exception;
/**
* 将bean注册到容器中
*
* @param name bean 名称
* @param bean bean实例
* @throws Exception 异常
*/
void registerBeanDefinition(String name, BeanDefinition bean) throws Exception;}

根据Bean的名字获取Bean对象,注册参数有2个,一个是Bean的名字,一个是 BeanDefinition 对象。

定义完了Bean最基本的容器,还需要一个最简单 BeanDefinition 接口,我们为了方便,但因为我们这个不必考虑扩展,因此可以直接设计为类,BeanDefinition 需要哪些元素和方法呢?

需要一个 Bean 对象,一个Class对象,一个ClassName字符串,还需要一个元素集合 PropertyValues。这些就能组成一个最基本的 BeanDefinition 类了。那么需要哪些方法呢?其实就是这些属性的get set 方法。

我们看看该类的详细:

package cn.thinkinjava.myspring;
/**
* bean 的定义
*
* @author stateis0
*/public class BeanDefinition {
/**
* bean
*/
private Object bean;
/**
* bean 的 CLass 对象
*/
private Class beanClass;
/**
* bean 的类全限定名称
*/
private String ClassName;
/**
* 类的属性集合
*/
private PropertyValues propertyValues = new PropertyValues();
/**
* 获取bean对象
*/
public Object getBean() {
return this.bean;
}
/**
* 设置bean的对象
*/
public void setBean(Object bean) {
this.bean = bean;
}
/**
* 获取bean的Class对象
*/
public Class getBeanclass() {
return this.beanClass;
}
/**
* 通过设置类名称反射生成Class对象
*/
public void setClassname(String name) {
this.ClassName = name;
try {
this.beanClass = Class.forName(name);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取bean的属性集合
*/
public PropertyValues getPropertyValues() {
return this.propertyValues;
}
/**
* 设置bean的属性
*/
public void setPropertyValues(PropertyValues pv) {
this.propertyValues = pv;
}
}

有了基本的 BeanDefinition 数据结构,还需要一个从XML中读取并解析为 BeanDefinition 的操作类,首先我们定义一个 BeanDefinitionReader 接口,该接口只是一个标识,具体由抽象类去实现一个基本方法和定义一些基本属性,比如一个读取时需要存放的注册容器,还需要一个委托一个资源加载器 ResourceLoader, 用于加载XML文件,并且我们需要设置该构造器必须含有资源加载器,当然还有一些get set 方法。

package cn.thinkinjava.myspring;
import cn.thinkinjava.myspring.io.ResourceLoader;
import java.util.HashMap;
import java.util.Map;
/**
* 抽象的bean定义读取类
*
* @author stateis0
*
/
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {
/**
* 注册bean容器
*/
private Map<String, BeanDefinition> registry;
/**
* 资源加载器
*/
private ResourceLoader resourceLoader;
/**
* 构造器器必须有一个资源加载器, 默认插件创建一个map容器
*
* @param resourceLoader 资源加载器
*/
protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
this.registry = new HashMap<>();
this.resourceLoader = resourceLoader;
}
/**
* 获取容器
*/
public Map<String, BeanDefinition> getRegistry() {
return registry;
}
/**
* 获取资源加载器
*/
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
}

有了这几个抽象类和接口,我们基本能形成一个雏形,BeanDefinitionReader 用于从XML中读取配置文件,生成 BeanDefinition 实例,存放在 BeanFactory 容器中,初始化之后,就可以调用 getBean 方法获取初始化成功的Bean。形成一个完美的闭环。

3. 如何实现

刚刚我们说了具体的流程:从XML中读取配置文件, 解析成 BeanDefinition,最终放进容器。说白了就3步。那么我们就先来设计第一步。

1. 从XML中读取配置文件, 解析成 BeanDefinition

我们刚刚设计了一个读取BeanDefinition 的接口 BeanDefinitionReader 和一个实现它的抽象类 AbstractBeanDefinitionReader,抽象了定义了一些简单的方法,其中由一个委托类-----ResourceLoader, 我们还没有创建, 该类是资源加载器,根据给定的路径来加载资源。

我们可以使用Java 默认的类库 java.net.URL 来实现,定义两个类,一个是包装了URL的类 ResourceUrl, 一个是依赖 ResourceUrl 的资源加载类。

ResourceUrl 代码实现

/**
* 资源URL
*
/
public class ResourceUrl implements Resource {
/**
* 类库URL
*/
private final URL url;
/**
* 需要一个类库URL
*/
public ResourceUrl(URL url) {
this.url = url;
}
/**
* 从URL中获取输入流
*/
@Override
public InputStream getInputstream() throws Exception {
URLConnection urlConnection = url.openConnection();
urlConnection.connect();
return urlConnection.getInputStream();
}
}

ResourceLoader 实现

/**
* 资源URL
*/
public class ResourceUrl implements Resource {
/**
* 类库URL
*/
private final URL url;
/**
* 需要一个类库URL
*/
public ResourceUrl(URL url) {
this.url = url;
}
/**
* 从URL中获取输入流
*/
@Override
public InputStream getInputstream() throws Exception {
URLConnection urlConnection = url.openConnection();
urlConnection.connect();
return urlConnection.getInputStream();
}
}

当然还需要一个接口,只定义了一个抽象方法

package cn.thinkinjava.myspring.io;
import java.io.InputStream;
/**
* 资源定义
*
* @author stateis0
*/
public interface Resource {
/**
* 获取输入流
*/
InputStream getInputstream() throws Exception;
}

好了, AbstractBeanDefinitionReader 需要的元素已经有了,但是,很明显该方法不能实现读取 BeanDefinition 的任务。那么我们需要一个类去继承抽象类,去实现具体的方法, 既然我们是XML 配置文件读取,那么我们就定义一个 XmlBeanDefinitionReader 继承 AbstractBeanDefinitionReader ,实现一些我们需要的方法, 比如读取XML 的readrXML, 比如将解析出来的元素注册到 registry 的 Map 中, 一些解析的细节。我们还是看代码吧。

XmlBeanDefinitionReader 实现读取配置文件并解析成Bean

package cn.thinkinjava.myspring.xml;
import cn.thinkinjava.myspring.AbstractBeanDefinitionReader;
import cn.thinkinjava.myspring.BeanDefinition;
import cn.thinkinjava.myspring.BeanReference;
import cn.thinkinjava.myspring.PropertyValue;
import cn.thinkinjava.myspring.io.ResourceLoader;
import java.io.InputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* 解析XML文件
*
* @author stateis0
*/
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
/**
* 构造器,必须包含一个资源加载器
*
* @param resourceLoader 资源加载器
*/
public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
super(resourceLoader);
}
public void readerXML(String location) throws Exception {
// 创建一个资源加载器
ResourceLoader resourceloader = new ResourceLoader();
// 从资源加载器中获取输入流
InputStream inputstream = resourceloader.getResource(location).getInputstream();
// 获取文档建造者工厂实例
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 工厂创建文档建造者
DocumentBuilder docBuilder = factory.newDocumentBuilder();
// 文档建造者解析流 返回文档对象
Document doc = docBuilder.parse(inputstream);
// 根据给定的文档对象进行解析,并注册到bean容器中
registerBeanDefinitions(doc);
// 关闭流
inputstream.close();
}
/**
* 根据给定的文档对象进行解析,并注册到bean容器中
*
* @param doc 文档对象
*/
private void registerBeanDefinitions(Document doc) {
// 读取文档的根元素
Element root = doc.getDocumentElement();
// 解析元素的根节点及根节点下的所有子节点并添加进注册容器
parseBeanDefinitions(root);
}
/**
* 解析元素的根节点及根节点下的所有子节点并添加进注册容器
*
* @param root XML 文件根节点
*/
private void parseBeanDefinitions(Element root) {
// 读取根元素的所有子元素
NodeList nl = root.getChildNodes();
// 遍历子元素
for (int i = 0; i < nl.getLength(); i++) {
// 获取根元素的给定位置的节点
Node node = nl.item(i);
// 类型判断
if (node instanceof Element) {
// 强转为父类型元素
Element ele = (Element) node;
// 解析给给定的节点,包括name,class,property, name, value,ref
processBeanDefinition(ele);
}
}
}
/**
* 解析给给定的节点,包括name,class,property, name, value,ref
*
* @param ele XML 解析元素
*/
private void processBeanDefinition(Element ele) {
// 获取给定元素的 name 属性
String name = ele.getAttribute("name");
// 获取给定元素的 class 属性
String className = ele.getAttribute("class");
// 创建一个bean定义对象
BeanDefinition beanDefinition = new BeanDefinition();
// 设置bean 定义对象的 全限定类名
beanDefinition.setClassname(className);
// 向 bean 注入配置文件中的成员变量
addPropertyValues(ele, beanDefinition);
// 向注册容器 添加bean名称和bean定义
getRegistry().put(name, beanDefinition);
}
/**
* 添加配置文件中的属性元素到bean定义实例中
*
* @param ele 元素
* @param beandefinition bean定义 对象
*/
private void addPropertyValues(Element ele, BeanDefinition beandefinition) {
// 获取给定元素的 property 属性集合
NodeList propertyNode = ele.getElementsByTagName("property");
// 循环集合
for (int i = 0; i < propertyNode.getLength(); i++) {
// 获取集合中某个给定位置的节点
Node node = propertyNode.item(i);
// 类型判断
if (node instanceof Element) {
// 将节点向下强转为子元素
Element propertyEle = (Element) node;
// 元素对象获取 name 属性
String name = propertyEle.getAttribute("name");
// 元素对象获取 value 属性值
String value = propertyEle.getAttribute("value");
// 判断value不为空
if (value != null && value.length() > 0) {
// 向给定的 “bean定义” 实例中添加该成员变量
beandefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
} else {
// 如果为空,则获取属性ref
String ref = propertyEle.getAttribute("ref");
if (ref == null || ref.length() == 0) {
// 如果属性ref为空,则抛出异常
throw new IllegalArgumentException(
"Configuration problem: <property> element for property '"
+ name + "' must specify a ref or value");
}
// 如果不为空,测创建一个 “bean的引用” 实例,构造参数为名称,实例暂时为空
BeanReference beanRef = new BeanReference(name);
// 向给定的 “bean定义” 中添加成员变量
beandefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanRef));
}
}
}
}
}

可以说代码注释写的非常详细,该类方法如下:

  1. public void readerXML(String location) 公开的解析XML的方法,给定一个位置的字符串参数即可。

  2. private void registerBeanDefinitions(Document doc) 给定一个文档对象,并进行解析。

  3. private void parseBeanDefinitions(Element root) 给定一个根元素,循环解析根元素下所有子元素。

  4. private void processBeanDefinition(Element ele) 给定一个子元素,并对元素进行解析,然后拿着解析出来的数据创建一个 BeanDefinition 对象。并注册到BeanDefinitionReader 的 Map 容器(该容器存放着解析时的所有Bean)中。

  5. private void addPropertyValues(Element ele, BeanDefinition beandefinition) 给定一个元素,一个 BeanDefinition 对象,解析元素中的 property 元素, 并注入到 BeanDefinition 实例中。

一共5步,完成了解析XML文件的所有操作。 最终的目的是将解析出来的文件放入到 BeanDefinitionReader 的 Map 容器中。

好了,到这里,我们已经完成了从XML文件读取并解析的步骤,那么什么时候放进BeanFactory的容器呢? 刚刚我们只是放进了 AbstractBeanDefinitionReader 的注册容器中。因此我们要根据BeanFactory 的设计来实现如何构建成一个真正能用的Bean呢?因为刚才的哪些Bean只是一些Bean的信息。没有我们真正业务需要的Bean。

2. 初始化我们需要的Bean(不是Bean定义)并且实现依赖注入

我们知道Bean定义是不能干活的,只是一些Bean的信息,就好比一个人,BeanDefinition 就相当你在公安局的档案,但是你人不在公安局,可只要公安局拿着你的档案就能找到你。就是这样一个关系。

那我们就根据BeanFactory的设计来设计一个抽象类 AbstractBeanFactory。

package cn.thinkinjava.myspring.factory;
import cn.thinkinjava.myspring.BeanDefinition;
import java.util.HashMap;
/**
* 一个抽象类, 实现了 bean 的方法,包含一个map,用于存储bean 的名字和bean的定义
*
* @author stateis0
*/
public abstract class AbstractBeanFactory implements BeanFactory {
/**
* 容器
*/
private HashMap<String, BeanDefinition> map = new HashMap<>();
/**
* 根据bean的名称获取bean, 如果没有,则抛出异常 如果有, 则从bean定义对象获取bean实例
*/
@Override
public Object getBean(String name) throws Exception {
BeanDefinition beandefinition = map.get(name);
if (beandefinition == null) {
throw new IllegalArgumentException("No bean named " + name + " is defined");
}
Object bean = beandefinition.getBean();
if (bean == null) {
bean = doCreate(beandefinition);
}
return bean;
}
/**
* 注册 bean定义 的抽象方法实现,这是一个模板方法, 调用子类方法doCreate,
*/
@Override
public void registerBeanDefinition(String name, BeanDefinition beandefinition) throws Exception {
Object bean = doCreate(beandefinition);
beandefinition.setBean(bean);
map.put(name, beandefinition);
}
/**
* 减少一个bean
*/
abstract Object doCreate(BeanDefinition beandefinition) throws Exception;
}package cn.thinkinjava.myspring.factory;
import cn.thinkinjava.myspring.BeanDefinition;
import cn.thinkinjava.myspring.PropertyValue;
import cn.thinkinjava.myspring.BeanReference;
import java.lang.reflect.Field;
/**
* 实现自动注入和递归注入(spring 的标准实现类 DefaultListableBeanFactory 有 1810 行)
*
* @author stateis0
*/
public class AutowireBeanFactory extends AbstractBeanFactory {
/**
* 根据bean 定义创建实例, 并将实例作为key, bean定义作为value存放,并调用 addPropertyValue 方法 为给定的bean的属性进行注入
*/
@Override
protected Object doCreate(BeanDefinition beandefinition) throws Exception {
Object bean = beandefinition.getBeanclass().newInstance();
addPropertyValue(bean, beandefinition);
return bean;
}
/**
* 给定一个bean定义和一个bean实例,为给定的bean中的属性注入实例。
*/
protected void addPropertyValue(Object bean, BeanDefinition beandefinition) throws Exception {
// 循环给定 bean 的属性集合
for (PropertyValue pv : beandefinition.getPropertyValues().getPropertyValues()) {
// 根据给定属性名称获取 给定的bean中的属性对象
Field declaredField = bean.getClass().getDeclaredField(pv.getname());
// 设置属性的访问权限
declaredField.setAccessible(true);
// 获取定义的属性中的对象
Object value = pv.getvalue();
// 判断这个对象是否是 BeanReference 对象
if (value instanceof BeanReference) {
// 将属性对象转为 BeanReference 对象
BeanReference beanReference = (BeanReference) value;
// 调用父类的 AbstractBeanFactory 的 getBean 方法,根据bean引用的名称获取实例,此处即是递归
value = getBean(beanReference.getName());
}
// 反射注入bean的属性
declaredField.set(bean, value);
}
}
}

可以看到 doCreate 方法使用了反射创建了一个对象,并且还需要对该对象进行属性注入,如果属性是 ref 类型,那么既是依赖关系,则需要调用 getBean 方法递归的去寻找那个Bean(因为最后一个Bean 的属性肯定是基本类型)。这样就完成了一次获取实例化Bean操作,并且也实现类依赖注入。

4. 总结

我们通过这些代码实现了一个简单的 IOC 依赖注入的功能,也更加了解了 IOC, 以后遇到Spring初始化的问题再也不会手足无措了。直接看源码就能解决。哈哈

good luck !!!

作者:莫那一鲁道
链接:https://www.jianshu.com/p/6e25dc62e3a1
近期热文推荐:

1.Java 15 正式发布, 14 个新特性,刷新你的认知!!

2.终于靠开源项目弄到 IntelliJ IDEA 激活码了,真香!

3.我用 Java 8 写了一段逻辑,同事直呼看不懂,你试试看。。

4.吊打 Tomcat ,Undertow 性能很炸!!

5.《Java开发手册(嵩山版)》最新发布,速速下载!

觉得不错,别忘了随手点赞+转发哦!

版权声明
本文为[Java技术栈]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/javastack/p/13948038.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云