Spring IOC之BeanDefinition的注册与加载

879 阅读12分钟

Spring IoC容器的实现之一(BeanDefinition的加载与解析)

本文所用源码为spring-framework-5.1.2,点我直达,分支为v5.1.2.RELEASE_DYX

根容器 BeanFactory

  1. 位于spring-beans组件中的org.springframework.beans.factory包中

    package org.springframework.beans.factory;
    
    import org.springframework.beans.BeansException;
    
    public interface BeanFactory {
      String FACTORY_BEAN_PREFIX = "&";
      Object getBean(String name) throws BeansException;
      <T> T getBean(String name, Class<T> requiredType) throws BeansException;
      Object getBean(String name, Object... args) throws BeansException;
      <T> T getBean(Class<T> requiredType) throws BeansException;
      <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
      <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
      <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
      boolean containsBean(String name);
      boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
      boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
      boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
      boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
      Class<?> getType(String name) throws NoSuchBeanDefinitionException;
      String[] getAliases(String name);
    }
    
  2. BeanFactory描述了整个IoC容器范围内的规范和原则,在注释上说明了BeanFactory的实现应该满足哪些契约或者规范,比如应该提供Bean与Bean之间的引用关系、生命周期方法、Bean的获得方式等

BeanFactory与FactoryBean详解

  1. BeanFactory用于创建并管理对象,他是一个工厂;FactoryBean指Factory这个Bean本身,而它是一个特殊的Bean,其中有个getObject()方法,如果一个类实现了这个接口,那个这个类的实例就作为一个工厂,它的getObject方法返回的是工厂所创建的对象,而不是直接返回“xx工厂”这个实例(xxxFactoryBean)本身,假如对象A实现了FactoryBean这个接口,并在xml文件中配置,则通过getBean("A")方法返回的是A工厂所创建的对象,而不是A工厂这个实例本身,如果要返回A工厂这个实例本身呢,使用getBean("&A")方法

    <bean id="person1" class="com.dyx.pojo.PersonFactoryBean"></bean>
    
    public class PersonFactoryBean implements FactoryBean<Person> {
      @Override
      public Person getObject() throws Exception {
         return new Person("test1","test2","test3");
      }
    
      @Override
      public Class<?> getObjectType() {
         return Person.class;
      }
    
      @Override
      public boolean isSingleton() {
         return false;
      }
    }
    
    Object person1 = beanFactory.getBean("person1");
    System.out.println(person1);
    
    Object person2 = beanFactory.getBean("&person1");
    System.out.println(person2);
    
  2. FactoryBean是实现AOP的一个重要的基石,FactoryBean也是属于BeanFactory的管辖范围之内的

  3. BeanFactory与FactoryBean的区别:

    1. BeanFactory是整个Spring框架中的根容器,它定义了IOC容器的最基本形式,并提供了IOC容器应遵守的的最基本的接口,也就是Spring IOC所遵守的最底层和最基本的编程规范。
    2. 在Spring代码中,BeanFactory只是个接口,并不是IOC容器的具体实现, 但是Spring容器给出了很多种实现,如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等,都是附加了某种功能的实现。
    3. FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它本身也受BeanFactory进行管理
    4. 以XxxFactoryBean结尾,表示它是一个工厂Bean,但这个Bean不是简单的Bean,不同于普通Bean的是:它是实现了FactoryBean接口的Bean,根据该Bean的ID从BeanFactory中获取(getBean方法)的实际上是FactoryBean的getObject()返回的对象,而不是FactoryBean本身,如果要获取FactoryBean对象,请在id前面加一个&符号来获取。所以在BeanFactory中定义了FACTORY_BEAN_PREFIX这个变量

DefaultListableBeanFactory及资源载入

  1. DefaultListableBeanFactory是BeanFactory的核心实现类,该类有个子类XmlBeanFactory,用于从xml文件中读取配置信息(完成资源的载入)并构建装配对象,但从Spring3.1开始,该类已经废弃,Spring官方推荐使用DefaultListableBeanFactory与XmlBeanDefinitionReader配合使用
  2. Spring将不同的资源文件抽象成为不同的Resource对象,使得后续处理只需要面对Resource,Resource表示资源的接口,不同来源的资源文件具有不同的接口实现
  3. XmlBeanDefinitionReader作为资源的读取器
     ClassPathResource resource = new ClassPathResource("applicationContext.xml");
     DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
     XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
     reader.loadBeanDefinitions(resource); 
    
  4. Spring加载资源并装配对象的过程
    1. 定义好Spring的配置文件,以xml配置文件为例
    2. 通过Resource对象将Spring配置文件进行抽象,抽象成为一个Resource对象
    3. 定义好Bean工厂(各种BeanFactory)
    4. 定义好XmlBeanDefinitionReader对象,并将工厂作为参数传递进去供后续回调使用
    5. 通过XmlBeanDefinitionReader对象读取(加载)之前抽象出的Resource对象,此过程包含XML文件的解析
    6. 本质上,Xml文件的解析是由XmlBeanDefinitionReader对象交由BeanDefinitionParserDelegate对象委托来完成的,实质上这里使用了委托模式
    7. IoC容器创建完毕,用户可以通过容器获取到所需的对象信息

