在上一篇文章中,楼主和大家一起分析spring的 IOC 实现,剖析了Spring的源码,看的出来,源码异常复杂,这是因为Spring的设计者需要考虑到框架的扩展性,健壮性,性能等待元素,因此设计的很复杂。楼主在最后也说要实现一个简单的 IOC,让我们更加深刻的理解IOC,因此,有了这篇文章。
当然我们是仿照Spring 的 IOC,因此代码命名和设计基本是仿照spring的。
我们将分为几步来编写简易 IOC,首先设计组件,再设计接口,然后关注实现。
1. 设计组件。
我们还记得Spring中最重要的有哪些组件吗?BeanFactory
容器,BeanDefinition
Bean的基本数据结构,当然还需要加载Bean的资源加载器
。大概最后最重要的就是这几个组件。容器用来存放初始化好的Bean,BeanDefinition 就是Bean的基本数据结构,比如Bean的名称,Bean的属性 PropertyValue
,Bean的方法,是否延迟加载,依赖关系等。资源加载器就简单了,就是一个读取XML配置文件的类,读取每个标签并解析。
2. 设计接口
首先肯定需要一个BeanFactory,就是Bean容器,容器接口至少有2个最简单的方法,一个是获取Bean,一个注册Bean.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
public interface BeanFactory {
Object getBean(String name) throws Exception;
void registerBeanDefinition(String name, BeanDefinition bean) throws Exception; }
|
根据Bean的名字获取Bean对象,注册参数有2个,一个是Bean的名字,一个是 BeanDefinition 对象。
定义完了Bean最基本的容器,还需要一个最简单 BeanDefinition 接口,我们为了方便,但因为我们这个不必考虑扩展,因此可以直接设计为类,BeanDefinition 需要哪些元素和方法呢? 需要一个 Bean 对象,一个Class对象,一个ClassName字符串,还需要一个元素集合 PropertyValues。这些就能组成一个最基本的 BeanDefinition 类了。那么需要哪些方法呢?其实就是这些属性的get set 方法。 我们看看该类的详细:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| package cn.thinkinjava.myspring;
public class BeanDefinition {
private Object bean;
private Class beanClass;
private String ClassName;
private PropertyValues propertyValues = new PropertyValues();
public Object getBean() { return this.bean; }
public void setBean(Object bean) { this.bean = bean; }
public Class getBeanclass() { return this.beanClass; }
public void setClassname(String name) { this.ClassName = name; try { this.beanClass = Class.forName(name); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
public PropertyValues getPropertyValues() { return this.propertyValues; }
public void setPropertyValues(PropertyValues pv) { this.propertyValues = pv; }
}
|
有了基本的 BeanDefinition 数据结构,还需要一个从XML中读取并解析为 BeanDefinition 的操作类,首先我们定义一个 BeanDefinitionReader 接口,该接口只是一个标识,具体由抽象类去实现一个基本方法和定义一些基本属性,比如一个读取时需要存放的注册容器,还需要一个委托一个资源加载器 ResourceLoader, 用于加载XML文件,并且我们需要设置该构造器必须含有资源加载器,当然还有一些get set 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| package cn.thinkinjava.myspring;
import cn.thinkinjava.myspring.io.ResourceLoader; import java.util.HashMap; import java.util.Map;
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {
private Map<String, BeanDefinition> registry;
private ResourceLoader resourceLoader;
protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) { this.registry = new HashMap<>(); this.resourceLoader = resourceLoader; }
public Map<String, BeanDefinition> getRegistry() { return registry; }
public ResourceLoader getResourceLoader() { return resourceLoader; }
}
|
有了这几个抽象类和接口,我们基本能形成一个雏形,BeanDefinitionReader 用于从XML中读取配置文件,生成 BeanDefinition 实例,存放在 BeanFactory 容器中,初始化之后,就可以调用 getBean 方法获取初始化成功的Bean。形成一个完美的闭环。
3. 如何实现
刚刚我们说了具体的流程:从XML中读取配置文件, 解析成 BeanDefinition,最终放进容器。说白了就3步。那么我们就先来设计第一步。
1. 从XML中读取配置文件, 解析成 BeanDefinition
我们刚刚设计了一个读取BeanDefinition 的接口 BeanDefinitionReader 和一个实现它的抽象类 AbstractBeanDefinitionReader,抽象了定义了一些简单的方法,其中由一个委托类—–ResourceLoader, 我们还没有创建, 该类是资源加载器,根据给定的路径来加载资源。我们可以使用Java 默认的类库 java.net.URL 来实现,定义两个类,一个是包装了URL的类 ResourceUrl, 一个是依赖 ResourceUrl 的资源加载类。
ResourceUrl 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
public class ResourceUrl implements Resource {
private final URL url;
public ResourceUrl(URL url) { this.url = url; }
@Override public InputStream getInputstream() throws Exception { URLConnection urlConnection = url.openConnection(); urlConnection.connect(); return urlConnection.getInputStream();
}
}
|
ResourceLoader 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
public class ResourceUrl implements Resource {
private final URL url;
public ResourceUrl(URL url) { this.url = url; }
@Override public InputStream getInputstream() throws Exception { URLConnection urlConnection = url.openConnection(); urlConnection.connect(); return urlConnection.getInputStream(); } }
|
当然还需要一个接口,只定义了一个抽象方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package cn.thinkinjava.myspring.io;
import java.io.InputStream;
public interface Resource {
InputStream getInputstream() throws Exception; }
|
好了, AbstractBeanDefinitionReader 需要的元素已经有了, 但是,很明显该方法不能实现读取 BeanDefinition 的任务。那么我们需要一个类去继承抽象类,去实现具体的方法, 既然我们是XML 配置文件读取,那么我们就定义一个 XmlBeanDefinitionReader 继承 AbstractBeanDefinitionReader ,实现一些我们需要的方法, 比如读取XML 的readrXML, 比如将解析出来的元素注册到 registry 的 Map 中, 一些解析的细节。我们还是看代码吧。
XmlBeanDefinitionReader 实现读取配置文件并解析成Bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
| package cn.thinkinjava.myspring.xml;
import cn.thinkinjava.myspring.AbstractBeanDefinitionReader; import cn.thinkinjava.myspring.BeanDefinition; import cn.thinkinjava.myspring.BeanReference; import cn.thinkinjava.myspring.PropertyValue; import cn.thinkinjava.myspring.io.ResourceLoader; import java.io.InputStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList;
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
public XmlBeanDefinitionReader(ResourceLoader resourceLoader) { super(resourceLoader); }
public void readerXML(String location) throws Exception { ResourceLoader resourceloader = new ResourceLoader(); InputStream inputstream = resourceloader.getResource(location).getInputstream(); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = factory.newDocumentBuilder(); Document doc = docBuilder.parse(inputstream); registerBeanDefinitions(doc); inputstream.close(); }
private void registerBeanDefinitions(Document doc) { Element root = doc.getDocumentElement(); parseBeanDefinitions(root); }
private void parseBeanDefinitions(Element root) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; processBeanDefinition(ele); } } }
private void processBeanDefinition(Element ele) { String name = ele.getAttribute("name"); String className = ele.getAttribute("class"); BeanDefinition beanDefinition = new BeanDefinition(); beanDefinition.setClassname(className); addPropertyValues(ele, beanDefinition); getRegistry().put(name, beanDefinition); }
private void addPropertyValues(Element ele, BeanDefinition beandefinition) { NodeList propertyNode = ele.getElementsByTagName("property"); for (int i = 0; i < propertyNode.getLength(); i++) { Node node = propertyNode.item(i); if (node instanceof Element) { Element propertyEle = (Element) node; String name = propertyEle.getAttribute("name"); String value = propertyEle.getAttribute("value"); if (value != null && value.length() > 0) { beandefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value)); } else { String ref = propertyEle.getAttribute("ref"); if (ref == null || ref.length() == 0) { throw new IllegalArgumentException( "Configuration problem: <property> element for property '" + name + "' must specify a ref or value"); } BeanReference beanRef = new BeanReference(name); beandefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanRef)); } } } }
}
|
可以说代码注释写的非常详细,该类方法如下:
- public void readerXML(String location) 公开的解析XML的方法,给定一个位置的字符串参数即可。
- private void registerBeanDefinitions(Document doc) 给定一个文档对象,并进行解析。
- private void parseBeanDefinitions(Element root) 给定一个根元素,循环解析根元素下所有子元素。
- private void processBeanDefinition(Element ele) 给定一个子元素,并对元素进行解析,然后拿着解析出来的数据创建一个 BeanDefinition 对象。并注册到BeanDefinitionReader 的 Map 容器(该容器存放着解析时的所有Bean)中。
- private void addPropertyValues(Element ele, BeanDefinition beandefinition) 给定一个元素,一个 BeanDefinition 对象,解析元素中的 property 元素, 并注入到 BeanDefinition 实例中。
一共5步,完成了解析XML文件的所有操作。 最终的目的是将解析出来的文件放入到 BeanDefinitionReader 的 Map 容器中。
好了,到这里,我们已经完成了从XML文件读取并解析的步骤,那么什么时候放进BeanFactory的容器呢? 刚刚我们只是放进了 AbstractBeanDefinitionReader 的注册容器中。因此我们要根据BeanFactory 的设计来实现如何构建成一个真正能用的Bean呢?因为刚才的哪些Bean只是一些Bean的信息。没有我们真正业务需要的Bean。
2. 初始化我们需要的Bean(不是Bean定义)并且实现依赖注入
我们知道Bean定义是不能干活的,只是一些Bean的信息,就好比一个人,BeanDefinition 就相当你在公安局的档案,但是你人不在公安局,可只要公安局拿着你的档案就能找到你。就是这样一个关系。
那我们就根据BeanFactory的设计来设计一个抽象类 AbstractBeanFactory。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| package cn.thinkinjava.myspring.factory;
import cn.thinkinjava.myspring.BeanDefinition; import java.util.HashMap;
public abstract class AbstractBeanFactory implements BeanFactory {
private HashMap<String, BeanDefinition> map = new HashMap<>();
@Override public Object getBean(String name) throws Exception { BeanDefinition beandefinition = map.get(name); if (beandefinition == null) { throw new IllegalArgumentException("No bean named " + name + " is defined"); } Object bean = beandefinition.getBean(); if (bean == null) { bean = doCreate(beandefinition); } return bean; }
@Override public void registerBeanDefinition(String name, BeanDefinition beandefinition) throws Exception { Object bean = doCreate(beandefinition); beandefinition.setBean(bean); map.put(name, beandefinition); }
abstract Object doCreate(BeanDefinition beandefinition) throws Exception; }
|
该类实现了接口的2个基本方法,一个是getBean,一个是 registerBeanDefinition, 我们也设计了一个抽象方法供这两个方法调用,将具体逻辑创建逻辑延迟到子类。这是什么设计模式呢?模板模式。主要还是看 doCreate 方法,就是创建bean 具体方法,所以我们还是需要一个子类, 叫什么呢? AutowireBeanFactory, 自动注入Bean,这是我们这个标准Bean工厂的工作。看看代码吧?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| package cn.thinkinjava.myspring.factory;
import cn.thinkinjava.myspring.BeanDefinition; import cn.thinkinjava.myspring.PropertyValue; import cn.thinkinjava.myspring.BeanReference; import java.lang.reflect.Field;
public class AutowireBeanFactory extends AbstractBeanFactory {
@Override protected Object doCreate(BeanDefinition beandefinition) throws Exception { Object bean = beandefinition.getBeanclass().newInstance(); addPropertyValue(bean, beandefinition); return bean; }
protected void addPropertyValue(Object bean, BeanDefinition beandefinition) throws Exception { for (PropertyValue pv : beandefinition.getPropertyValues().getPropertyValues()) { Field declaredField = bean.getClass().getDeclaredField(pv.getname()); declaredField.setAccessible(true); Object value = pv.getvalue(); if (value instanceof BeanReference) { BeanReference beanReference = (BeanReference) value; value = getBean(beanReference.getName()); } declaredField.set(bean, value); }
}
}
|
可以看到 doCreate 方法使用了反射创建了一个对象,并且还需要对该对象进行属性注入,如果属性是 ref 类型,那么既是依赖关系,则需要调用 getBean 方法递归的去寻找那个Bean(因为最后一个Bean 的属性肯定是基本类型)。这样就完成了一次获取实例化Bean操作,并且也实现类依赖注入。
4. 总结
我们通过这些代码实现了一个简单的 IOC 依赖注入的功能,也更加了解了 IOC, 以后遇到Spring初始化的问题再也不会手足无措了。直接看源码就能解决。哈哈
具体代码楼主放在了github上,地址:自己实现的一个简单IOC,包括依赖注入
good luck !!!