实现简单的 IOC 容器

本文介绍了实现一个简单的IOC容器的步骤,包括读取XML配置文件、加载Element元素、解析Element、生成bean实例并进行属性注入。通过理解这些步骤,有助于深入理解Spring框架的IOC机制。文中详细讲解了如何从XML文件中解析配置信息,生成并注入bean实例。
摘要由CSDN通过智能技术生成

最近在看Spring的源码,对依赖xml配置文件的IOC容器的实现部分阅读起来比较吃力。主要是因为自己对IOC实现机制不是很了解,于是萌生了按其原理实现一个简单的IOC容器,来以便自己对SpringIOC 实现能有更深入的了解。

什么是 IOC

IOC 是英文控制反转的缩写,是一种设计思想。对于Spring 框架来说,IOC 就是由 Spring 来负责对象的生命周期和对象间的关系。以前需要一个对象,就得 new 一个来保持对象的引用,对象耦合严重。而 Spring IOC容易就统一负责对象的关系,动态地向某个对象提供它所需要的其它对象。

实现步骤

实现一个 IOC 需要哪些步骤呢,我把它总结为了下面几点。用过 Spring 的都知道,Spring 是用xml 配置文件来配置 bean的。所以首先是要读取和解析 xml 配置文件来生成 bean 实例。
1. 读取 xml 配置文件。Spring 框架是采用 dom4j 框架来读取 xml 文件。它将 xml 文件生成 Document 对象,xml 配置属性在 Document 对象中都能找到,xml 的配置属性和 Document对象的属性是一一对应的。
2. 加载Document对象中的 Element。什么是 Element,这么来说吧,xml 文件中<bean> </bean> 是一个 Element<bean> 下面的 <constructor-arg> 也是一个Elementxml 中每一个配置属性都是用 Document 对象中的 Element来表示。
3. 解析Element。将加载的 Element 属性进行解析。比如解析<bean>class 属性值,就可以找到这个 bean 类的位置,并生成他 的对象实例。
4. 生成实例并缓存。根据第三步解析到的数据生成 bean 实例,并缓存。
5. 注入属性。根据第三步解析到属性对 bean实例进行属性注入。
最后实现一个总的容器类将前几步整合起来,并对外提供获取 bean 实例的接口。现在按照这个步骤依从实现各部分的功能。

读取 xml 配置文件。

配置一个接口类,该接口类有一个 getDocument(String path) 方法。通过传入 xml 文件的位置路径来生成 Document 对象。

// XML 文件读取接口类
public interface DocumentHolder {

    Document getDocument(String filePath);

}

来看看该接口的具体实现方法。

public class XMLDocumentHolder implements DocumentHolder {
   

    //建立一个HashMap用来存放字符串和文档
    private Map<String, Document> docs = new HashMap<String, Document>();

    @Override
    public Document getDocument(String filePath) {

        Document doc=this.docs.get(filePath);//用HashMap先根据路径获取文档

        if (doc==null) {

            this.docs.put(filePath, readDocument(filePath)); //如果为空,把路径和文档放进去

        }

        return  this.docs.get(filePath);
    }

    /**
     * 根据路径读Document
     * @param filePath
     * @return
     */
    private Document readDocument(String filePath) {

        Document doc =null;

        try {

            SAXReader reader = new SAXReader(true);//借用dom4j的解析器

            reader.setEntityResolver(new IoCEntityResolver());

            File xmlFile = new File(filePath); //根据路径创建文件

            doc = reader.read(xmlFile);//用dom4j自带的reader读取去读返回一个Document

        } catch (Exception e) {

            e.printStackTrace();

        }
        return doc;
    }


}

这里面的 IoCEntityResolver 是自己实现的类。这个类是干嘛的呢?还是先介绍一下xml 配置文件的命名空间吧,了解了xml 文件的命名空间,就明白这个类是干什么的了。下面是Spring xml文件两种配置头。

// xsd 方式验证xml 文件
<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

命名空间相当于java中的包,xsd 方式中 xmlns 相当于一个保留字,用来申明一个命名空间。上面就申明了别名为 xsi、context、aoptx命名空间,别名后面的url包含了该命名空间的信息。申明了这些命名空间,就可以用该命名空间的元素了。比如上面申明了aop 命名空间,就可以 用<aop:config>来配置切面了。Spring 使用 xml配置 bean信息,在解析 xml文件时,首先就要验证 xml 文件的正确性。当 xml 文件使用了命名空间中不存在的元素或者使用方式有问题,xml 验证就会报错。那么怎么验证呢?通常一个命名空间的URL 对应着一个 xsd地址,如上面的 xsi:schemaLocation 所示,所以xsi:schemaLocatio 后面的 URL都是成对的。通过这个xsd 可以获取到 xsd 文件,xsd文件包含着该对应命名空间所有的使用规则。所以 xml验证怎么验证呢,就是通过这些 xsd 文件来验证其相当应命名空间元素使用正确性。