XmlBeanDefinitionReader与Resource

ClassPathResource resource = new ClassPathResource("applicationContext.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
  1. 在DefaultListableBeanFactory中有个很重要的Map对象:BeanDefinitionMap对象,里面用于存储Bean的相关信息,这些信息使用BeanDefinition对象进行抽象,在BeanDefinitionMap对象中,Bean的名字作为Key,value为BeanDefinition

  2. ClassPathResource用于从类路径下加载配置文件,该类含有三个成员变量path表示资源文件的存放路径,classLoader表示类加载器,clazz表示类(字节码)对象: new ClassPathResource("applicationContext.xml");

  3. XmlBeanDefinitionReader的loadBeanDefinitions(resource) 方法用于加载资源文件,并解析相关引用信息,并装配:

    1. 上文中的reader.loadBeanDefinitions(resource)又调用了loadBeanDefinitions(new EncodedResource(resource)),表示从指定的xml文件中加载BeanDefinition并返回所加载BeanDefinition的数量。

    2. loadBeanDefinitions(EncodedResource encodedResource)方法中,实际上真正加载、解析、装配的方法是doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法,inputSource是resource中所含有的InputStream对象,该InputStream是一个含有xml配置文件的输入流对象,Resource接口又继承了InputStreamResource接口,encodedResource是classPathResource的包装对象,里面配置了编码信息

      InputStream inputStream = encodedResource.getResource().getInputStream();
      try {
         InputSource inputSource = new InputSource(inputStream);
         if (encodedResource.getEncoding() != null) {
            inputSource.setEncoding(encodedResource.getEncoding());
         }
         // 这个方法是真正完成从指定的xml文件中加载bean definition的
         return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
      }
      finally {
         inputStream.close();
      }
      
    3. 首先将xml配置文件解析成一个Document对象,这里使用的是W3C的sax解析机制

      try {
        	// document代表的是xml文档中的根对象
        	Document doc = doLoadDocument(inputSource, resource);
         // 注册BeanDefinition,并返回注册的个数
        	int count = registerBeanDefinitions(doc, resource);
        	if (logger.isDebugEnabled()) {
        		logger.debug("Loaded " + count + " bean definitions from " + resource);
        	}
        	return count;
        }
        catch (BeanDefinitionStoreException ex) {
        	throw ex;
        }
      ...
      

      而在doLoadDocument方法中:

      protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        // documentLoader是一个用于加载xml中document的加载器,
        // 这里借用w3c的sax解析机制来获取xml中的document,Spring本身没有做任何解析xml的操作
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
        		getValidationModeForResource(resource), isNamespaceAware());
       }
      
    4. 调用registerBeanDefinitions(document,resource)方法

      public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
      // 使用 DefaultBeanDefinitionDocumentReader 实例化 BeanDefinitionDocumentReader
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        // 获取以前已经存在,即已经注册好的bean definition的数量,
        // 实际上就是org.springframework.beans.factory.support.DefaultListableBeanFactory中的beanDefinitionMap的size
        int countBefore = getRegistry().getBeanDefinitionCount();
      
        // 真正的注册逻辑,很关键
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
      // 记录本次加载的 BeanDefinition 个数
        return getRegistry().getBeanDefinitionCount() - countBefore;
       }
      
    5. 调用createBeanDefinitionDocumentReader()方法,创建一个BeanDefinitionDocumentReader接口对象,是解析BeanDefinition的文档读取器,他是一个接口,且只有一个实现:DefaultBeanDefinitionDocumentReader类,它是BeanDefinitionDocumentReader的默认实现,这个类会根据在spring-beans中的DTD与XSD中对bean的定义来读取相应的信息。

    6. 使用documentReader调用registerBeanDefinitions(doc, createReaderContext(resource))方法,实际上是调用DefaultBeanDefinitionDocumentReader对象的方法实现,而createReaderContext(resource)方法返回的是XmlReaderContext对象,是XmlBeanDefinitionReader的一个增强插件,里面封装了Resource、ProblemReporter、ReaderEventListener、SourceExtractor、XmlBeanDefinitionReader、NamespaceHandlerResolver等对象实例

      // 打开document,并在<beans></beans>层级上初始化默认设置,随后解析其内的bean的定义
      public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        // doc.getDocumentElement()实际上就是xml文档的根元素root,这个方法的重要目的之一就是提取root,以便于再次将 root 作为参数继续 BeanDefinition 的注册
          doRegisterBeanDefinitions(doc.getDocumentElement());
      }
      
    7. 在DefaultBeanDefinitionDocumentReader对象中,包含有XmlReaderContext与BeanDefinitionParserDelegate(这个类是解析xml的关键类)成员变量,上一步的方法调用将XmlReaderContext设置进来,并调用doRegisterBeanDefinitions(root)方法;在doRegisterBeanDefinitions方法中实际上调用preProcessXml(root)、parseBeanDefinitions(root, this.delegate)、postProcessXml(root)方法来真正地解析Document文档对象,this.delegate是BeanDefinitionParserDelegate委托对象,其中preProcessXml()和postProcessXml()默认是空方法,我们可以通过继承该DefaultBeanDefinitionDocumentReader类,并重写上述两个方法,自定义对Element文档对象的前置及后置处理,实现对xml文件的自定义扩展,这里体现了“模板方法”设计模式

      protected void doRegisterBeanDefinitions(Element root) {
      
         BeanDefinitionParserDelegate parent = this.delegate;
         this.delegate = createHelper(readerContext, root, parent);
      
         if (this.delegate.isDefaultNamespace(root)) {
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
               String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                     profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
               // We cannot use Profiles.of(...) since profile expressions are not supported
               // in XML config. See SPR-12458 for details.
               if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                  if (logger.isDebugEnabled()) {
                     logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                           "] not matching: " + getReaderContext().getResource());
                  }
                  return;
               }
            }
         }
      
         preProcessXml(root);
         parseBeanDefinitions(root, this.delegate);
         postProcessXml(root);
         this.delegate = parent;
      }
      
    8. 实际上解析xml文件的是BeanDefinitionParserDelegate对象,它是一个委托对象,在BeanDefinitionParserDelegate类中,定义了在xml配置文件中可以使用的全部属性的值,在DefaultBeanDefinitionDocumentReader类的createHelper方法中构建委托对象并返回供后续使用,里面的initDefaults方法会初始化文档的一些默认配置

      protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate) {
        BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext, environment);
        delegate.initDefaults(root, parentDelegate);
        return delegate;
      }
      
    9. BeanDefinitionParserDelegate对象解析资源文档,并装配生成BeanDefinition,调用parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)方法

