先来试一个最简单的例子
package gordon.study.spring.sample;
public class Employee {
private String name;
private String address;
private Long badgeNumber;
// getters and setters
public void welcome() {
System.out.printf("Welcome to our company, %s!", getName());
}
}
package gordon.study.spring.sample;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Sample001 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("sample1.xml");
Employee gordon = (Employee) context.getBean("gordon");
gordon.welcome();
}
}
------------------------------sample1.xml--------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<bean id="gordon" class="gordon.study.spring.sample.Employee">
<property name="name" value="Gordon" />
</bean>
</beans>
============================================================
例子太简单了以至于我不想对此写任何注释,让我来思考一下如何自己来实现它吧。
我们知道Spring Ioc的核心功能就是提供一个Ioc容器,该容器通过配置文件管理bean的生命周期,包括初始化一个bean和注入依赖关系。main函数无外乎就是通过读取一个xml文件获得bean的配置信息,生成一个容器(ApplicationContext),然后通过getBean方法让容器提供一个装配好的bean给自己使用。
毫无疑问,容器对Bean的创建和依赖的注入显然是通过反射实现的。而对于配置信息,我们将xml中的bean标签抽象成一个BeanDefinition类,BeanDefinition至少需要有id、clazz属性和一个property列表,property可以抽象为BeanProperty类。所有的BeanDefinition,可以通过一个map来管理。
读取xml文件选用dom4j 的 DOM 方式,这个最简单。
一个隐藏细节是:目前的scope是singleton的。也就是说无论我们调用多少次 getBean("gordon"),返回的都是同一个对象。Spring中默认就是 singleton
我们将代码移入一个新项目 mySpring,删除所有的对spring包的导入,然后开始自己实现。
============================================================
代码实现也非常简单,纯粹是用来热手的,就不复制过来了,可以去 https://github.com/klg0705/mySpring 获取,tag为 001-lw
唯一碰到的麻烦是如何读取classpath路径上的文件,暂时用以下方法获得文件路径:
URL url = ClassLoader.getSystemResource(fileName);
File f = new File(url.getFile());
另一件事情是如何匹配 setter 方法的参数类型
public class ClassPathXmlApplicationContext implements ApplicationContext {
Map<String, BeanDefinition> beanDefinitions;
Map<String, Object> beans;
public ClassPathXmlApplicationContext(String fileName) throws Exception {
beanDefinitions = XmlConfigReader.readXmlConfig(fileName);
beans = new HashMap<String, Object>();
}
@Override
public Object getBean(String beanName) throws Exception {
if (beans.containsKey("beanName")) {
return beans.get(beanName);
}
Object bean = createBean(beanName);
beans.put(beanName, bean);
return bean;
}
private Object createBean(String beanName) throws Exception {
if (!beanDefinitions.containsKey(beanName)) {
throw new Exception("Bean " + beanName + " cannot be found.");
}
BeanDefinition beanDef = beanDefinitions.get(beanName);
Object obj = Class.forName(beanDef.getClazz()).newInstance();
for (BeanProperty prop : beanDef.getProperties()) {
getSetterMethod(obj, prop.getPropertyName()).invoke(obj, prop.getPropertyValue());
}
return obj;
}
private Method getSetterMethod(Object obj, String property) throws Exception {
String setter = getSetterString(property);
Method method = obj.getClass().getMethod(setter, String.class);
return method;
}
private String getSetterString(String property) {
StringBuilder sb = new StringBuilder("set");
sb.append(Character.toUpperCase(property.charAt(0))).append(
property.substring(1));
return sb.toString();
}
}
当前的实现中,只去找参数类型为 Stirng 的setter 方法(见getSetterMethod),显然,这只能保证目前的用例可以执行,其本身逻辑是错误的。这个问题将在下一篇解决。
<bug 14行代码无力吐槽了>