spring技术内幕读书笔记之IoC容器的学习
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring技术内幕读书笔记之IoC容器的学习相关的知识,希望对你有一定的参考价值。
第二篇:IoC容器的初始化
在介绍FileSystemXmlApplicationContext 的例子时有说到IoC容器的初始化由refresh()方法开始启动,此方法标志着IoC容器的启动[构造器中有此方法的容器]。细分的话,这个启动过程具体的可分为三部分:
第一部分:BeanDefinition的Resource定位
是指BeanDefinition(容器内部定义的bean的数据结构),它由ResourceLoader通过统一的Resource接口来完成,Resource对各种形式的BeanDefinition的使用都提供了统一的接口,比如文件系统中的Bean定义信息可以通过FileSystemResource进行抽象,类路径中的Bean定信息可以通过ClassPathResource来获取。
在介绍XmlBeanFactory时可以看到,使用DefaultListableBeanFactory时,首先要定义一个Resource(即ClassPathResourceres = new ClassPathResource("SpringBeans.xml");)这里定义的Resource不能由DefaultListableBeanFactory直接使用,而是通过初始化一个XmlBeanDefinitionReader,靠reader对象来处理,完成定位的。
再拿FileSystemXmlApplicationContext的例子与之比较,不难发现ApplicationContext相对于直接使用DefaultListBeanFactory有一个好处----在ApplicationContext中Spring已经为我们提供了一系列加载不同Resource的读取器的实现。具体的读取器是怎样实现的正是此部分研究的重点。
还是那句话IoC容器的初始化由refresh()方法开始启动,FileSystemXmlApplicationContext在构造器中调用了refresh方法,在refresh方法中会调用obtainFreshBeanFactory获取beanFactory,在obtainFreshBeanFactory中会调用refreshBeanFactory这一抽象方法,在此方法的实现过程中构造了DefaultListableBeanFactory,通过XmlBeanFactory的例子我们知道DefaultListableBeanFactory初始化一个XmlBeanDefinitionReader,靠reader对象来处理,完成定位的。
reader对象有了之后,该怎样读入呢?
XmlBeanFactory是这样
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); }
这里同样需要调用loadBeanDefinitions方法,但resource是通过调用重写getResourceByPath获得的。子类FileSystemXmlApplicationContext重写了此方法会返回FileSystemResource对象,其他类型的ApplicationContext会对应生成其他类型的Resource。
这个FileSystemXmlApplicationContext的Resource定位用一句话概括就是:refresh()之后,构造出XmlBeanDefinitionReader对象,在loadBeanDefinitions时根据FileSystemXmlApplicationContext重写getResourceByPath返回的以FileSystem方式存在的resource对象进行BeanDefinition的载入,在定位完成之后知识为载入提供了I/O操作条件,并未开始读入。
第二部分:BeanDefinition的载入和解析
这个过程就是把用户定义好的Bean表示成IoC容器内部的数据结构即BeanDefinition。首先需要知道IoC容器对Bean的管理和依赖注入功能的实现,是通过对其持有的BeanDefinition进行各种相关操作来完成的。这些BeanDefinition数据在IoC容器中通过一个HashMap来保持和维护。当然这只是一种比较简单的维护方式,如果需要提高IoC容器的性能和容量,完全可以自己做一些扩展。
下面,从DefaultListableBeanFactory的设计入手,看看IoC容器是怎样完成BeanDefinition载入的。
首先是初始化的入口—refresh方法,该方法在FileSystemXmlApplicationContext的构造方法中被调用,在FileSystemXmlApplicationContext的基类AbstractApplicationContext中实现,它详细地描述了整个ApplicationContext的初始化过程,比如BeanFactory的更新,MessageSource和PostProcessor的注册等。这里看起来更像是对ApplicationContext进行初始化的模块或执行提纲,这个执行过程为Bean的生命周期管理提供了条件。
refresh实现源码:
public void refresh() throwsBeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context forrefreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. // 这里是在子类中启动refreshBeanFactory()的地方 ConfigurableListableBeanFactory beanF actory= obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // 设置BeanFactory的后置处理 // Allows post-processing ofthe bean factory in context subclasses. postProcessBeanFactory(beanFactory); // 调用BeanFactory的后处理器,这些后处理器是在Bean定义中向容器注册的 // Invoke factory processorsregistered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // 注册Bean的后处理器,在Bean创建过程中调用 // Register bean processorsthat intercept bean creation. registerBeanPostProcessors(beanFactory); // 对上下文中的消息源进行初始化 // Initialize message sourcefor this context. initMessageSource(); // 初始化上下文中的事件机制 // Initialize event multicasterfor this context. initApplicationEventMulticaster(); // 初始化其他的特殊Bean // Initialize other specialbeans in specific context subclasses. onRefresh(); // 检查监听Bean并且将这些Bean向容器注册 // Check for listener beans andregister them. registerListeners(); // 实例化所有的(non-lazy-init)单件 // Instantiate all remaining(non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // 发布容器事件,结束Refresh过程 // Last step: publish correspondingevent. finishRefresh(); }catch (BeansException ex) { logger.warn("Exceptionencountered during context initialization - cancelling refresh attempt",ex); // 为防止Bean资源占用,在异常处理中,销毁已经在前面过程中生成的单件Bean // Destroy already createdsingletons to avoid dangling resources. destroyBeans(); // 重置 ‘active‘标志 // Reset ‘active‘ flag. cancelRefresh(ex); // Propagate exception tocaller. throw ex; } } }
在ConfigurableListableBeanFactory beanFactory =obtainFreshBeanFactory();中会调用AbstractRefreshableApplicationContext中的refreshBeanFactory方法,在这个方法中创建了BeanFactory。在创建IoC容器前,如果已经有容器存在,那么需要把已有的容器销毁和关闭,保证在refresh以后使用的是新建立起来的IoC容器。在建好容器后开始BeanDefinition的载入工作。
具体的refreshBeanFactory实现:
protected final void refreshBeanFactory()throws BeansException { // 这里判断,如果已经建立了BeanFactory,则销毁并关闭该BeanFactory if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } // 这里是创建并设置持有的DefaultListableBeanFactory的地方 // 同时调用loadBeanDefinitions载入BeanDefinition的信息 try { DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } }catch (IOException ex) { throw new ApplicationContextException("I/O error parsing beandefinition source for " + getDisplayName(), ex); } }
BeanDefinition的载入交互过程:
这里调用的loadBeanDefinitions实际上是一个抽象方法,那么实际的载入过程发生在哪里呢?我们看看前面提到的loadBeanDefinitions在AbstractRefreshableApplicationContext的子类AbstractXmlApplicationContext中的实现,在这个loadBeanDefinitions中,初始化了读取器XmlBeanDefinitionReader,然后把这个读取器在IoC容器中设置好(过程和编程式使用XmlBeanFactory是类似的),最后是启动读取器来完成BeanDefinition在IoC容器的载入。
// 这里是实现loadBeanDefinitions的地方 protected void loadBeanDefinitions(DefaultListableBeanFactorybeanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. // 创建XmlBeanDefinitionReader,并通过回调设置到BeanFactory中去,创建BeanFactory // 的过程可以参考上文对编程式使用IoC容器的相关分析,这里和前面一样,使用的也是 // DefaultListableBeanFactory XmlBeanDefinitionReader beanDefinitionReader = newXmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context‘s // resource loading environment. // 这里设置XmlBeanDefinitionReader,为XmlBeanDefinitionReader配 // ResourceLoader,因为DefaultResourceLoader是父类,所以this可以直接被使用 beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(newResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. // 这是启动Bean定义信息载入的过程 initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); } protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader){ reader.setValidating(this.validating); }
接着就是loadBeanDefinitions调用的地方,首先得到BeanDefinition信息的Resource定位,然后直接调用XmlBeanDefinitionReader来读取,具体的载入过程是委托给BeanDefinitionReader完成的。因为这里的BeanDefinition是通过XML文件定义的,所以这里使用XmlBeanDefinitionReader来载入BeanDefinition到容器中。
protected voidloadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException,IOException { // 以Resource的方式获得配置文件的资源位置 Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } // 以String的形式获得配置文件的定位 String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } } protected Resource[] getConfigResources() { return null; }
通过以上对实现原理的分析,我们可以看到,在初始化FileSystemXmlApplicationContext的过程中是通过调用IoC容器的refresh来启动整个BeanDefinition的载入过程的,这个初始化是通过定义的XmlBeanDefinitionReader来完成的。同时,我们也知道实际使用的IoC容器是DefualtListableBeanFactory,具体的Resource载入在XmlBeanDefinitionReader读入BeanDefinition时实现。
因为Spring可以对应不同形式的BeanDefinition。由于这里使用的是XML方式的定义,所以需要使用XmlBeanDefinitionReder。如果使用了其他的BeanDefinition方式,就需要使用其他种类的BeanDefinitionReder来完成数据的载入工作。
在XmlBeanDefinitionReader的实现中可以看到,是在reader.loadBeanDefinitions中开始进行BeanDefinition的载入的,而这时XmlBeanDefinitionReader的父类AbstractBean-Definition-Reader已经为BeanDefinition的载入做好了准备。
@Override publicint loadBeanDefinitions(Resource... resources) throwsBeanDefinitionStoreException { Assert.notNull(resources,"Resource array must not be null"); intcounter = 0; for(Resource resource : resources) { counter+= loadBeanDefinitions(resource); } returncounter; }
这里调用的loadBeanDefinitions(resource);在AbstractBeanDefinitionReader中没有实现,它是一个接口方法,具体实现在XmlBeanDefinitionReader中。在拥有了文件对象后就可以按照spring的bean定义规则来对XML文档树进行解析了,如何得到的document对象,可以参考DefaultDocumentLoader。紧接着Spring的BeanDefinition如何按照bean语义进行解析转化成容器内部数据结构的,这部分在registerBeanDefinition(doc,resource)中完成。
loadBeanDefinitionsà doLoadBeanDefinitionsà registerBeanDefinitions
至于怎样详细转化的不再深入了。
第三部分:向IoC容器注册这些BeanDefinition
这个过程是通过BeanDefinitionRegistry接口把载入过程中解析得到的BeanDefinition项IoC容器注册,IoC容器内部将这些BeanDefinition注入到HashMap中,IoC容器就是通过这个hashmap来持有这些BeanDefinition数据的。可以看到HashMap定义在DefaultBeanFactory中。
private static final Map<String,Reference<DefaultListableBeanFactory>> serializableFactories =
new ConcurrentHashMap<String,Reference<DefaultListableBeanFactory>>(8);
具体的注册调用过程为:
从上图的调用关系可以看到registerBeanDefinition方法何时被调用。
注意:这个初始化过程不包括Bean依赖注入的实现,依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候,可以通过设置Bean的lazyinit属性使得bean的依赖注入在IoC容器初始化是就完成。
以上是关于spring技术内幕读书笔记之IoC容器的学习的主要内容,如果未能解决你的问题,请参考以下文章