BeanDefinitionParserDelegate与资源解析

  1. BeanDefinitionParserDelegate的字符串常量中封装了xml文件中常见的属性配置,如name、bean、id、class、lazy-init等

  2. 当文档中含有beans标签的嵌套时,采用递归回滚的方式进行解析

BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createHelper(readerContext, root, parent);
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
  1. 在parseBeanDefinitions()方法中,判断当前解析元素是否属于默认的命名空间,如果是的话,就调用parseDefaultElement()方法,否则调用delegate上parseCustomElement()方法,只有http://www.springframework.org/schema/beans 会被认为是默认的命名空间。也就是说,beans、bean这些元素,会认为属于默认的命名空间,而像task:scheduled这些,就认为不属于默认命名空间。根节点beans的一个子节点bean,是属于默认命名空间的,所以会进入parseDefaultElement()方法

  2. 这里可能会有4种情况,import、alias、bean、beans,分别有一个方法与之对应,如果解析的是bean元素,所以会进入processBeanDefinition()方法,如果解析的是beans元素,则认定为嵌套元素,再递归解析

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
      if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
          importBeanDefinitionResource(ele);
       }
       else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
          processAliasRegistration(ele);
       }
       else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
          processBeanDefinition(ele, delegate);
       }
       else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
          // recurse
          doRegisterBeanDefinitions(ele);
       }
    }
    
  3. 这里主要有3个步骤,先是委托delegate对bean进行解析,然后委托delegate对bean进行装饰,最后由一个工具类来完成BeanDefinition的注册

    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
       BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
       if (bdHolder != null) {
          bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
          try {
             // Register the final decorated instance.
             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));
       }
    }
    
  4. 从设计原则上看,XmlBeanDefinitionReader负责资源文件的加载读取,而BeanDefinitionParserDelegate负责真正的解析工作,体现了单一职责原则

