我的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构建的

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值