码迷,mamicode.com
首页 > 编程语言 > 详细

Spring IoC容器初始化过程

时间:2016-12-05 02:16:21      阅读:264      评论:0      收藏:0      [点我收藏+]

标签:int   default   method   url   protected   学习   用户   sys   final   

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容器的初始化过程可以分为三步:

  1. Resource定位(Bean的定义文件定位)
  2. 将Resource定位好的资源载入到BeanDefinition
  3. 将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 BeanDefinitionDocumentReader
public
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架构与设计原理》以及结合网上博客等仓促而作,作此文只希望自己的技术不断提高,同时记录自己学习过程中的点点滴滴,其中肯定有许多不足之处,望谅解与指出。

Spring IoC容器初始化过程

标签:int   default   method   url   protected   学习   用户   sys   final   

原文地址:http://www.cnblogs.com/chenjunjie12321/p/6124649.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!