参考:Tiny Spring 分析一,原文:1000行代码读懂Spring(一)- 实现一个基本的IoC容器
代码地址:https://github.com/xiaoxicode/jdk_source_1.7/tree/master/tinyspring
特此声明,本文不能算严格意义上的原创,只能算是黄文章的再次解读吧。
开工
如下的代码不需要解释了吧。
step1
- package com.myspring.factory;
-
-
- public interface BeanFactory {
- Object getBean(String name) throws Exception;
- }
- package com.myspring.bean;
-
-
- /**
- * bean的内容及元数据,保存在BeanFactory中,包装bean的实体
- */
- public class BeanDefinition {
-
- private Object bean;
- private Class<?> beanClass;
- private String beanClassName;
- public BeanDefinition() {
- }
- public BeanDefinition(Object object){
- this.bean=object;
- }
-
- public void setBeanClassName(String beanClassName) {
- this.beanClassName = beanClassName;
- try {
- this.beanClass = Class.forName(beanClassName);
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
- //省略部分get/set方法
- }
- package com.myspring.factory;
-
- import java.util.ArrayList;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Map;
- import java.util.concurrent.ConcurrentHashMap;
-
- import com.myspring.bean.BeanDefinition;
-
- public class AbstractBeanFactory implements BeanFactory {
-
-
-
-
- private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
-
- private final List<String> beanDefinitionNames = new ArrayList<String>();
-
-
- @Override
- public Object getBean(String name) throws Exception {
- BeanDefinition beanDefinition = beanDefinitionMap.get(name);
- if (beanDefinition == null) {
- throw new IllegalArgumentException("No bean named " + name + " is defined");
- }
- Object bean = beanDefinition.getBean();
- return bean;
- }
-
-
-
-
-
- public void registerBeanDefinition(String name, BeanDefinition beanDefinition) throws Exception {
- beanDefinitionMap.put(name, beanDefinition);
- beanDefinitionNames.add(name);
- }
- }
来测试一下,以HelloWorldServiceImpl为例
- package com.myspring;
-
-
- public class HelloWorldServiceImpl {
-
- public void helloWorld2() {
- System.out.println("hello");
- }
- }
测试代码如下
- public void Step1() throws Exception {
- BeanFactory beanFactory=new AbstractBeanFactory();
- BeanDefinition beanDefinition=new BeanDefinition(new HelloWorldServiceImpl());
- ((AbstractBeanFactory)beanFactory).registerBeanDefinition("helloworld",beanDefinition);
-
- HelloWorldServiceImpl h=(HelloWorldServiceImpl) beanFactory.getBean("helloworld");
- h.helloWorld2();
- }
很简单,打印出了hello;
ok 至此 我们最简单的IoC就搭建完成了。
现在我们就一步一步地完善我们的容器
step2
第一步的时候,beanDefinition里面我们直接放入了bean,向下面这个,我们放入classname会如何?
- @Test
- public void Step2() throws Exception {
- BeanFactory beanFactory=new AbstractBeanFactory();
- BeanDefinition beanDefinition=new BeanDefinition();
- beanDefinition.setBeanClassName("com.myspring.HelloWorldServiceImpl");
- ((AbstractBeanFactory)beanFactory).registerBeanDefinition("helloworld",beanDefinition);
-
- HelloWorldServiceImpl h=(HelloWorldServiceImpl) beanFactory.getBean("helloworld");
- h.helloWorld2();
- }
解决办法很简单,在AbstractBeanFactory/getBean()方法返还bean之前加上如下代码即可。
if (bean==null) {
bean=beanDefinition.getBeanClass().newInstance();
}
step3
继续走,如果我们在HelloWorldServiceImpl里面有简单的参数怎么办,示意代码如下
private String text;
private int a;
public void helloWorld(){
System.out.println(text+a+" ss");
}
既然有参数,那我们就设计一个PropertyValue
- package com.myspring.bean;
-
-
-
-
-
- public class PropertyValue {
- private final String name;
- private final Object value;
- }
下来就是在BeanDefinition里面增加一个List<PropertyValue> pvs=new ArrayList<PropertyValue>,毕竟不能限制一个类只有一个属性吧。
一个类中不会只有一个参数,那必然就是List了。好像说的有道理,目前我们是人为地给pvs里面加数据,用add()方法,如果一个类中,有重复的属性呢?
开玩笑,java里能出现两个变量同名吗?
当然java类里是不存在的,可我们得知道成型的spring可是从xml里面读取数据的
如果我写成这样 怎么办?
- <bean id="userService" class="com.bjsxt.services.UserService" >
- <property name="userDao" bean="u" />
- <property name="userDao" bean="u" />
- </bean>
因此我们得再加入一个类PropertyValues
- package com.myspring.bean;
-
- import java.util.ArrayList;
- import java.util.List;
-
-
-
-
-
- public class PropertyValues {
-
-
- private final List<PropertyValue> propertyValueList = new ArrayList<PropertyValue>();
- public PropertyValues() {
- }
-
- public void addPropertyValue(PropertyValue pv) {
-
-
- this.propertyValueList.add(pv);
- }
- public List<PropertyValue> getPropertyValues() {
- return this.propertyValueList;
- }
- }
因而在BeanDefinition里加入private PropertyValues propertyValues;即可
相应的getBean方法也要变
- @Override
- public Object getBean(String name) throws Exception {
- BeanDefinition beanDefinition = beanDefinitionMap.get(name);
- if (beanDefinition == null) {
- throw new IllegalArgumentException("No bean named " + name
- + " is defined");
- }
- Object bean = beanDefinition.getBean();
- if (bean == null) {
- bean = beanDefinition.getBeanClass().newInstance();
- }
- creatBean(bean, beanDefinition);
-
-
- return bean;
- }
大家看到了关键问题在creatBean方法上
- public void creatBean(Object bean, BeanDefinition beanDefinition)
- throws Exception {
- if (beanDefinition.getPropertyValues() != null)
- creatBeanWithProperty(bean, beanDefinition);
- }
-
- public void creatBeanWithProperty(Object bean, BeanDefinition beanDefinition) throws Exception{
-
-
- int size =beanDefinition.getPropertyValues().getPropertyValues().size();
- List<PropertyValue> list = beanDefinition.getPropertyValues().getPropertyValues();
- for (int i = 0; i <size ; i++) {
-
-
-
- if(list.get(i).getValue() instanceof BeanReference){
- String beanName=((BeanReference)list.get(i).getValue()).getName();
-
- Object referenceBean=getBean(beanName);
- String ms="set"+Character.toUpperCase(list.get(i).getName().charAt(0))+list.get(i).getName().substring(1);
-
- Method m=bean.getClass().getDeclaredMethod(ms, referenceBean.getClass());
- m.invoke(bean, referenceBean);
- }
- else {
- String fieldName = list.get(i).getName();
- Object value = list.get(i).getValue();
- Field field = bean.getClass().getDeclaredField(fieldName);
- field.setAccessible(true);
- field.set(bean, value);
- field.setAccessible(false);
- }
- }
-
- }
ok,看看测试代码
- public void Step3() throws Exception {
-
- BeanFactory beanFactory = new AbstractBeanFactory();
-
-
-
- BeanDefinition beanDefinition = new BeanDefinition();
- beanDefinition.setBeanClassName("com.myspring.HelloWorldServiceImpl");
-
-
-
- PropertyValues propertyValues = new PropertyValues();
- propertyValues.addPropertyValue(new PropertyValue("text","Hello World!"));
- propertyValues.addPropertyValue(new PropertyValue("a",new Integer(15)));
- beanDefinition.setPropertyValues(propertyValues);
-
-
-
- ((AbstractBeanFactory)beanFactory).registerBeanDefinition("helloworld", beanDefinition);
-
-
- HelloWorldServiceImpl h = (HelloWorldServiceImpl) beanFactory
- .getBean("helloworld");
- h.helloWorld();
- }
测试结果
Hello World!15 ss
step4
上面的类里面的成员变量依然是int,double等的基本变量(String 在这里也算基本变量),如果我在类里面加一个引用变量呢?
如下
private OutputService out;
public void helloWorld3(){
out.output(text);
}
OutputService的output方法很简单就是输出text的内容。
那么下来,理所应当的我们会设计出一个参考类
- package com.myspring;
-
- public class BeanReference {
- private String name;
- private Object bean;
- }
对于BeanReference,我们可以按下面的方式使用
- public void Step4() throws Exception {
-
- BeanFactory beanFactory = new AbstractBeanFactory();
-
-
-
- BeanDefinition beanDefinition = new BeanDefinition();
- beanDefinition.setBeanClassName("com.myspring.HelloWorldServiceImpl");
-
- BeanDefinition beanDefinition2 = new BeanDefinition();
- beanDefinition2.setBeanClassName("com.myspring.OutputService");
-
- BeanReference beanReference=new BeanReference("outPutService");
- beanReference.setBean(beanDefinition2);
-
-
-
- PropertyValues propertyValues = new PropertyValues();
- propertyValues.addPropertyValue(new PropertyValue("text","Hello World! with referencebean"));
- propertyValues.addPropertyValue(new PropertyValue("a",new Integer(15)));
- propertyValues.addPropertyValue(new PropertyValue("out",beanReference));
- beanDefinition.setPropertyValues(propertyValues);
-
-
-
- ((AbstractBeanFactory)beanFactory).registerBeanDefinition("helloworld", beanDefinition);
- ((AbstractBeanFactory)beanFactory).registerBeanDefinition("out", beanDefinition2);
-
-
- HelloWorldServiceImpl h = (HelloWorldServiceImpl) beanFactory
- .getBean("helloworld");
- h.helloWorld3();
- }
看到第四步注册bean的时候,大家应该想到如果有n个bean,我就得调用registerBeanDefinition方法n次吗?
目前就只能是这个方法了,技术用for循环,beanDefinition的名字也没办法,现在毕竟是模拟,各个变量的名字都是由人输入的,以后会从xml中读,就简单多了。
下面的麻烦的代码大家应该也能猜处理,就是creatBean部分。
- int size =beanDefinition.getPropertyValues().getPropertyValues().size();
- for (int i = 0; i <size ; i++) {
- List<PropertyValue> list = beanDefinition.getPropertyValues().getPropertyValues();
- //到底是不是引用类型 得区别开
- //不区别行不行?
- if(list.get(i).getValue() instanceof BeanReference){
- String beanName=list.get(i).getName();
- Object referenceBean=getBean(beanName); //循环调用getBean
- String ms="set"+Character.toUpperCase(beanName.charAt(0))+beanName.substring(1);
-
-
- Method m=bean.getClass().getDeclaredMethod(ms, referenceBean.getClass());
- m.invoke(bean, referenceBean);
-
- }
- else {
- String fieldName = list.get(i).getName();
- Object value = list.get(i).getValue();
- Field field = bean.getClass().getDeclaredField(fieldName); // getDeclaredField是获得所有的字段(不仅仅是public)
- field.setAccessible(true); // 这一步必须有
- field.set(bean, value);
- field.setAccessible(false); // 这一步必须有
- }
-
- }
还是上面的问题,如果不区分是引用类型还是基本类型可以不?
property里面的value是object类型的,如果我们给里面放的是int,直接set到bean里面,可是这个object要是BeanReference呢,还得取出BeanReference里面的value,然后在循环getbean()。你们说不区分能行吗?
测试结果
Hello World! with referencebean
step5
看完了前面的几步,到现在我们必然要想到的问题就是,数据要是放在xml中怎么读?
其实按照正常思维一步一步来,从xml中读数据和之前手工配进去并没有什么大的区别,只要读出来就OK了。
先看测试程序,
- public void Step5() throws Exception {
-
- XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader();
- xmlBeanDefinitionReader.loadBeanDefinitions("bin/resources/tinyioc.xml");
-
-
- BeanFactory beanFactory = new AbstractBeanFactory();
- for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getBeanDefinitionMap().entrySet()) {
- ((AbstractBeanFactory)beanFactory).registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
- }
-
-
- HelloWorldServiceImpl helloWorldService = (HelloWorldServiceImpl) beanFactory.getBean("helloWorldService");
- helloWorldService.helloWorld3();
- }
关于路径问题
在java中,获取文件(包括xml,jpg等等)有两种方式
Class类下的getResource(String path)方法与
ClassLoader类下的getResource(String path)方法
先说后面一种,ClassLoader.getResource()参数中不带"/",默认就是根路径(在Eclipse中,跟路径就是工程下的bin文件夹,在默认情况下,eclipse会把项目中的src下的内容拷贝到bin下,因此也可以理解为根目录就是src目录)
第一种Class.getResource()可以带"/"也可以不带
一旦带了/ 就默认从根路径(就是bin 就是src)下查找了
如果没有/ 就从这个类本身的那个路径下查找
详细资料见
http://www.cnblogs.com/yejg1212/p/3270152.html
在step5这个例子里
InputStream is = new FileInputStream(local);这里的local是从项目目录算的,因此还得加上bin
看了测试代码大家就会知道,我们的程序结构了吧,XmlBeanDefinitionReader的主要作用就是读取xml,然后转换成一个个BeanDefinition,再存储进BeanDefinitionMap,再创建一个BeanFactory,将BeanDefinitionMap中的记录一个一个再注册一边。
- public class XmlBeanDefinitionReader {
-
-
- private Map<String, BeanDefinition> beanDefinitionMap;
-
- public XmlBeanDefinitionReader(){
- beanDefinitionMap = new HashMap<String, BeanDefinition>();
- }
-
- public void loadBeanDefinitions(String local) throws IOException, ParserConfigurationException, SAXException {
- InputStream is = new FileInputStream(local);
- parseNode(is);
- }
看了loadBeanDefinitions,很简单吧,就是建一个InputStream,连接到文件上,然后从文件中读数据。
这里面的东西不难,但是比较繁杂,牵扯最多的就是对xml的解析
相关知识见
http://blog.csdn.net/dlf123321/article/details/39649089
-
-
-
-
-
-
-
- public void parseNode(InputStream is) throws ParserConfigurationException, SAXException, IOException {
- DocumentBuilderFactory domfac = DocumentBuilderFactory.newInstance();
- DocumentBuilder domBuilder = domfac.newDocumentBuilder();
-
-
-
-
- Document doc = domBuilder.parse(is);
- Element root = doc.getDocumentElement();
- NodeList beans = root.getChildNodes();
- for (int i = 0; i < beans.getLength(); i++)
- if (beans.item(i) instanceof Element) {
- Element el=(Element)beans.item(i);
- parseElement(el);
- }
- is.close();
-
- }
-
-
-
-
-
- public void parseElement(Element el){
- String id=el.getAttribute("id");
- String classPath=el.getAttribute("class");
-
- BeanDefinition bd=new BeanDefinition();
- bd.setBeanClassName(classPath);
- parseProperties(el,bd);
- beanDefinitionMap.put(id, bd);
- }
-
-
-
-
-
-
- public void parseProperties(Element el,BeanDefinition bd){
- NodeList bl=el.getElementsByTagName("property");
- for (int i = 0; i < bl.getLength(); i++)
- if (bl.item(i) instanceof Element) {
- Element property=(Element)bl.item(i);
- String name=property.getAttribute("name");
-
- if (property.getAttribute("ref")!="") {
- BeanReference br=new BeanReference(property.getAttribute("ref"));
- PropertyValue pV=new PropertyValue(name,br);
- bd.getPropertyValues().addPropertyValue(pV);
-
- }
- if (property.getAttribute("value")!="") {
- String value=property.getAttribute("value");
- PropertyValue pV=new PropertyValue(name, value);
- bd.getPropertyValues().addPropertyValue(pV);
-
- }
- }
-
- }
-
- public Map<String, BeanDefinition> getBeanDefinitionMap() {
- return beanDefinitionMap;
- }
再剩下的代码,参考step4就ok
step6
如果仔细,比对XmlBeanDefinitionReader与AbstractBeanFactory,就能发现两个类里面都有beanDefinitionMap,重写两边,不合适。
另外在step5中
-
- BeanFactory beanFactory = new AbstractBeanFactory();
- for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getBeanDefinitionMap().entrySet()) {
- ((AbstractBeanFactory)beanFactory).registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
- }
按照使用者与创建者分离的原则,初始化注册的代码出现在客户端也不合适;
怎么办?
合起来呗。
还是先写测试代码 如下
- <pre name="code" class="java">public void Step7() throws Exception {
- ApplicationContext ac=new ApplicationContext("bin/resources/tinyioc.xml");
- HelloWorldServiceImpl helloWorldService = (HelloWorldServiceImpl) ac.getBean("helloWorldService");
- helloWorldService.helloWorld3();
- }
漂亮!关键就是ApplicationContext,上面已经说了,要把XmlBeanDefinitionReader与AbstractBeanFactory合起来,也就是说要把getBean与loadBeanDefinitions装到一个类里面去
- package com.myspring.context;
-
- import java.util.Map;
-
- import com.bjsxt.spring.BeanFactory;
- import com.myspring.beans.BeanDefinition;
- import com.myspring.beans.factory.AbstractBeanFactory;
- import com.myspring.beans.xml.XmlBeanDefinitionReader;
-
- public class ApplicationContext implements BeanFactory {
-
- private AbstractBeanFactory abf=new AbstractBeanFactory();
-
- public ApplicationContext(String local) throws Exception {
-
- loadBeanDefinitions(abf,local);
- }
-
- protected void loadBeanDefinitions(AbstractBeanFactory beanFactory,String configLocation) throws Exception {
- XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader();
- xmlBeanDefinitionReader.loadBeanDefinitions(configLocation);
- for (Map.Entry<String, BeanDefinition> beanDefinitionEntry :
- xmlBeanDefinitionReader.getBeanDefinitionMap().entrySet()) {
- beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
- }
- }
- @Override
- public Object getBean(String id) {
-
- Object obj=null;
- try {
- obj = abf.getBean(id);
- } catch (Exception e) {
-
- e.printStackTrace();
- }
- return obj;
- }
- }
AbstractBeanFactory与ApplicationContext都继承了BeanFactory,然后我们不直接用BeanFactory,而是让ApplicationContext中装一个AbstractBeanFactory,这不就是最简单的代理模式么?
代码到这里,就算是完成了黄亿华大神的TinySpring中IoC的大部分代码
之所以说是大部分,也就是说现在大家看到的代码还有一些部分与TinySpring不同
主要在这几个方面
1 Resources部分
在我完成的代码里,读取xml的时候直接就是一个InputStream,TinySpring的方式是有一个Resource类,同时还有一个LoadResource类用来加载资源,当然实现的内部机理都是inputstream;
2 接口问题
我一直认为,良好的代码是一次一次重构出来的,依我现在的水平,确实不能够很清晰地说出,分了那么多层接口,抽象类的实际作用,因此在我的代码里各个部分都很"薄弱"(只有一层)
3 对于类的加载,有两种方式一种直接加载,一种延迟加载,TinySpring最开始的那几个step还是在getBean的时候才newInstance的,但是到后面
- protected void onRefresh() throws Exception{
- beanFactory.preInstantiateSingletons();
- }
-
-
- public void preInstantiateSingletons() throws Exception {
- for (Iterator<String> it = this.beanDefinitionNames.iterator(); it.hasNext();) {
- String beanName = (String) it.next();
- getBean(beanName);
- }
- }
所以的类都直接加载了;
4 单例模式
TinySpring中一个bean默认只会加载一次,第二次getBean()的时候会取出之前已经creat的那个;
- public Object getBean(String name) throws Exception {
- BeanDefinition beanDefinition = beanDefinitionMap.get(name);
- if (beanDefinition == null) {
- throw new IllegalArgumentException("No bean named " + name + " is defined");
- }
- Object bean = beanDefinition.getBean();
- if (bean == null) {
- bean = doCreateBean(beanDefinition);
- bean = initializeBean(bean, name);
- beanDefinition.setBean(bean);
- }
- return bean;
- }
-
-
- protected Object doCreateBean(BeanDefinition beanDefinition) throws Exception {
- Object bean = createBeanInstance(beanDefinition);
- beanDefinition.setBean(bean);
- applyPropertyValues(bean, beanDefinition);
- return bean;
- }
我写的代码中,没有上面的步骤,因此即使第二次get一个已经get过得bean,仍然会产生一个新的bena!
我写的代码 下载地址
http://download.csdn.net/detail/dlf123321/7992633
参考资料
http://www.cnblogs.com/yejg1212/p/3270152.html
http://blog.csdn.net/dlf123321/article/details/39649089
所有评论(0)