// dtd 方式验证 xml 文件
<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE beans PUBLIC "-//CRAZYIT//DTD BEAN//EN" "http://www.crazyit.org/beans.dtd">  
<beans>  

</beans>  

上面是 dtd 方式验证xml文件。申明了beans 命名空间,该命名空间后面包括两部分,分别是PUBLICIDSYSTEID。如上面所示,PUBLICID-//CRAZYIT//DTD BEAN//ENSYSTEMIDhttp://www.crazyit.org/beans.dtdSYSTEMIDURL 定义了dtd 文件的位置,跟 xsd 中的 schemaLocationURL 一样。这个 DTD 文件功能和 xsd 是一样的,都包含着该对应命名空间所有的使用规则,只是验证xml文件方式有差异而言。不管是 xsd方式还是dtd方式,为了保证本地没联网的时候能获取到xsddtd文件,Sping 在本地包就保存了各个版本的 xsddtd文件,当本地获取不到的时候,才通过URL联网获取。所以 IoCEntityResolver这个类干嘛呢,就是指定本地 xsd 或者 dtd文件的位置,让 验证 xml 的时候直接从本地读取。IoCEntityResolver 类的实现如下:

public class IoCEntityResolver implements EntityResolver {
   
    @Override
    public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
        if ("http://www.crazyit.org/beans.dtd".equals(systemId)){
            InputStream stream = IoCEntityResolver.class.getResourceAsStream("spring-beans-2.0.dtd");
            return new InputSource(stream);
        } else {
            return null;
        }
    }
}

这里采用了 dtd的验证方式。所以我的xml 配置文件如下。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//CRAZYIT//DTD BEAN//EN" "http://www.crazyit.org/beans.dtd">
<beans>

    <bean id="category" class="com.kdk.action.domain.Category">
        <constructor-arg>
        <value type="java.lang.Integer">552</value>
        </constructor-arg>
        <constructor-arg>
           <value type="java.lang.String">code</value>
        </constructor-arg>
    </bean>
    <bean id="book" class="com.kdk.action.domain.Book" autowire="byName">
        <constructor-arg>
            <value type="java.lang.String">kdk</value>
        </constructor-arg>
        <constructor-arg>
            <value type="java.lang.String">c study</value>
        </constructor-arg>
    </bean>

</beans>

SYSTEMIDhttp://www.crazyit.org/beans.dtd 时,就从本地获取 dtd 文件,我本地 dtd 文件就直接采用 spring提供的,并没有自定义。自此,就可以将我的xml 文件生成 Document 对象了。

加载 Element 元素

获取到 Document 对象后,接下来就可以加载 Document 中的Element元素了。定义加载 Element的接口。

public interface ElementLoader {
    //加载 Doucument 中所有的 Element 并缓存在本地
    void addElements(Document doc);

    //根据 Element 的 id 来获取 Element
    Element getElements(String id);

    //返回 Document 中所有的 Element
    Collection<Element> getElements();
}

按照接口的定义,依次实现接口的方法。

public class ElementLoaderImpl implements ElementLoader {
   
    private Map<String,Element> elements = new HashMap<>();

    @Override
    public void addElements(Document doc) {
        List<Element> eles = doc.getRootElement().elements();
        for (Element e : eles){
            String id = e.attributeValue("id");
            elements.put(id,e);
        }
    }

    @Override
    public Element getElements(String id) {
        return elements.get(id);
    }

    @Override
    public Collection<Element> getElements() {
        return elements.values();
    }
}

加载Element元素比较简单,利用 Document提供的方法就可以获取所有的根 Element,并将其缓存在 Map 中。

解析 Element

这个简单的IOC 容器,我只实现了对 bean Element 的部分属性的解析,但是了解这部分的解析,自然也就明白其它属性的解析原理了。 Element 解析接口如下:

public interface ElementReader {
    //是否懒加载
    boolean isLazy(Element element);

    //获得bean元素下面的constructor-arg
    List<Element> getConstructorElements(Element element);

    //获取属性为name的属性值
    String getAttribute(Element element,String name);

    boolean isSingleton(Element element);

    //获得一个bean元素下面的所有的property元素
    List<Element> getPropertyElements(Element element);

    //获得bean元
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值