Реализация простого IOC своими руками

Java задняя часть Spring Element

В предыдущей статье арендодатель и все вместе проанализировали реализацию Spring IOC и проанализировали исходный код Spring. Видно, что исходный код очень сложен. Это связано с тем, что разработчикам Spring необходимо учитывать масштабируемость, надежность и производительность фреймворка.Ожидаются элементы, поэтому конструкция сложная. В конце арендодатель также сказал, что нам нужно реализовать простой IOC, чтобы мы могли глубже понять IOC, поэтому у нас есть эта статья.

Конечно, мы смоделированы по образцу IOC Spring, поэтому кодовое наименование и дизайн в основном смоделированы по образцу Spring.

Мы напишем простой IOC в несколько шагов, сначала разработав компонент, затем разработав интерфейс, а затем сосредоточившись на реализации.

1. Спроектируйте компонент.

Помним ли мы, какие самые важные компоненты в Spring?BeanFactoryконтейнер,BeanDefinitionБазовая структура данных Бина, конечно же, также нужна для загрузки данных Бина.资源加载器. Вероятно, последними и наиболее важными являются эти компоненты. Контейнер используется для хранения инициализированных бинов BeanDefinition — это базовая структура данных бина, такая как имя бина и свойства бина.PropertyValue, методы Бина, будь то ленивая загрузка, зависимости и т.д. Загрузчик ресурсов прост, это класс, который читает файл конфигурации XML, считывает каждый тег и анализирует его.

2. Дизайн интерфейса

Прежде всего, вам понадобится BeanFactory, который является контейнером Bean.У интерфейса контейнера есть по крайней мере два простейших метода: один для получения 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 получается по имени бина.Есть два регистрационных параметра, один - имя бина, а другой - объект BeanDefinition.

После определения самого базового контейнера Bean нам также понадобится простейший интерфейс BeanDefinition. Для удобства, но поскольку нам не нужно рассматривать расширение, мы можем напрямую спроектировать его как класс. Какие элементы и методы нужны BeanDefinition? Требуется объект Bean, объект Class, строка ClassName и коллекция элементов PropertyValues. Они могут формировать базовый класс BeanDefinition. Итак, какие методы необходимы? По сути, это метод установки этих свойств. Давайте посмотрим на детали этого класса:

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 методы.

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 для получения успешной инициализации. , Фасоль. образуют идеальный замкнутый цикл.

3. Как реализовать

Мы только что говорили о конкретном процессе: прочитать файл конфигурации из XML, разобрать его в BeanDefinition и, наконец, поместить в контейнер. Грубо говоря, 3 шага. Итак, давайте сделаем первый шаг проектирования.

1. Считайте файл конфигурации из XML и разберите его в BeanDefinition.

Мы только что разработали интерфейс BeanDefinitionReader, который считывает BeanDefinition, и абстрактный класс AbstractBeanDefinitionReader, который его реализует, абстрагируя и определяя некоторые простые методы, который состоит из класса делегата ----- ResourceLoader, который мы еще не создали, этот класс является Загрузчик ресурсов, загружает ресурсы по указанному пути. Для этого мы можем использовать библиотеку классов Java по умолчанию java.net.URL и определить два класса: один — это класс ResourceUrl, который обертывает URL-адрес, а другой — класс загрузки ресурсов, зависящий от 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 для реализации некоторых необходимых нам методов, таких как readrXML для чтения XML, таких как проанализированные элементы прописаны в Карте реестра, некоторые детали парсинга. Давайте посмотрим на код.

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 с проанализированными данными. И зарегистрируйте его в контейнере Map BeanDefinitionReader (контейнер хранит все бины на момент парсинга).
  5. private void addPropertyValues(Element ele, BeanDefinition beandefinition) Учитывая элемент, объект BeanDefinition анализирует элемент свойства в элементе и внедряет его в экземпляр BeanDefinition.

Всего за 5 шагов выполнены все операции разбора XML-файла. Конечная цель — поместить проанализированный файл в контейнер карты BeanDefinitionReader.

Ну вот, мы завершили шаги по чтению и анализу XML-файла, так когда же мы поместим его в контейнер BeanFactory? Только что мы просто поместили в регистрационный контейнер AbstractBeanDefinitionReader. Итак, нам нужно понять, как создать настоящий пригодный для использования Бин в соответствии с дизайном BeanFactory? Потому что сейчас Bean — это всего лишь некоторая информация о Bean. Нет бобов, которые нужны нашему реальному бизнесу.

2. Инициализируем нужные нам bean-компоненты (не определения bean-компонентов) и реализуем внедрение зависимостей

Мы знаем, что определение Bean не может работать, это просто некоторая информация Bean, точно так же, как человек, BeanDefinition эквивалентен вашему файлу в Бюро общественной безопасности, но вы не в Бюро общественной безопасности, а до тех пор, пока Бюро общественной безопасности есть ваш файл, вы можете найти его вы. Это такие отношения.

Затем мы разрабатываем абстрактный класс AbstractBeanFactory в соответствии с дизайном BeanFactory.

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;
}

Этот класс реализует два основных метода интерфейса, один — getBean, а другой — registerBeanDefinition.Мы также разрабатываем абстрактный метод для вызова этих двух методов и откладываем логику создания конкретной логики до подкласса. Что это за шаблон проектирования? Шаблонный режим. Главное смотреть на метод doCreate, который должен создать конкретный метод бина, поэтому нам еще нужен подкласс, как он называется?AutowireBeanFactory, который автоматически внедряет бины, это работа нашей стандартной фабрики бинов. Взгляните на код, пожалуйста?

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 этот боб (потому что последняя фана, имущество, безусловно, является примитивным типом). Это завершает работу для получения экземпляра фасоли, а также реализует впрыск классов зависимости.

4. Резюме

Мы реализовали простую функцию внедрения зависимостей IOC с помощью этих кодов и лучше понимаем IOC, так что мы больше не будем в растерянности, когда столкнемся с проблемами инициализации Spring в будущем. Это можно решить напрямую, посмотрев исходный код. Ха-ха

Конкретный код арендодателя размещен на github, адрес:Простой IOC, реализованный мной, включая внедрение зависимостей

удачи! ! !