近两个月一直被肠胃病折磨着,痛苦了好久,这段时间稍微好点了。身体不好,技术也就放下了。搞技术的朋友们啊,要保重好自己的身体啊,年轻并不代表可以挥霍健康。
好了,废话少说,今天,这几天尝试自己写了一点点IOC的实现,当然只是很基本的属性注入的,对象的那些还没有去处理。但起码把自己一直以来想要深入理解一个思想的想法付诸行动了。
搞JAVA的朋友肯定都知道IOC是啥来的,如果不知道的,看看这个http://baike.baidu.com/view/146665.htm#sub6386770。简要的说一下就是把对象之间的关联交给容器来处理,而不是我们以前代码那样,直接来set或new一个。
知道了它是什么来的,我们肯定很希望知道它是怎么实现的,用过spring的朋友当然都知道。拿个xml文件,配置一下,然后加载,再getBean,一切Ok,啥都不用管,或者web中用contextLoaderListener或者DispatcherServlet都可以搞定,但停留在使用的层面上,显然不是我们想要的。
我们知道容器只是帮我们处理了一些依赖,如set,new等等,如此而已(听起来很简单,但有很多东西需要考虑的)。
下面我们就一起来看看,实现一个基本的可以注入属性的IOC容器,我们先不管注入依赖对象。
1)要注入属性,我们一般是通过调用setXXX方法,这里我们也是这样的(暂时不考虑构造函数注入),我们通过反射来调用相应的set方法。
我们这里的配置载体还是最流行的XML了,注解也很流行,但暂时还是不弄了。
我们有一个Bean来保存所有bean的配置属性等,就如同spring的BeanDefinitionHolder:
package com.shun.bean;
import java.util.List;
import java.util.Map;
public class BeanDefinition {
private String name;//保存bean的id
private String type;//保存class的值
private List<Map<String,Object>> properties;//保存property标签的相关配置
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public List<Map<String,Object>> getProperties() {
return properties;
}
public void setProperties(List<Map<String,Object>> properties) {
this.properties = properties;
}
}
当然,很简陋,只保存了相当于bean的id,class还有一系列property标签的配置。但已经足够我们来学习基本的属性注入了。
2)我们是通过反射来调用相应的类的属性的set方法,当然这时属性的首字母会变大写,如属性为name,则set方法是setName,我写了一个工具类来处理首字母大写:
package com.shun.utils;
public class DataUtils {
/**
* 将传入的字符串首字母大写
* @param str
* @return
*/
public static String capitalize(String str) {
return str.substring(0,1).toUpperCase()+str.substring(1);
}
}
3)然后最主要的当然是数我们的IOC处理类了MyIocProcessor:
首先我们来看看读取配置文件的方法:
/**
* 读取配置文件,并把配置文件中的相关配置保存到beanDefinitionList中
* @param configPath
* @return
*/
public List<BeanDefinition> processConfig(String configPath){
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
List<BeanDefinition> beanDefinitionList = new ArrayList<BeanDefinition>();
try {
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new File(configPath));
NodeList nodeList = doc.getElementsByTagName("bean");
//分析处理每一个bean标签
for (int i = 0; i < nodeList.getLength(); i ++) {
Node node = nodeList.item(i);
NamedNodeMap attributes = node.getAttributes();
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setName(attributes.getNamedItem("id").getNodeValue());//取得bean的id
beanDefinition.setType(attributes.getNamedItem("class").getNodeValue());//取得class
NodeList properties = node.getChildNodes();
List<Map<String,Object>> propertyMapList = new ArrayList<Map<String,Object>>();
//处理bean标签下的property标签
for (int j = 0; j < properties.getLength(); j ++) {
Node property = properties.item(j);
Map<String,Object> propertyMap = new HashMap<String,Object>();
if (property instanceof Element) {
NamedNodeMap propertyAttributes = property.getAttributes();
propertyMap.put("name",propertyAttributes.getNamedItem("name").getNodeValue());
propertyMap.put("value",propertyAttributes.getNamedItem("value").getNodeValue());
propertyMapList.add(propertyMap);
}
}
beanDefinition.setProperties(propertyMapList);
beanDefinitionList.add(beanDefinition);
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return beanDefinitionList;
}
这段代码只是读取xml配置文件,并把相应的配置属性放到beanDefinition中,这里我们都把name,value写死在代码里面,这没关系,因为我们只是一个例子,但当你真正想做一个框架级别的东西,绝对不要这样。
我们的配置文件类似下面的:
<beans> <bean id="person" class="com.shun.test.Person" > <property name="name" value="shun"/> <property name="age" value="25" /> </bean> <bean id="person2" class="com.shun.test.Person" > <property name="name" value="shun2" /> <property name="age" value="24" /> </bean> </beans>
我们就不搞那些什么schema和namespace了。
上面的代码应该不难理解,只是读取文件,然后解析bean标签,再接着解析property标签,保存name和value。
下面我们再来看看如何取得我们定义的bean,方法如下:
@SuppressWarnings({ "rawtypes", "unchecked" })
public Object getBean(String beanName){
Object obj = null;
//循环取得定义的bean TODO 这里考虑用map来实现会好点
for (BeanDefinition beanDefinition:beanDefinitionList) {
if (beanName.equals(beanDefinition.getName())) {
String typeName = beanDefinition.getType();
try {
//通过指定的class类型生成对象
Class bean = Class.forName(typeName);
obj = bean.newInstance();
List<Map<String,Object>> propertyMapList = beanDefinition.getProperties();
//这里通过反射调用相应属性的set方法
for (Map<String,Object> propertyMap:propertyMapList) {
Method[] methods = bean.getMethods();
for (Method method:methods){
//这里取得所有方法,判断相应的setXXX方法,然后取得参数类型再执行
if (("set"+DataUtils.capitalize(propertyMap.get("name").toString())).equals(method.getName())) {
//取得set方法参数类型
Class[] parameterTypes = method.getParameterTypes();
if (parameterTypes[0].isAssignableFrom(String.class)) {
method.invoke(obj, propertyMap.get("value"));
} else {
method.invoke(obj, Integer.valueOf(propertyMap.get("value").toString()));
}
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
return obj;
}
这段代码主要是用了反射,通过配置的class属性,生成相应的类的实例,然后再根据property配置的name调用相应的set方法,然后再返回对象。因为没涉及到对象依赖,所以代码不复杂。
当然,当我们真正实现的时候,需要判断boolean,float,double等等类型,还有集合List,Array,Map,Properties等。这些都是我们现在没考虑的。
这几个方法都在我们的MyIocProcessor类中,它的代码如下:
package com.shun.bean;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import com.shun.utils.DataUtils;
public class MyIocProcessor {
private List<BeanDefinition> beanDefinitionList;
public MyIocProcessor(){}
public MyIocProcessor(String configPath){
this.beanDefinitionList = processConfig(this.getClass().getResource("/").getPath()+configPath);
}
//上面的两个方法,这里省略了
}
这里处理的只是classpath里面的文件,根据传入的文件名,读取相应的xml文件,把读取的相应的bean信息放入到beanDefinitionList中,我们getBean的时候就根据它来取得。
我们例子中用到的bean只是一个简单的JAVABEAN:
package com.shun.test;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString(){
return "Name:"+name+",Age:"+age;
}
}
3)基本实现后,我们就来测试一下:
package com.shun.test;
import org.junit.Test;
import com.shun.bean.MyIocProcessor;
public class TestIoc {
@Test
public void test(){
MyIocProcessor myIoc = new MyIocProcessor("beans.xml");
Person person = (Person)myIoc.getBean("person2");
System.out.println(person);
}
}
运行后,我们可以看到:
我们可以修改,发现它都可以根据我们修改的值,打印出来,这证明我们的最基本的IOC是实现了,并且功能没问题。
依赖对象的注入,我们将会继续下来研究,如果大家有兴趣,可以自己研究。
最后再提醒大家,要注意身体,只有身体好,才谈得上赚钱。与广大javaeye上的朋友共勉。