小弟我的IOC(一)
题外话
重复造轮子并不是件不好的事,作为学习我觉得造几个轮子还是很有必要的,光看不练假把式,况且看完后容易忘掉,又要重新看,还不如动手写几行,一来加深印象,二来在coding过程中还能学到不少相关的知识,比如在实现自己IOC的时候,除了深入理解其原理,还能了解诸如xml解析,编写dtd文档保证xml格式的有效性等等。最后写篇博文总结并梳理一下思绪,好了下面正式开始。
首先新建一个Person类
public class Person { private String name; private Integer age; //省略setter,getter方法 }
然后再建一个PersonService
public class PersonService { private Person person; public void info(){ System.out.println("My name's "+person.getName()+" , I'm "+person.getAge()+" years old!"); } public void setPerson(Person person) { this.person = person; } }
和Spring一样,这里采用setter方法实现依赖注入,使用XML文件来保存对象之间的依赖关系
配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="person" class="com.firefly.core.Person"> <property name="name" value="Jack"/> <property name="age" value="12"/> </bean> <bean id="personService" class="com.firefly.core.PersonService"> <property name="person" ref="person"/> </bean> </beans>
一开始只实现最最基础的部分, 即读取、解析配置文件,然后利用反射实现对象的依赖注入。
我们先不管怎么实现解析xml,也不管怎么依赖注入。首先回顾一下Spring当中配置好xml后怎么来使用,一般都是这样
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("firefly.xml"); PersonService personService = (PersonService)applicationContext.getBean("personService");
ok,那么我们也新建一个ApplicationContext接口,里面有个getBean方法,然后新建一个FileSystemXmlApplicationContext来实现这个接口
/** * IOC容器基本接口 * @author 杰然不同 * @date 2010-11-26 * @Description: 定义IOC容器基本规范 * @Version 1.0 */ public interface ApplicationContext { public Object getBean(String name); }
/** * 容器接口实现 * @author 杰然不同 * @date 2010-11-29 * @Version 1.0 */ public class FileSystemXmlApplicationContext extends AbstractApplicationContext{ public FileSystemXmlApplicationContext(String fileName) { super.reader = new XmlBeanDefinitionReader(fileName); // 启动容器初始化 refresh(); } }
细心的朋友会看到FileSystemXmlApplicationContext并没有直接实现ApplicationContext,而是继承了名为AbstractApplicationContext
的抽象类,我们先来看下这是个什么类
/** * IOC容器的具体实现 * @author 杰然不同 * @date 2010-12-5 * @Version 1.0 */ public abstract class AbstractApplicationContext implements ApplicationContext { protected Map<String,BeanDefinition> beansDefinitionMap = new HashMap<String, BeanDefinition>(); protected Map<String, Object> beansMap = new HashMap<String, Object>(); protected BeanDefinitionReader reader; /** * 创建Bean * @Date 2010-11-30 * @param beanName * @return 具体Bean实例 */ protected Object createBean(String beanName) { Object beanobj = this.beansMap.get(beanName); if(beanobj != null) return beanobj; Class<?> clazz = null; Object obj = null; BeanDefinition beanDefinitionan = (BeanDefinition)beansDefinitionMap.get(beanName); if(beanDefinitionan != null){ try { clazz = Class.forName(beanDefinitionan.getClassName()); obj = clazz.newInstance(); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }else{ } // 依赖注入 setProperties(obj, beanDefinitionan.getProperties()); beansMap.put(beanName, obj); return obj; } /** * 获取Bean */ public Object getBean(String name){ return createBean(name); } /** * 使用set方法注入值 */ private Object setProperties(Object obj, Map<String, Object> properties) { Class<?> clazz = obj.getClass(); try { Method[] methods = clazz.getMethods(); for(Entry<String, Object> entry : properties.entrySet()){ String key = entry.getKey(); Object value = entry.getValue(); for(Method m : methods){ // 取出所有set方法并且只有一个参数 String methodName = m.getName(); Class<?>[] argsType = m.getParameterTypes(); if(methodName.startsWith("set") && argsType.length == 1){ String tempName = methodName.substring(3, methodName.length()).toLowerCase(); if(tempName.equals(key)){ setFieldValue(argsType[0].getName(),(String)value,m,obj); } } } } } catch (Exception e) { e.printStackTrace(); } return obj; } private void setFieldValue(String className, String value, Method m, Object obj) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { if (className.equals("byte")) m.invoke(obj, Byte.parseByte(value)); else if (className.equals("short")) m.invoke(obj, Short.parseShort(value)); else if (className.equals("int")) m.invoke(obj, Integer.parseInt(value)); else if (className.equals("long")) m.invoke(obj, Long.parseLong(value)); else if (className.equals("float")) m.invoke(obj, Float.parseFloat(value)); else if (className.equals("double")) m.invoke(obj, Double.parseDouble(value)); else if (className.equals("boolean")) m.invoke(obj, Boolean.parseBoolean(value)); else if (className.equals("java.lang.Byte")) m.invoke(obj, new Byte(value)); else if (className.equals("java.lang.Short")) m.invoke(obj, new Short(value)); else if (className.equals("java.lang.Integer")) m.invoke(obj, new Integer(value)); else if (className.equals("java.lang.Long")) m.invoke(obj, new Long(value)); else if (className.equals("java.lang.Float")) m.invoke(obj, new Float(value)); else if (className.equals("java.lang.Double")) m.invoke(obj, new Double(value)); else if (className.equals("java.lang.String")) m.invoke(obj, new String(value)); else if (className.equals("java.lang.Boolean")) m.invoke(obj, new Boolean(value)); else m.invoke(obj, createBean(value)); } /** * IOC容器初始化入口 * @Date 2010-11-29 */ public void refresh(){ beansDefinitionMap = reader.loadBeanDefinitions(); } }
原来IOC的具体实现是在这个类中,之所以这么做也是为了能有更好的扩展性。在这里我有必要说一下springioc工作的流程。IOC容器需要进行初始化,粗略的说就是解析配置文件将bean信息缓存到一个HashMap中,在这个过程中有专门的读取器对资源进行定位、解析、注册,这也是解耦的一种体现。于是在AbstractApplicationContext 中声明一个读取器BeanDefinitionReader,然后在其子类指明是由哪种读取器来读取,
此处自然我们需要一个解析xml的读取器super.reader = new XmlBeanDefinitionReader(fileName);
在FileSystemXmlApplicationContext中只做两件事,一个是将配置文件名给读取器,配置文件放在classpath下面,
然后调用refresh()启动IOC的初始化
我们顺着refresh()看下去,读取器调用loadBeanDefinitions()方法开始处理配置文件,下面是读取器的相关内容
BeanDefinition用来表示单个Bean
public class BeanDefinition { // Bean的id private String id; // Bean的class private String className; // Bean的属性集合 private Map<String, Object> properties = new HashMap<String, Object>(); // 省略getter,setter方法 }
读取、解析配置文件
1.创建读取配置文件通用接口BeanDefinitionReader
/** * 加载配置文件接口 * @author 杰然不同 * @date 2010-11-28 * @Version 1.0 */ public interface BeanDefinitionReader { /** * 读取配置文件中的信息 * @Date 2010-11-29 * @param fileName 文件名 * @return map */ public abstract Map<String, Bean> loadBeanDefinitions(); }
2.创建XmlBeanDefinitionReader实现以上接口,看名字很容易知道是读取XML形式的配置文件,今后可以扩展其他形式,只需实现上面的接口即可。
public class XmlBeanDefinitionReader implements BeanDefinitionReader { private final String fileName; protected static Logger log = Logger.getLogger(XmlBeanDefinitionReader.class.getName()); public XmlBeanDefinitionReader(String fileName) { this.fileName = fileName; } /** * 读取配置文件,将Bean信息存入HashMap中 * @Date 2010-12-5 * @return */ @SuppressWarnings("unchecked") public Map<String, BeanDefinition> loadBeanDefinitions(){ Map<String, BeanDefinition> beanDefinitionsMap = new HashMap<String, BeanDefinition>(); Document doc = null; // 获得Xml文档对象 try { log.info("Get XML Document"); doc = readDocument(this.fileName); } catch (DocumentException e) { e.printStackTrace(); return null; } // 获得根节点 List<Element> beans = doc.getRootElement().elements("bean"); // 遍历所有跟节点 for(Element e : beans){ BeanDefinition beanDefinitionan = new BeanDefinition(); String id = e.attributeValue("id"); String className = e.attributeValue("class"); beanDefinitionan.setId(id); beanDefinitionan.setClassName(className); // 获得Bean中所有property List<Element> propertiesList = e.elements("property"); // 遍历所有property for(Element e1 : propertiesList){ String name = e1.attributeValue("name"); // 如果是普通赋值形式 if(e1.attribute("value") != null) beanDefinitionan.getProperties().put(name, e1.attributeValue("value")); // 如果是引用另一个Bean形式 if(e1.attribute("ref") != null) beanDefinitionan.getProperties().put(name, e1.attributeValue("ref")); } beanDefinitionsMap.put(id, beanDefinitionan); } return beanDefinitionsMap; } /** * 根据文件读取Document * @Date 2010-11-28 * @param filePath * @return 文档对象 * @throws DocumentException */ private Document readDocument(String filePath) throws DocumentException{ // 获得带上classpath路径的文件路径 filePath = Thread.currentThread().getContextClassLoader().getResource( filePath).getPath().substring(1); log.info("Loading XML bean definitions from file [" + filePath + "]"); //使用SAXReader来读取xml文件 SAXReader reader = new SAXReader(); Document doc = null; doc = reader.read(new File(filePath)); return doc; } }
读取器经过一系列处理后,将xml中所有的内容缓存到Map<String, BeanDefinition>中,到此为止IOC容器初始化完毕。Bean的依赖注入在第一次getBean的时候触发。
我们回到AbstractApplicationContext中看getBean方法,它直接调用了createBean新建Bean,顺着方法看下去,可以看到是利用了java反射机制来实例化Bean对象和它的属性对象,然后同样缓存到HashMap中。
到这里一个简单IOC完成了,我们写一个测试
public class ApplicationContextTest extends TestCase { @Test public void testGetBean(){ ApplicationContext applicationContext = new FileSystemXmlApplicationContext("firefly.xml"); PersonService personService = (PersonService)applicationContext.getBean("personService"); personService.info(); Person person = (Person)applicationContext.getBean("person"); System.out.println(person.getName()); } }
输出:
My name's Jack , I'm 12 years old!
Jack
下一节我们进行第一次重构!!!
ps:附件是重构前的源码,用maven构建的