近两个月一直被肠胃病折磨着,痛苦了好久,这段时间稍微好点了。身体不好,技术也就放下了。搞技术的朋友们啊,要保重好自己的身体啊,年轻并不代表可以挥霍健康。
好了,废话少说,今天,这几天尝试自己写了一点点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上的朋友共勉。