java|撸一个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导入我们的依赖吧。

junit junit 4.11 test commons-beanutils commons-beanutils 1.9.3 commons-logging commons-logging 1.1.1 dom4j dom4j 1.6.1 jaxen jaxen 1.1-beta-6

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

对于上面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 properties = new ArrayList(); 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 getProperties() { return properties; }public void setProperties(List 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 = https://www.it610.com/article/value; }public String getRef() { return ref; }public void setRef(String ref) { this.ref = ref; }@Override public String toString() { return"Property [name=" + name + ", value="https://www.it610.com/article/+ 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 getBeanConfig(String path) { // 存放配置信息,返回结果 Map result = new HashMap(); // 创建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 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 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容器生成出来的对象)。
【java|撸一个springIoc容器】到此我们可以在主函数中测试一下我们的代码。
Map beanCofig= ConfigurationManager.getBeanConfig("/applicationContext.xml"); for (Map.Entry e:beanCofig.entrySet()){ System.out.println(e.getKey()+":"+e.getValue()); }

java|撸一个springIoc容器
文章图片

如果结果如图所示 就基本没错了。数据有了接下来就是通过造对象存入容器咯。
既然是模仿 我们就豆腐渣到底了,有基础的同学都知到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 config; // 存放bean对象的容器 private Map context = new HashMap<>(); public ClassPathXmlApplicationContext(String path) { // 读取配置文件中bean的信息 config = ConfigurationManager.getBeanConfig(path); // 遍历初始化bean if (config != null) { for (Map.Entry 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 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 beanCofig= ConfigurationManager.getBeanConfig("/applicationContext.xml"); //for (Map.Entry 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)); } }

结果如下图:
java|撸一个springIoc容器
文章图片


    推荐阅读