自己动手实现一个简单的 IOC,牛皮!!
发布日期:2021-06-30 12:54:04 浏览次数:2 分类:技术文章

本文共 12770 字,大约阅读时间需要 42 分钟。

Java技术栈

www.javastack.cn

关注阅读更多优质文章

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

1. 设计组件。

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

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

2. 设计接口

首先肯定需要一个,就是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;}

我们看看该类的详细:

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
 registry;   /**   * 资源加载器   */  private ResourceLoader resourceLoader;   /**   * 构造器器必须有一个资源加载器, 默认插件创建一个map容器   *   * @param resourceLoader 资源加载器   */  protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {    this.registry = new HashMap<>();    this.resourceLoader = resourceLoader;  }   /**   * 获取容器   */  public Map
 getRegistry() {    return registry;  }   /**   * 获取资源加载器   */  public ResourceLoader getResourceLoader() {    return resourceLoader;  }  }

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

3. 如何实现

刚刚我们说了具体的流程:从XML中读取配置文件, 解析成 BeanDefinition,最终放进容器。说白了就3步。那么我们就先来设计第一步。推荐看下《,关注公众号Java技术栈获取更多Spring系列教程。

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: 
 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  实例中。

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
 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初始化的问题再也不会手足无措了,直接看源码就能解决。关注公众号Java技术栈将继续分享更多手写开源框架系列。

good luck !!!

作者:莫那一鲁道

链接:https://www.jianshu.com/p/6e25dc62e3a1

关注Java技术栈看更多干货

戳原文,获取精选面试题!

转载地址:https://javastack.blog.csdn.net/article/details/109567608 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:手写一个 Redis 客户端,So easy~
下一篇:Java只有值传递!为什么?

发表评论

最新留言

很好
[***.229.124.182]2024年04月07日 12时01分37秒