#前言
上一节我们分析了Spring的实例化和IOC过程。在熟悉了Spring的处理流程后,我们自己能不能写一个IOC的容器和实现依赖注入呢?要注意哪些问题呢?本章节我们重点关注两个问题。
- 手写一个简单的IOC容器并实现依赖注入
- 分析Spring是怎样解决循环依赖的问题
1、加载配置文件
先来看配置文件,我们定义了两个Bean,User和Role。
复制代码
扫描方式很简单,main方法指定了XML文件的路径。获取文件的输入流,转成Document对象解析即可,这点和Spring的做法是一致的。并把property属性简单化处理,放在一个List<Map<String,String>>中。
/*** * 遍历XML文件,解析bean标签 * @param location * @throws Exception */ private void loadBeans(String location) throws Exception { // 加载 xml 配置文件 InputStream inputStream = new FileInputStream(location); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = factory.newDocumentBuilder(); Document doc = docBuilder.parse(inputStream); Element root = doc.getDocumentElement(); NodeList nodes = root.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { BeanDefinition beanDefinition = new BeanDefinition(); Node node = nodes.item(i); if (node instanceof Element) { Element ele = (Element) node; String id = ele.getAttribute("id"); String beanName = ele.getAttribute("class"); beanDefinition.setId(id); beanDefinition.setBeanName(beanName); NodeList propertyNodes = ele.getElementsByTagName("property"); List
2、实例化
拿到beanName的集合,遍历进行实例化和依赖注入。
private void doLoadBeanDefinitions() throws Exception{ for(String beanName:beanNames){ BeanDefinition beanDefinition = beanClassMap.get(beanName); DI(beanDefinition); }}private void DI(BeanDefinition beanDefinition) throws Exception{ Class beanClass = createBean(beanDefinition.getBeanName()); Object bean = beanClass.newInstance(); List
3、测试
进行main函数,看看测试结果。
public static void main(String[] args) throws Exception { String path = "D:\\Workspaces\\Netty\\src\\ioc\\ioc_1.xml"; new IOC_1(path); Iterator> item = beanMap.entrySet().iterator(); while(item.hasNext()){ Entry next = item.next(); if (next.getValue() instanceof User) { User user = (User) next.getValue(); System.out.println("userId:"+user.getId()); System.out.println("userName:"+user.getName()); System.out.println("userRoleName:"+user.getRole().getName()); }else{ Role role = (Role) next.getValue(); System.out.println("roleId:"+role.getId()); System.out.println("roleName:"+role.getName()); } System.out.println("-----------------"); }}复制代码
输出结果如下
roleId:r_2001roleName:管理员-----------------userId:u_1001userName:吴蓓蓓userRoleName:管理员-----------------复制代码
从结果来看,这两个Bean的实例化和依赖注入是没问题的,完成了我们的本章节提出的第一个小目标。
4、循环依赖
如果我们把配置文件改一下,让User和Role形成循环依赖呢?我们的程序还能正常吗?
复制代码
哈哈,不敢运行。就上面的代码而言,它肯定会死循环。User依赖Role,注入的时候发现还没有Role的实例,就先去实例化Role;实例化Role的时候,又发现依赖了User,再去实例化User...好了,下面我们看下Spring是怎么解决这事的。 分析Spring之前,我们先来了解几个缓存的定义
名称 | 作用 |
---|---|
singletonObjects | 用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用 |
earlySingletonObjects | 存放原始的 bean 对象(尚未填充属性),用于解决循环依赖 |
singletonFactories | 存放 bean 工厂对象,用于解决循环依赖 |
singletonsCurrentlyInCreation | 当前正在创建的bean的名称 |
看到这几个缓存,我们可以大概理出一个思路。
1、实例化Bean的时候,先从singletonObjects查找缓存,如果命中就可以直接返回,未命中的话先把Bean放入singletonsCurrentlyInCreation,说明自己正在创建中。2、具体开始实例化。完成后,把beanName和对应的bean工厂放入singletonFactories。3、依赖注入,当有循环依赖的时候,重复第1个步骤。还是从singletonObjects查找缓存,虽然还是未命中,但是发现bean正在创建中了。然后从singletonFactories中获取bean的工厂对象,拿到该Bean的对象。然后把这个Bean提前曝光,放入earlySingletonObjects。4、注入完成,循环依赖问题解决。复制代码
来看个流程图,再整理下思路。
基于上面的思路,把上面我们自己实现的代码重新改造一下。全部代码在,大家可以拿下来运行一下看看。
- 1、还是先遍历所有的beanName
/** * 遍历XML中配置的bean,进行实例化和IOC * @throws Exception */ private void doLoadBeanDefinitions() throws Exception{ for(String beanName:beanNames){ BeanDefinition beanDefinition = beanClassMap.get(beanName); doGetBean(beanDefinition); } }复制代码
- 2、 获取Bean实例
private Object doGetBean(BeanDefinition beanDefinition) throws Exception{ Object bean = null; String beanName = beanDefinition.getId(); Object sharedInstance = getSingleton(beanName,true); if (sharedInstance !=null) { bean = sharedInstance; }else{ Object singletonObject = getSingleton(beanDefinition); bean = singletonObject; } return bean; }复制代码
- 3、下面来看两个getSingleton方法。第一个主要是为了判断是否正在创建中,如果是就从工厂里先拿到一个Bean返回;第二个是Bean实际创建过程。
/** * 先从缓存中获取,如果未命中并且没有在创建中,返回NULL * 如果Bean正在创建中,从工厂中先拿到Bean返回(还未填充属性) * @param beanName * @param allowEarlyReference * @return */ private Object getSingleton(String beanName,boolean allowEarlyReference){ Object beanObject = singletonObjects.get(beanName); if (beanObject == null && singletonsCurrentlyInCreation.contains(beanName)) { beanObject = earlySingletonObjects.get(beanName); if (beanObject ==null && allowEarlyReference) { Object singletonFactory = singletonFactories.get(beanName); if (singletonFactory != null) { beanObject = singletonFactory; earlySingletonObjects.put(beanName, beanObject); singletonFactories.remove(beanName); } } } return beanObject; } /** * 先从缓存获取Bean,如果未命中,直接创建,并把创建完且注入完成的Bean放入缓存 * @param beanDefinition * @return * @throws Exception */ private Object getSingleton(BeanDefinition beanDefinition) throws Exception{ String beanName = beanDefinition.getId(); Object singletonObject = singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = createBean(beanDefinition); singletonObjects.put(beanName,singletonObject); singletonFactories.remove(beanName); earlySingletonObjects.remove(beanName); } return singletonObject; } 复制代码
- 4、Bean的实际创建,重点是创建之前把之前放入singletonsCurrentlyInCreation,创建之后把自己的实例放入bean工厂,singletonFactories。
/** * 实际创建Bean的过程 * 先把自己放入singletonsCurrentlyInCreation,说明正在创建中 * 把创建好的实例放入工厂。singletonFactories * @param beanDefinition * @return * @throws Exception */ private Object createBean(BeanDefinition beanDefinition) throws Exception{ String beanName = beanDefinition.getId(); singletonsCurrentlyInCreation.add(beanName); Object bean = beanDefinition.getBeanClass().newInstance(); if (!singletonObjects.containsKey(beanName)) { singletonFactories.put(beanName, bean); earlySingletonObjects.remove(beanName); } populateBean(bean, beanDefinition.getPropertyList()); return bean; }复制代码
- 5、注入属性,属性值的类型如果是ref引用类型,就再循环调用doGetBean。同一个Bean在第二次调用的时候,就会拿到工厂里的Bean对象并返回,完成注入。
public void populateBean(Object bean,List
5、总结
关于循环依赖,Spring源码里处理的时候非常的绕,还有很多内部类糅杂在一块,刚开始看的我简直怀疑人生。最好先弄明白那几个缓存的含义,再去理解这个流程。 关于Spring源码这一块就不贴了,太分散而且太多,有兴趣的小伙伴可以自行翻阅。