BeanDefinitionParserDelegate深入详解

  1. delegate.parseBeanDefinitionElement(ele)方法返回一个BeanDefinitionHolder对象,其内包含一个BeanDefinition beanDefinition、String beanName、String[] aliases的引用
  2. 对BeanDefinitionHolder对象进行装饰
  3. 调用BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());实际上是调用DefaultListableBeanFactory的registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法,将BeanName和BeanDefinition存入BeanDefinitionMap中
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
    // ...
       this.beanDefinitionMap.put(beanName, beanDefinition);
    // ...
    }
    

Spring Bean的创建与获取

  1. 上述过程并不涉及Bean的创建,只是处理XML的解析、BeanDefinition的注册,调用getBean方法,实际上调用的是AbstractBeanFactory类中的doGetBean方法

    public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
    	return doGetBean(name, requiredType, null, false);
    }
    
  2. Spring的Bean实际上是缓存在DefaultSingletonBeanRegistry类的众多HashMap中,其中之一是singletonObjects

    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
    
  3. 在创建Bean之前,首先需要将该Bean的创建标识设定好,表示该Bean已经或者是即将被创建,为的是增强缓存的效率

  4. 根据Bean的scope属性来确定是singleton或者是prototype,然后创建相应的Bean,调用getSingleton(String beanName, ObjectFactory singletonFactory)方法,参数中ObjectFactory接口的意义与FactoryBean类似,都是获取对象的接口,getObject()方法,在getSingleton()方法中使用匿名内部类调用createBean()方法,获得实例

    sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
    					public Object getObject() throws BeansException {
    						try {
    							return createBean(beanName, mbd, args);
    						}
    						catch (BeansException ex) {
    							// Explicitly remove instance from singleton cache: It might have been put there
    							// eagerly by the creation process, to allow for circular reference resolution.
    							// Also remove any beans that received a temporary reference to the bean.
    							destroySingleton(beanName);
    							throw ex;
    						}
    					}
    				});
    
  5. 实际创建Bean的过程位于AbstractAutowireCapableBeanFactory类的doCreateBean方法中,通过反射来创建Bean的实例,在创建之前首先检查访问修饰符,如果不是public的,则调用setAccessible(true)来突破Java的语法限制,使得可以通过如私有的构造方法来创建实例,并且在创建时会检查依赖,如果有依赖,会先创建被依赖的对象实例

  6. 接下来,寻找Bean的属性值,完成属性的注入,instantiateBean()方法等,

  7. 将所创建的singleton实例添加到上述第二步的singletonObjects缓存中,供下次直接使用

IoC容器中的缓存

其中的几个重要的缓存 用途
singletonObjects 用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用
earlySingletonObjects 存放原始的 bean 对象(尚未填充属性),用于解决循环依赖
singletonFactories 存放 bean 工厂对象,用于解决循环依赖
singletonsCurrentlyInCreation 当前正在创建的bean的名称
  1. 解析并注册BeanDefinination时的大多数缓存位于DefaultSingletonBeanRegistry类当中。如果Bean是单例的,实例化后会将Bean存入singletonObjects缓存中,如果是prototype的,则不会存入缓存。

  2. 实例化Bean的时候,先从singletonObjects查找缓存,如果命中就可以直接返回,未命中的话先把Bean放入singletonsCurrentlyInCreation,说明自己正在创建中。

  3. 具体开始实例化。完成后,把beanName和对应的bean工厂放入singletonFactories。

  4. 依赖注入,当有循环依赖的时候,重复第1个步骤。

  5. 还是从singletonObjects查找缓存,虽然还是未命中,但是发现bean正在创建中了。

  6. 然后从singletonFactories中获取bean的工厂对象,拿到该Bean的对象。然后把这个Bean提前曝光,放入earlySingletonObjects。

  7. 注入完成,循环依赖问题解决。

ApplicationContext

  1. ApplicationContext接口继承自ListableBeanFactory、HierarchicalBeanFactory接口,能够包含 BeanFactory的所有基本功能,并提供了更多的扩展功能。

    1. 在ApplicationContext的其中一个实现类中XmlWebApplicationContext中,该类中的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)与BeanFactory类的核心实现---------DefaultListableBeanFactory的子类加载资源并解析过程一致

    2. XmlWebApplicationContext中指定了配置文件的默认为/WEB-INF/applicationContext.xml,如果不使用或者没有指定配置文件的位置,则会加载默认位置的配置文件

      /** Default config location for the root context */
      public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";     
      
  2. 开发中常用ClassPathXmlApplicationContext类