撸一个springIoc容器

全部代码在我已经push到github上了 ,地址:https://github.com/Awakeyoyoyo/lqhaoSpringIoc.git

觉得好的给个星星吧各位T_T。。。

SpringIoc的概念网上一大把,我在这里也就不细细说了 直接贴一个毕竟全面的博客https://www.jianshu.com/p/1af66a499f49

对springioc暂时一知半解的同学可以先去看一下 我贴的博客。

然后代码原帖是这里:https://blog.csdn.net/TimHeath/article/details/69663495,我只是更为详细的解析一下原博主的意图。

这个springioc容器只实现了配置文件注入!!!!!!

好我们现在就开始直入主题了。

先简单来说一下思路:

读取配置文件(我这里用的是xml文件),然后使用反射的API,基于类名实例化对应的对象实例,然后将其存入工厂之中,最后直接使用工厂对象getbean获取我们里面的对象。

了解完思路是不是觉得很简单?动手开撸吧!

由于本人本人懒得到处找jar包,所以没像原博主那样导入jar包,而是直接新建了一个maven项目,新建maven的时候选的是quickstart,显而易见是一个快速开始的java helloworld代码。 然后就开始编写pom.xml导入我们的依赖吧。

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--注入属性的时候要用到,一个内省工具包-->
    <dependency>
      <groupId>commons-beanutils</groupId>
      <artifactId>commons-beanutils</artifactId>
      <version>1.9.3</version>
    </dependency>
    <!--beanutils依赖的jar包-->
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.1.1</version>
    </dependency>
    <!--读取配置文件时用到,解析XML的-->
    <dependency>
      <groupId>dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>1.6.1</version>
    </dependency>
    <!--配合dom4j,令其支持XPath(XML路径语言)-->
    <dependency>
      <groupId>jaxen</groupId>
      <artifactId>jaxen</artifactId>
      <version>1.1-beta-6</version>
    </dependency>
  </dependencies>

然后下一步,仿照我们的spring用标签注入对象的格式编写我们的xml文件,如下图,噢对了用maven新建项目记得新建一个sources文件夹。然后把我们的xml文件放进去,我们的xml文件,为了更像spring就写成applicationContext.xml吧。

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean name="A" class="com.awakeyoyoyo.bean.A">
        <property name="name" value="Jason"></property>
        <property name="b" ref="B"></property>
    </bean>

    <bean name="B" class="com.awakeyoyoyo.bean.B" scope="prototype">
        <property name="age" value="13"></property>
    </bean>
</beans>

对于上面xml文件 不用多少解释了 就是一个A对象里面有一个B对象,A依赖于B才完整。

接着,第一步说到我们要读取xml中的信息存起来,看着xml文件我们可以大致想到新建一个bean类 一个property类来存储我们的信息为后面做准备。

package com.awakeyoyoyo.config;

import java.util.ArrayList;
import java.util.List;

public class Bean {

    public static final String SINGLETON = "singleton";
    public static final String PROTOTYPE = "prototype";
    private String name;
    private String className;
    // 默认创建的bean对象设置成是单例的
    private String scope = SINGLETON;
    private List<Property> properties = new ArrayList<Property>();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public List<Property> getProperties() {
        return properties;
    }

    public void setProperties(List<Property> properties) {
        this.properties = properties;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    @Override
    public String toString() {
        return "Bean [name=" + name + ", className=" + className + ", scope=" + scope + ", properties=" + properties
                + "]";
    }

}
package com.awakeyoyoyo.config;

public class Property {
    private String name;
    private String value;
    private String ref;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public String getRef() {
        return ref;
    }

    public void setRef(String ref) {
        this.ref = ref;
    }

