我的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" value="Jack"/><property name="age" value="12"/></bean><bean id="personService" 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));elsem.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的idprivate String id;// Bean的classprivate 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中所有propertyList<Element> propertiesList = e.elements("property");// 遍历所有propertyfor(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 {@Testpublic 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构建的
?