题外话
重复造轮子并不是件不好的事,作为学习我觉得造几个轮子还是很有必要的,光看不练假把式,况且看完后容易忘掉,又要重新看,还不如动手写几行,一来加深印象,二来在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构建的