    @Override
    public String toString() {
        return "Property [name=" + name + ", value=" + value + ", ref=" + ref + "]";
    }

}

看出来bean,property里的属性对应着我们的标签。好的下面就到了我们的读取环节了

package com.awakeyoyoyo.config.parsing;

import com.awakeyoyoyo.config.Bean;
import com.awakeyoyoyo.config.Property;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ConfigurationManager {
    /**
     * 根据指定的路径读取配置文件
     *
     * @param path
     *            配置文件路径
     * @return
     */
    public static Map<String, Bean> getBeanConfig(String path) {
        // 存放配置信息,返回结果
        Map<String, Bean> result = new HashMap<String, Bean>();
        // 创建xml文件解析器
        SAXReader reader = new SAXReader();
        // 加载配置文件
        InputStream is = ConfigurationManager.class.getResourceAsStream(path);
        Document doc = null;
        try {
            doc = reader.read(is);
        } catch (DocumentException e) {
            e.printStackTrace();
            throw new RuntimeException("加载配置文件出错");
        }
        // XPath语句,选取所有 bean元素
        String xpath = "//bean";
        List<Element> beanNodes = doc.selectNodes(xpath);
        // 遍历所有bean节点,并将信息封装到Bean对象中
        for (Element ele : beanNodes) {
            Bean bean = new Bean();
            bean.setName(ele.attributeValue("name"));
            bean.setClassName(ele.attributeValue("class"));
            String scope = ele.attributeValue("scope");
            // 如果指定了scope则设置,不然用默认的singleton
            if (scope != null && scope.trim().length() > 0) {
                bean.setScope(scope);
            }
            // 获取bean节点下所有的property节点
            List<Element> propNodes = ele.elements("property");
            if (propNodes != null) {
                // 遍历property节点,并封装到Property对象中,再添加到所属Bean对象中
                for (Element prop : propNodes) {
                    Property p = new Property();
                    p.setName(prop.attributeValue("name"));
                    p.setValue(prop.attributeValue("value"));
                    p.setRef(prop.attributeValue("ref"));
                    // 将property添加到所属bean中
                    bean.getProperties().add(p);
                }
            }
            result.put(bean.getName(), bean);
        }
        return result;
    }

}

我它的作用呢其实就是读取xml中的bean信息,然后生成一个个bean对象(这里的bean对象只是存储着信息的对象,不是真正意义springioc容器生成出来的对象)。

到此我们可以在主函数中测试一下我们的代码。

