框架复习(一):不如写个tiny-Spring?
项目来源
IOC
为什么要有IOC?
- 什么是依赖?
依赖,可以粗暴地理解为import,如果代码中import了某个类,那这段代码就依赖了这个类。面向接口编程时,逻辑都是接口逻辑(例如接口IA,有方法doX,doY,接口逻辑例如是main中实例化了IA后,顺序执行了doX和doY),但具体实例化的对象是IA接口的实现(例如类CA实现了IA,重写了方法doX,doY)。如果不用工厂,直接new,那么main文件里面就必须import了CA,也就是main“依赖”了CA这个实现。而面向接口编程中,main应该跟CA解耦(就是不直接依赖CA,不会看到import CA)。工厂方法就是解决这种import CA的解决途径之一,简单工厂为例,原本main里面的IA a = new CA(),就变成了IA a = AFactory.getA(“CA”),并且getA的具体实现中,可以通过如果是字符串“CA”就new CA()返回了。这样子的话,main里面就不用import CA了(但是要import AFactory),也即是不“依赖”CA,与CA解耦了。依赖注入,就是把上面的工厂,获取CA对象的方式,变成反射(也还是根据字符串来生成对象,不过就不用简单工厂if-else那么粗暴了,多一个if又要改一遍工厂的实现,多累啊),根据配置来生成对象。不用import某个实际类,但是也把依赖(逻辑过程实际执行还是CA来做的)给注入(放到main中)了。(上述的main指代任意一个逻辑执行过程,不一定是main函数)
- 依赖注入,把底层类作为参数传入上层类,实现上层类对下层类的控制。A类中:@Autowird B b;那不论B类怎么改变,都不需要改变A类中的代码。比如构造函数创建A(B b),还有Setter创建。反转A类中不应该B b=new b(),而是应该从外界注入。
- 从哪个外界注入呢?Spring设计的是IOC容器,相当于是框架本身管理注入过程。相当于A需要B b的时候,框架就getBean(“b”)给A类。
- 如果A需要b,B需要a,怎么注入?控制反转,交给IOC容器去解决。tiny-spring的实现思路:先根据xml获得全部bean标签内容,然后在getBean的时候再lazy-init。这样A需要b时,会先创建A,当遇到b时转而去创建b,最后在创建出完成的A。整个类似于一个递归(dfs)的过程。
- IOC,控制反转,通过配置文件/注解自动对对象进行初始化
- 控制反转解决了对象层级嵌套的问题,在创建一个对象时可以自动创建依赖对象并注入,Spring的IOC容器实现了从xml或注解中进行自动初始化。
- 控制反转容器因为是自上而下创建实例的,因此不需要知道其依赖类的创建方法,屏蔽了内部的细节,从外部看像一个工厂。
IOC部分要实现什么功能?
- 读取XML文件,标签为beans和property
- property内标签可为value或ref,即支持依赖注入
- 封装成ApplicationContext创建所有的bean,并且解决循环依赖
- TODO:注解版和Java配置版
第0步:下载项目
- 请用git clone下载,这样才能够通过git checkout step-1-container-register-and-get一步一步的查看不同版本。
第1步:最基本的容器
- 最基本的容器是指BeanFactory和BeanDefinition。前者有一个ConcurrentHashMap<String,BeanDefinition>,因为实现xml中字符串id对对象实例的映射。BeanDefinition包装了Bean。
第2步:将bean创建放入工厂
-
Spring中Bean实例的生成是由容器控制的,而不是由用户,因此Bean对象的创建要放在BeanFactory中。为了仿照Spring,因此抽象出FactoryBean接口,AbstractBeanFactory模板类。模板类中最重要的是protected doCreateBean()。
-
在注册的时候通过反射调用doCreateBean方法创建对象,并放入BeanDefinition包装类中。doCreateBean相当于是个动态工厂,根据string类型的全类名反射出一个Object对象。
public void registerBeanDefinition(String name,BeanDefinition beanDefinition){ Object bean = doCreateBean(beanDefinition); beanDefinition.setBean(bean); beanDefinitionMap.put(name,beanDefinition); }
-
到这一步就实现了BeanFactory的实现类可以通过全类名创建一个对象。
public class BeanFactoryTest { @Test public void test(){ BeanFactory beanFactory = new AutowireCapableBeanFactory(); BeanDefinition beanDefinition = new BeanDefinition();//创建一个包装类 beanDefinition.setBeanClassName("beans.Car");//通过反射创建,要求必须有无参构造函数 beanFactory.registerBeanDefinition("audi",beanDefinition);//注册到hashmap中,注册之前先调用doCreateObject方法创建对象,实现了在Facoty中创建对象 System.out.println((Car)beanFactory.getBean("audi")); } }
-
在看上述代码,我们要传给factory什么?1.全类名,即beans.Car。2.实例化后的实例名称,即audi。这两项显然我们都能在配置的xml中获取,这在第四步中完成。其次,我们目前创建出的对象还是一个依靠无参构造函数创建的,因此内部成员变量均为null,所以下一步是对成员变量进行赋值。
第3步:为Bean注入属性
-
-
这一步有两个类,PropertyValues和PropertyValue。PV类相当于是C++中的Pair<String fieldName,Object Value>类,保存字段和字段对应的值。PVS中保存了一个对象中所有字段和值的对应关系,即保存了一个List。每个BeanDefinition中都有一个PVS,因此每个BeanDefinition在创建完空Bean后可以遍历PVS,通过反射实现Setter。
protected void applyPropertyValues(Object bean,BeanDefinition mbd) throws NoSuchFieldException, IllegalAccessException { for(PropertyValue propertyValue:mbd.getPropertyValues().getPropertyValues()){ Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName()); declaredField.setAccessible(true); declaredField.set(bean,propertyValue.getValue()); } }
第4步:读取xml配置来初始化bean
- 解决获取IO流的问题?URL类定位xml文件,url.openConnect().connect()即可定位并打开文件,利用getInputStream获得文件输入流。
- 通过XMLBeanDefinitionReader类和DocumentBuilder对xml进行解析。先根据bean定位到所有的bean,根据类名和实例名构建一个空实例,然后每一个bean中定位property,利用PVS类和PV类实现对bean属性的赋值
- 官方结构
第5步:为bean注入bean
- 核心解决三个问题1.ref怎么实现?2.怎么解决xml中顺序问题?2.怎么避免循环依赖?
-
怎么实现ref?
-
这个问题好解决。判断xml中是ref还是value,如果是value(本项目目前value如果是基本类型,只允许是String)则直接用PV(PropertyValue)封装,如果是ref,就用BeanReference{name,bean}封装一下然后再用PV封装。
private void processProperty(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 beanReference = new BeanReference(ref); beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference)); } } } }
-
在调用applyPropertyValues()方法——通过反射装填实例的成员变量时,如果该变量是BeanReference,则该变量有可能需要创建一下。
protected void applyPropertyValues(Object bean, BeanDefinition mbd) throws Exception { for (PropertyValue propertyValue : mbd.getPropertyValues().getPropertyValues()) { Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName()); declaredField.setAccessible(true); Object value = propertyValue.getValue(); if (value instanceof BeanReference) { BeanReference beanReference = (BeanReference) value; value = getBean(beanReference.getName()); } declaredField.set(bean, value); } }
-
注意上述代码中的value=getBean(beanReference.getName())。实例的创建过程有可能就在此刻完成。这里需要明确的是下图:
读取xml后,所有的类信息都在XmlBeanDefinitionReader实例中,但是XmlBDFR中的bean
-