全部代码在我已经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 = 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 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容器生成出来的对象)。
到此我们可以在主函数中测试一下我们的代码。
Map beanCofig= ConfigurationManager.getBeanConfig("/applicationContext.xml");
for (Map.Entry 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 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));
}
}
结果如下图: