IoC容器是什么?
IoC文英全称Inversion of Control,即控制反转,我么可以这么理解IoC容器:
把某些业务对象的的控制权交给一个平台或者框架来同一管理,这个同一管理的平台可以称为IoC容器。
我们刚开始学习spring的时候会经常看到的类似下面的这代码:
ApplicationContext appContext = new ClassPathXmlApplicationContext("cjj/models/beans.xml"); Person p = (Person)appContext.getBean("person");
上面代码中,在创建ApplicationContext实例对象过程中会创建一个spring容器,该容器会读取配置文件"cjj/models/beans.xml",并统一管理由该文件中定义好的所有bean实例对象,如果要获取某个bean实例,使用getBean方法就行了。例如我们只需要将Person提前配置在beans.xml文件中(可以理解为注入),之后我们可以不需使用new Person()的方式创建实例,而是通过容器来获取Person实例,这就相当于将Person的控制权交由spring容器了,差不多这就是控制反转的概念。
那在创建IoC容器时经历了哪些呢?为此,先来了解下Spring中IoC容器分类,继而根据一个具体的容器来讲解IoC容器初始化的过程。
Spring中有两个主要的容器系列:1、实现BeanFactory接口的简单容器;2、实现ApplicationContext接口的高级容器。
ApplicationContext比较复杂,它不但继承了BeanFactory的大部分属性,还继承其它可扩展接口,扩展的了许多高级的属性,其接口定义如下:
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, //继承于BeanFactory HierarchicalBeanFactory,//继承于BeanFactory MessageSource, // ApplicationEventPublisher,// ResourcePatternResolver //继承ResourceLoader,用于获取resource对象
在BeanFactory子类中有一个DefaultListableBeanFactory类,它包含了基本Spirng IoC容器所具有的重要功能,开发时不论是使用BeanFactory系列还是ApplicationContext系列来创建容器基本都会使用到DefaultListableBeanFactory类,可以这么说,在spring中实际上把它当成默认的IoC容器来使用。下文在源码实例分析时你将会看到这个类。
回到本文正题上来,关于Spirng IoC容器的初始化过程在《Spirng技术内幕:深入解析Spring架构与设计原理》一书中有明确的指出,IoC容器的初始化过程可以分为三步:
- Resource定位(Bean的定义文件定位)
- 将Resource定位好的资源载入到BeanDefinition
- 将BeanDefiniton注册到容器中
- 第一步 Resource定位
Resource是Sping中用于封装I/O操作的接口。正如前面所见,在创建spring容器时,通常要访问XML配置文件,除此之外还可以通过访问文件类型、二进制流等方式访问资源,还有当需要网络上的资源时可以通过访问URL,Spring把这些文件统称为Resource,Resource的体系结构如下:
常用的resource资源类型如下:
FileSystemResource:以文件的绝对路径方式进行访问资源,效果类似于Java中的File;
ClassPathResourcee:以类路径的方式访问资源,效果类似于this.getClass().getResource("/").getPath();
ServletContextResource:web应用根目录的方式访问资源,效果类似于request.getServletContext().getRealPath("");
UrlResource:访问网络资源的实现类。例如file: http: ftp:等前缀的资源对象;
ByteArrayResource: 访问字节数组资源的实现类。
那如何获取上图中对应的各种Resource对象呢?
Spring提供了ResourceLoader接口用于实现不同的Resource加载策略,该接口的实例对象中可以获取一个resource对象,也就是说将不同Resource实例的创建交给ResourceLoader的实现类来处理。ResourceLoader接口中只定义了两个方法:
Resource getResource(String location); //通过提供的资源location参数获取Resource实例ClassLoader getClassLoader(); // 获取ClassLoader,通过ClassLoader可将资源载入JVM
注:ApplicationContext的所有实现类都实现RecourceLoader接口,因此可以通过直接调用getResource(参数)获取Resoure对象。不同的ApplicatonContext实现类使用getResource方法取得的资源类型不同,例如:FileSystemXmlApplicationContext.getResource获取的就是FileSystemResource实例;ClassPathXmlApplicationContext.gerResource获取的就是ClassPathResource实例;XmlWebApplicationContext.getResource获取的就是ServletContextResource实例。
在资源定位过程完成以后,就为资源文件中的bean的载入创造了I/O操作的条件,如何读取资源中的数据将会在下一步介绍的BeanDefinition的载入过程中描述。
- 第二步 通过返回的resource对象,进行BeanDefinition的载入
1.什么是BeanDefinition? BeanDefinition与Resource的联系呢?
官方文档中对BeanDefinition的解释如下:
A BeanDefinition describes a bean instance, which has property values, constructor argument values, and further information supplied by concrete implementations.
它们之间的联系从官方文档描述的一句话:Load bean definitions from the specified resource中可见一斑。
/** * Load bean definitions from the specified resource. * @param resource the resource descriptor * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */ int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
总之,BeanDefinition相当于一个数据结构,这个数据结构的生成过程是根据定位的resource资源对象中的bean而来的,这些bean在Spirng IoC容器内部表示成了的BeanDefintion这样的数据结构,IoC容器对bean的管理和依赖注入的实现都是通过操作BeanDefinition来进行的。
2.如何将BeanDefinition载入到容器?
在Spring中,配置文件主要格式是XML,对于用来读取XML型资源文件来进行初始化的IoC 容器而言,该类容器会使用到AbstractXmlApplicationContext类,该类定义了一个名为loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 的方法用于获取BeanDefinition,此方法在具体执行过程中首先会new一个与容器对应的BeanDefinitionReader型实例对象,然后将生成的BeanDefintionReader实例作为参数,传入loadBeanDefintions(XmlBeanDefinitionReader)继续往下执行载入BeanDefintion的过程。例如FileSystemXmlApplicationContext容器会在此方法中new一个XmlBeanDefinitionReader对象,这个对象专门用来载入所有的BeanDefinition。下面以XmlBeanDefinitionReader对象载入BeanDefinition为例,使用源码说明载入BeanDefinition的过程:
//该方法属于AbstractXmlApplicationContect类中的一个方法protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { Resource[] configResources = getConfigResources();//获取所有定位到的resource资源位置 if (configResources != null) { reader.loadBeanDefinitions(configResources);//载入BeanDefinitions } String[] configLocations = getConfigLocations();//获取所有配置文件的位置 if (configLocations != null) { reader.loadBeanDefinitions(configLocations);//载入BeanDefinitions } }
通过上面代码中的reader.loadBeanDefinitions(configLocations)访问到AbstractBeanDefinitionReader类中的方法具体定义如下:
public int loadBeanDefinitions(Resource[] resources) throws BeanDefinitionStoreException { Assert.notNull(resources, "Resource array must not be null"); int counter = 0; for (int i = 0; i < resources.length; i++) { counter += loadBeanDefinitions(resources[i]);//该方法的在AbstractBeanDefinitionReader中并没有实现,仍然预留 //但在XmlBeanDefinitionReader中实现了 } return counter; }
XmlBeanDefinitionReader类实现了BeanDefinitionReader接口中的loadBeanDefinitions(Resource)方法,XmlBeanDefinitionReader类中几个方法的源码:
//XmlBeanDefinitionReader 继承于 AbstractBeanDefinitionReader //AbstractBeanDefinitionReader implements BeanDefinitionReader public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); } /** * Load bean definitions from the specified XML file.*/ public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isInfoEnabled()) { logger.info("Loading XML bean definitions from " + encodedResource.getResource()); } try { //通过resource对象得到XML文件,并为IO的InputSource做准备 InputStream inputStream = encodedResource.getResource().getInputStream(); try { // Create a new input source with a byte stream. InputSource inputSource = new InputSource(inputStream);if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } //开始准备 load bean definitions from the specified XML file return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } } protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { int validationMode = getValidationModeForResource(resource);//获取指定资源的验证模式 // 从资源对象中加载DocumentL对象,大致过程为:将resource资源文件的内容读入到document中 // DocumentLoader在容器读取XML文件过程中有着举足轻重的作用! // XmlBeanDefinitionReader实例化时会创建一个DefaultDocumentLoader型的私有属性,继而调用loadDocument方法 // inputSource--要加载的文档的输入源 Document doc = this.documentLoader.loadDocument( inputSource, this.entityResolver, this.errorHandler, validationMode, this.namespaceAware); return registerBeanDefinitions(doc, resource);//将document文件的bean封装成BeanDefinition,并注册到容器 } catch ...(略) }
DefaultDocumentLoader大致了解即可,感兴趣可继续深究,其源码如下:
/* Simply loads documents
using the standard JAXP-configured XML parser. */
public class DefaultDocumentLoader implements DocumentLoader { //JAXP解析XML的三种基本接口为:文档型的DOM接口,简单型的SAX接口以及XML流接口 private static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema"; protected final Log logger = LogFactory.getLog(getClass()); public Document loadDocument( InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { //创建DocumentBuilder工场 DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isDebugEnabled()) { logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); } // DOM解析器被称作DocumentBuilder,通过工场的方式获取DocumentBuilder实例对象 DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); return builder.parse(inputSource);//把文件或解析流转化成Document对象 } protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware) throws ParserConfigurationException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(namespaceAware); if (validationMode != XmlBeanDefinitionReader.VALIDATION_NONE) { factory.setValidating(true); //是否使用jaxp接口提供的XML Schema验证 if (validationMode == XmlBeanDefinitionReader.VALIDATION_XSD) { factory.setNamespaceAware(true); try { factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE); } catch (IllegalArgumentException ex) { throw new BeanDefinitionStoreException( "Unable to validate using XSD: Your JAXP provider [" + factory + "] does not support XML Schema. Are you running on Java 1.4 or below with " + "Apache Crimson? Upgrade to Apache Xerces (or Java 1.5) for full XSD support."); } } } return factory; } protected DocumentBuilder createDocumentBuilder( DocumentBuilderFactory factory, EntityResolver entityResolver, ErrorHandler errorHandler) throws ParserConfigurationException { DocumentBuilder docBuilder = factory.newDocumentBuilder(); if (errorHandler != null) { docBuilder.setErrorHandler(errorHandler); } if (entityResolver != null) { docBuilder.setEntityResolver(entityResolver); } return docBuilder; } }
上面代码分析到了registerBeanDefinitions(doc, resource)这一步,也就是准备将Document中的Bean按照Spring bean语义进行解析并转化为BeanDefinition类型,这个方法的具体过程如下:
//该方法属于XmlBeanDefinitionReader类,返回找到的bean definition的总数量public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // 为后向兼容性支持老版本XmlBeanDefinitionParser SPI. if (this.parserClass != null) { XmlBeanDefinitionParser parser = (XmlBeanDefinitionParser) BeanUtils.instantiateClass(this.parserClass); return parser.registerBeanDefinitions(this, doc, resource); } // 基于新的BeanDefinitionDocumentReader SPI版本读取document. BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();//该过程会获取到DefaultBeanDefinitionDocumentReader实例 int countBefore = getBeanFactory().getBeanDefinitionCount();//获取容器中bean的数量 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getBeanFactory().getBeanDefinitionCount() - countBefore; }
通过XmlBeanDefinitionReader类中的私有属性private Class documentReaderClass = DefaultBeanDefinitionDocumentReader.class 以及createBeanDefinitionDocumentReader() 方法可以获得一个DefaultBeanDefinitionDocumentReader实例对象,这个实例对象的registerBeanDefinitions方法定义如下:
//DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReaderpublic void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); //获取root节点,通过该节点能够访问所有的子节点 Element root = doc.getDocumentElement(); //处理beanDefinition的过程委托给BeanDefinitionParserDelegate实例对象来完成 BeanDefinitionParserDelegate delegate = createHelper(readerContext, root); // Default implementation is empty. // Subclasses can override this method to convert custom elements into standard Spring bean definitions preProcessXml(root); parseBeanDefinitions(root, delegate); postProcessXml(root); }
上面出现的BeanDefinitionParserDelegate类非常非常重要,Spirng BeanDefinition的解析就是在这个类下完成的,此类包含了各种对符合Spring Bean语义规则的处理,比如<bean></bean>、<import></import>、<alias><alias/>等的检测。parseBeanDefinitions(root, delegate)方法如下:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); // 遍历所有节点,做对应解析工作,如遍历到<import>标签节点就调用importBeanDefinitionResource(ele)方法对应处理 // 遍历到<bean>标签就调用processBeanDefinition(ele,delegate)方法对应处理 for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); //对应用户自定义节点处理方法 } } } } else { delegate.parseCustomElement(root); } } private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { //解析<import>标签 if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } //解析<alias>标签 else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } //解析<bean>标签,最常用,过程最复杂 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } //解析<beans>标签 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
这里针对常用的<bean>标签中的方法做简单介绍,其他标签的加载方式类似:
/** Process the given bean element, parsing the bean definition and registering it with the registry. */ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { //该对象持有beanDefinition的name和alias,可以使用该对象完成beanDefinition向容器的注册 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { //注册最终被修饰的bean实例 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name ‘" + bdHolder.getBeanName() + "‘", ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
这里public BeanDefinitionHolder parseBeanDefinitionElement(Element ele)方法会调用parseBeanDefinitionElement(ele, null)方法并将值返回,这个方法将会对给定的<bean>标签进行解析,如果在解析<bean>标签的过程中出现错误则返回空值,需要强调一下的是在该方法中产生了一个抽象类型的BeanDefinition实例,这也是我们首次看到直接定义BeanDefinition的地方,parseBeanDefinitionElement(ele, null)方法会产生一个BeanDefinition对象,并将<bean>标签中的内容解析到BeanDefinition中,之后再将BeanDefinition与beanName,Alias等封装到BeanDefinitionHolder 对象中,该部分源码如下:
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { String id = ele.getAttribute(ID_ATTRIBUTE); String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); ... String beanName = id; ... // 从上面按过程走来,首次看到直接定义BeanDefinition !!! // 该方法会对<bean>节点以及所有子节点如<property>、<List>、<Set>等做出解析,具体过程本文不做分析(太多太长) AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { .... } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }
- 第三步,将BeanDefiniton注册到容器中
最终Bean配置会被解析成BeanDefinition并与beanName,Alias一同封装到BeanDefinitionHolder中,之后beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition()),注册到DefaultListableBeanFactory.beanDefinitionMap中。之后客户端如果要获取Bean对象,XmlBeanFactory会根据注册的BeanDefinition信息进行实例化。
BeanDefinitionReaderUtils类:
public static void registerBeanDefinition( BeanDefinitionHolder bdHolder, BeanDefinitionRegistry beanFactory) throws BeansException { // Register bean definition under primary name. String beanName = bdHolder.getBeanName(); // 注册beanDefinition!!! beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition()); // Register aliases for bean name, if any. String[] aliases = bdHolder.getAliases(); if (aliases != null) { for (int i = 0; i < aliases.length; i++) { beanFactory.registerAlias(beanName, aliases[i]); } } }
上面调用BeanDefinitionRegistry接口实例获得的beanFactory对象是由DefaultBeanDefinitionRegistry.registerBeanDefinition(beanName, bdHolder.getBeanDefinition())获得,具体方法如下:
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { Assert.hasText(beanName, "Bean name must not be empty"); Assert.notNull(beanDefinition, "Bean definition must not be null"); if (beanDefinition instanceof AbstractBeanDefinition) { try { ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex); } } //beanDefinitionMap是个HashMap类型数据,用于存放beanDefinition,它的key值是beanName Object oldBeanDefinition = this.beanDefinitionMap.get(beanName); if (oldBeanDefinition != null) { if (!this.allowBeanDefinitionOverriding) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Cannot register bean definition [" + beanDefinition + "] for bean ‘" + beanName + "‘: there‘s already [" + oldBeanDefinition + "] bound"); } else { if (logger.isInfoEnabled()) { logger.info("Overriding bean definition for bean ‘" + beanName + "‘: replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]"); } } } else { this.beanDefinitionNames.add(beanName); } // 将获取到的BeanDefinition放入HashMap中,容器操作使用bean时通过这个HashMap找到具体的BeanDefinition this.beanDefinitionMap.put(beanName, beanDefinition);// Remove corresponding bean from singleton cache, if any. // Shouldn‘t usually be necessary, rather just meant for overriding // a context‘s default beans (e.g. the default StaticMessageSource // in a StaticApplicationContext). removeSingleton(beanName); }
本文可看做本人看书笔记学习,其中大部分参考《Spirng技术内幕:深入解析Spring架构与设计原理》以及结合网上博客等仓促而作,作此文只希望自己的技术不断提高,同时记录自己学习过程中的点点滴滴,其中肯定有许多不足之处,望谅解与指出。