 Map<String,Bean> beanCofig= ConfigurationManager.getBeanConfig("/applicationContext.xml");
        for (Map.Entry<String,Bean> e:beanCofig.entrySet()){
            System.out.println(e.getKey()+":"+e.getValue());
        }

如果结果如图所示 就基本没错了。数据有了接下来就是通过造对象存入容器咯。

既然是模仿 我们就豆腐渣到底了,有基础的同学都知到BeanFactory是spring中最原始的factory,我们这里自己也搞一个最原始的factory接口。

package com.awakeyoyoyo.core;

public interface BeanFactory {
    Object getBean(String name);
}

然后呢实现它好吧

package com.awakeyoyoyo.core;

import com.awakeyoyoyo.config.Bean;
import com.awakeyoyoyo.config.Property;
import com.awakeyoyoyo.config.parsing.ConfigurationManager;
import org.apache.commons.beanutils.BeanUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ClassPathXmlApplicationContext implements BeanFactory {
    // 存放配置文件信息
    private Map<String, Bean> config;
    // 存放bean对象的容器
    private Map<String, Object> context = new HashMap<>();


    public ClassPathXmlApplicationContext(String path) {
        // 读取配置文件中bean的信息
        config = ConfigurationManager.getBeanConfig(path);
        // 遍历初始化bean
        if (config != null) {
            for (Map.Entry<String, Bean> e : config.entrySet()) {
                // 获取bean信息
                String beanName = e.getKey();
                Bean bean = e.getValue();
                // 如果设置成单例的才创建好bean对象放进容器中
                if (bean.getScope().equals(Bean.SINGLETON)) {
                    Object beanObj = createBeanByConfig(bean);
                    context.put(beanName, beanObj);
                }
            }

        }
    }
    private Object createBeanByConfig(Bean bean) {
        // 根据bean信息创建对象
        Class clazz = null;
        Object beanObj = null;
        try {
            //以下用到一些反射知识
            clazz = Class.forName(bean.getClassName());

            // 创建bean对象
            beanObj = clazz.newInstance();
            // 获取bean对象中的property配置
            List<Property> properties = bean.getProperties();
            // 遍历bean对象中的property配置,并将对应的value或者ref注入到bean对象中
            for (Property prop : properties) {
                if (prop.getValue() != null) {
                    // 将value值注入到bean对象中  根据变量名字 注入值。
                    BeanUtils.setProperty(beanObj, prop.getName(),prop.getValue());
                } else if (prop.getRef() != null) {
                    Object ref = context.get(prop.getRef()); //直接容器里面 beanname 找实例对象
                    // 如果依赖对象还未被加载则递归创建依赖的对象
                    if (ref == null) {
                        ref = createBeanByConfig(config.get(prop.getRef())); //根据类名从bean信息里面根据beanname获取该bean信息 然后新建一个bean对象
                    }
                    // 将ref对象注入bean对象中
                    BeanUtils.setProperty(beanObj, prop.getName(),ref);
                }
            }
        } catch (Exception e1) {
            e1.printStackTrace();
            throw new RuntimeException("创建" + bean.getClassName() + "对象失败");
        }
        return beanObj;
    }

    @Override
    public Object getBean(String name) {
        Bean bean = config.get(name);
        Object beanObj = null;
        if (bean.getScope().equals(Bean.SINGLETON)) {
            // 如果将创建bean设置成单例则在容器中找
            beanObj = context.get(name);
        } else if (bean.getScope().equals(Bean.PROTOTYPE)) {
            // 如果是prototype则新创建一个对象
            beanObj = createBeanByConfig(bean);
        }
        return beanObj;
    }
}

这里我们来详细说一下为什么要这样设计。首先我们得先搞懂scope属性,我们在xml文件中没有设置A的scope吗,而在bean类中设置为SINGLETON,B呢就设置为prototype。就是可以有多个,相当于每次getbean(B)就新建一个。再看回我们实现的ClassPathXmlApplicationContext类,有两个属性config(用于存储文件信息,一个个bean信息对象),context(bean对象,就是我们的容器啦),再看createBeanByConfig方法 根据bean信息对象和里面的property集合来构建实例对象,这时看我们上面测试出来的数据,不难发现当property里面的value有值时,他不是引用变量直接setproperty即可,当ref有值的时候说明这个对象里面还包含了一个对象。所以遇到ref有值我们需要先从容器里找是否有(因为若是单例一开始容器初始化的时候就已经生成好了),若没有就再调用一createBeanByConfig方法新建对象。最后将其setproperty。最后最后返回对象。这就是我们的所有代码,是不是很简单。

最后跑一下我们的ioc容器吧

package com.awakeyoyoyo;

import com.awakeyoyoyo.bean.A;
import com.awakeyoyoyo.bean.B;
import com.awakeyoyoyo.config.Bean;
import com.awakeyoyoyo.config.parsing.ConfigurationManager;
import com.awakeyoyoyo.core.BeanFactory;
import com.awakeyoyoyo.core.ClassPathXmlApplicationContext;

import java.util.Map;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
//        Map<String,Bean> beanCofig= ConfigurationManager.getBeanConfig("/applicationContext.xml");
//        for (Map.Entry<String,Bean> e:beanCofig.entrySet()){
//            System.out.println(e.getKey()+":"+e.getValue());
//        }

        BeanFactory ac = new ClassPathXmlApplicationContext("/applicationContext.xml");//单例的会先注册进去
        A a = (A) ac.getBean("A");
        A a1 = (A) ac.getBean("A");
        B b = (B) ac.getBean("B");
        B b1 = (B) ac.getBean("B");
        System.out.println(a.getB());
        System.out.println("a==a1 : "+(a==a1));
        System.out.println("b==b1 : "+(b==b1));
  
    }
}

结果如下图:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值