zoukankan      html  css  js  c++  java
  • Spring源码解析二:IOC容器初始化过程详解

    IOC容器初始化分为三个步骤,分别是:

    1、Resource定位,即BeanDefinition的资源定位。

    2、BeanDefinition的载入

    3、向IOC容器注册BeanDefinition

    下面我们来详细展开这三部分的内容

    一、Resource定位

     以ApplicationContext的具体实现FileSystemXmlApplicationContext来介绍Resource定位的过程:

     IOC容器初始化类比为用木桶来装水,Resource的定位过程就是寻找水的过程。

    它是由ResourceLoader通过统一的Resource接口来完成的,Resource对各种形式的BeanDefinition的使用都提供了统一的接口。

    Resource接口有许多实现类,针对不同的BeanDefinition,如:

    在文件系统中的Bean定义信息可以使用FileSystemResource来进行抽象。

    在类路径中的Bean定义信息可以使用ClassPathResource来进行抽象。

    我们来看下FileSystemXmlApplicationContext其中的一个构造函数:

    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
                throws BeansException {
    
            super(parent);
            setConfigLocations(configLocations);
            if (refresh) {
                refresh();
            }
        }

    这里有个refresh()方法,这个方法非常重要,可以说是IOC容器启动的入口,该方法的实现在AbstractApplicationContext中。

    在AbstractApplicationContext的refresh方法中,最终对调用到AbstractRefreshableApplicationContext的refreshBeanFactory,在这里会创建一个基础的IOC容器供ApplicationContext使用,这个基础的BeanFactory就是DefaultListableBeanFactory。

    protected final void refreshBeanFactory() throws BeansException {
            if (hasBeanFactory()) {
                destroyBeans();
                closeBeanFactory();
            }
            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 bean definition source for " + getDisplayName(), ex);
            }
        }

    再根据loadBeanDefinitions方法一直往下查找,最终会找到DefaultResourceLoader类的getResource方法:

    public Resource getResource(String location) {
            Assert.notNull(location, "Location must not be null");
            if (location.startsWith(CLASSPATH_URL_PREFIX)) {
                return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
            }
            else {
                try {
                    // Try to parse the location as a URL...
                    URL url = new URL(location);
                    return new UrlResource(url);
                }
                catch (MalformedURLException ex) {
                    // No URL -> resolve as resource path.
                    return getResourceByPath(location);
                }
            }
        }

    这里的getResourceByPath方法最终被子类重写,该场景下重写该方法的类就是FileSystemXmlApplicationContext

    protected Resource getResourceByPath(String path) {
            if (path != null && path.startsWith("/")) {
                path = path.substring(1);
            }
            return new FileSystemResource(path);
        }

    这样我们从FileSystemXmlApplicationContext的构造函数中的refresh方法开始,最终到getResourceByPath结束,完成了资源定位的解析。但是这个只是容器启动中的一部分,还有载入和解析也是糅合在一起的,只是为了思路清晰,故意对这些地方视而不见。

    二、BeanDefinition的载入和解析

    在第一部分资源的定位中,我们是以getResourceByPath()结束的,接下来我们要通过该方法返回的Resource对象来进行BeanDefinition的载入了。现在我们已经知道水在哪里了,就可以现在打水和装水之旅了。

    这个过程就是将定义的BeanDefinition在IOC容器中转化成一个Spring内部表示的数据结构的过程。我们紧接着前面的源码往下梳理:

    AbstractRefreshableApplicationContext的方法refreshBeanFactory中调用loadBeanDefinitions(),往下梳理找到类AbstractXmlApplicationContext,源码片段如下;

     1 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
     2         // Create a new XmlBeanDefinitionReader for the given BeanFactory.
     3         XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
     4 
     5         // Configure the bean definition reader with this context's
     6         // resource loading environment.
     7         beanDefinitionReader.setResourceLoader(this);
     8         beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
     9 
    10         // Allow a subclass to provide custom initialization of the reader,
    11         // then proceed with actually loading the bean definitions.
    12         initBeanDefinitionReader(beanDefinitionReader);
    13         loadBeanDefinitions(beanDefinitionReader);
    14     }

    其中第7行是为读取器设置ResourceLoader,为后面的资源读取做准备;第13行就是启动Bean定义信息载入的开始。

     1 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
     2         Resource[] configResources = getConfigResources();
     3         if (configResources != null) {
     4             reader.loadBeanDefinitions(configResources);
     5         }
     6         String[] configLocations = getConfigLocations();
     7         if (configLocations != null) {
     8             reader.loadBeanDefinitions(configLocations);
     9         }
    10     }

    第2行是以Resource的方式获取配置文件的资源位置,而第6行是以String的形式获取配置文件的位置。

    reader.loadBeanDefinitions()的实现见XmlBeanDefinitionReader的父类AbstractBeanDefinitionReader

    1 public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
    2         Assert.notNull(locations, "Location array must not be null");
    3         int counter = 0;
    4         for (String location : locations) {
    5             counter += loadBeanDefinitions(location);
    6         }
    7         return counter;
    8     }

    这里如果Resource为空,则停止BeanDefinition的载入。在启动BeanDefinition的过程中,会遍历整个Resource集合所包含的BeanDefinition的信息。

    这里面调用的loadBeanDefinitions()方法具体实现并不在AbstractBeanDefinitionReader,而是在子类里:

     1 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
     2         Assert.notNull(encodedResource, "EncodedResource must not be null");
     3         if (logger.isInfoEnabled()) {
     4             logger.info("Loading XML bean definitions from " + encodedResource.getResource());
     5         }
     6 
     7         Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
     8         if (currentResources == null) {
     9             currentResources = new HashSet<EncodedResource>(4);
    10             this.resourcesCurrentlyBeingLoaded.set(currentResources);
    11         }
    12         if (!currentResources.add(encodedResource)) {
    13             throw new BeanDefinitionStoreException(
    14                     "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    15         }
    16         try {
    17             InputStream inputStream = encodedResource.getResource().getInputStream();
    18             try {
    19                 InputSource inputSource = new InputSource(inputStream);
    20                 if (encodedResource.getEncoding() != null) {
    21                     inputSource.setEncoding(encodedResource.getEncoding());
    22                 }
    23                 return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    24             }
    25             finally {
    26                 inputStream.close();
    27             }
    28         }

    在这里可以拿到代表XML文件的Resource,读取器在打开I/O流后就可以得到XML的文件对象,接下来的事情就是按照Spring的Bean定义规则对这个XML文件做解析并封装了。而这个解析的过程是在类BeanDefinitionParserDelegate

    在调用doLoadBeanDefinitions过程中,代码会走到类XmlBeanDefinitionReader中的doLoadBeanDefinitions的方法

    1 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
    2             throws BeanDefinitionStoreException {
    3         try {
    4             int validationMode = getValidationModeForResource(resource);
    5             Document doc = this.documentLoader.loadDocument(
    6                     inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
    7             return registerBeanDefinitions(doc, resource);
    8         }

    这里第5行就是Document对象生成的入口,具体实现在DefaultDocumentLoader中。

    第7行就是BeanDefinition解析的入口,我们接着往下走,最终的解析过程在类BeanDefinitionParserDelegate中,源码如下:

     1 public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
     2         String id = ele.getAttribute(ID_ATTRIBUTE);
     3         String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
     4 
     5         List<String> aliases = new ArrayList<String>();
     6         if (StringUtils.hasLength(nameAttr)) {
     7             String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, BEAN_NAME_DELIMITERS);
     8             aliases.addAll(Arrays.asList(nameArr));
     9         }
    10 
    11         String beanName = id;
    12         if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
    13             beanName = aliases.remove(0);
    14             if (logger.isDebugEnabled()) {
    15                 logger.debug("No XML 'id' specified - using '" + beanName +
    16                         "' as bean name and " + aliases + " as aliases");
    17             }
    18         }
    19 
    20         if (containingBean == null) {
    21             checkNameUniqueness(beanName, aliases, ele);
    22         }
    23 
    24         AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    25         if (beanDefinition != null) {
    26             if (!StringUtils.hasText(beanName)) {
    27                 try {
    28                     if (containingBean != null) {
    29                         beanName = BeanDefinitionReaderUtils.generateBeanName(
    30                                 beanDefinition, this.readerContext.getRegistry(), true);
    31                     }
    32                     else {
    33                         beanName = this.readerContext.generateBeanName(beanDefinition);
    34                         // Register an alias for the plain bean class name, if still possible,
    35                         // if the generator returned the class name plus a suffix.
    36                         // This is expected for Spring 1.2/2.0 backwards compatibility.
    37                         String beanClassName = beanDefinition.getBeanClassName();
    38                         if (beanClassName != null &&
    39                                 beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
    40                                 !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
    41                             aliases.add(beanClassName);
    42                         }
    43                     }
    44                     if (logger.isDebugEnabled()) {
    45                         logger.debug("Neither XML 'id' nor 'name' specified - " +
    46                                 "using generated bean name [" + beanName + "]");
    47                     }
    48                 }
    49                 catch (Exception ex) {
    50                     error(ex.getMessage(), ele);
    51                     return null;
    52                 }
    53             }
    54             String[] aliasesArray = StringUtils.toStringArray(aliases);
    55             return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    56         }
    57 
    58         return null;
    59     }

    其中 第24行会触发对Bean元素的详细解析,解析完成后会返回一个AbstractBeanDefinition对象,可以看下这个对象的属性,基本上都是Spring配置文件中的常用属性。

    解析方法的具体内容如下:

     1 public AbstractBeanDefinition parseBeanDefinitionElement(
     2             Element ele, String beanName, BeanDefinition containingBean) {
     3 
     4         this.parseState.push(new BeanEntry(beanName));
     5 
     6         String className = null;
     7         if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
     8             className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
     9         }
    10 
    11         try {
    12             String parent = null;
    13             if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
    14                 parent = ele.getAttribute(PARENT_ATTRIBUTE);
    15             }
    16             AbstractBeanDefinition bd = createBeanDefinition(className, parent);
    17 
    18             parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
    19             bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
    20 
    21             parseMetaElements(ele, bd);
    22             parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
    23             parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
    24 
    25             parseConstructorArgElements(ele, bd);
    26             parsePropertyElements(ele, bd);
    27             parseQualifierElements(ele, bd);
    28 
    29             bd.setResource(this.readerContext.getResource());
    30             bd.setSource(extractSource(ele));
    31 
    32             return bd;
    33         }

    其中第18行-27行,都是对Spring配置文件中不同<Bean>节点的解析,可以每个都深入去研究下。最终返回一个AbstractBeanDefinition对象。

    到这里,XML文件中定义的BeanDefinition就被整个载入到IOC容器中,建立了对应的数据结构,可以看成是POJO在容器中的抽象。但是这个时候容器中还只是有一些静态的配置信息,完成了管理Bean对象的数据准备工作,但是容器还没有完全发挥作用。

    三、BeanDefinition注册过程

    这个源码中方法调用过程比较简单,我们直接来看注册的核心代码:

     1 public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
     2             throws BeanDefinitionStoreException {
     3 
     4         Assert.hasText(beanName, "Bean name must not be empty");
     5         Assert.notNull(beanDefinition, "BeanDefinition must not be null");
     6 
     7         if (beanDefinition instanceof AbstractBeanDefinition) {
     8             try {
     9                 ((AbstractBeanDefinition) beanDefinition).validate();
    10             }
    11             catch (BeanDefinitionValidationException ex) {
    12                 throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
    13                         "Validation of bean definition failed", ex);
    14             }
    15         }
    16 
    17         synchronized (this.beanDefinitionMap) {
    18             Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
    19             if (oldBeanDefinition != null) {
    20                 if (!this.allowBeanDefinitionOverriding) {
    21                     throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
    22                             "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
    23                             "': There is already [" + oldBeanDefinition + "] bound.");
    24                 }
    25                 else {
    26                     if (this.logger.isInfoEnabled()) {
    27                         this.logger.info("Overriding bean definition for bean '" + beanName +
    28                                 "': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
    29                     }
    30                 }
    31             }
    32             else {
    33                 this.beanDefinitionNames.add(beanName);
    34                 this.frozenBeanDefinitionNames = null;
    35             }
    36             this.beanDefinitionMap.put(beanName, beanDefinition);
    37 
    38             resetBeanDefinition(beanName);
    39         }
    40     }

    最关键的一步就是第36行,所谓的注册就是将解析得到的BeanDefinition设置到hashMap中去。至此就完成了整个IOC容器初始化的工作,这样一来就可以在beanDefinition里检索和使用已经配置好的Bean了。ICO容器的作用就是维护和处理里面的Bean。

    最后,我们来回答下这个问题,spring 容器中bean是什么时候被初始化的

    通过前面的分析,我们知道bean的实例化是伴随着容器的初始化而同时进行的(但是也有特例),并且默认情况下是单例的。

  • 相关阅读:
    Redis 笔记
    React Native
    docker run 命令
    vue 集成腾讯地图基础api Demo集合
    在uniapp H5项目中使用腾讯地图sdk
    基于UE4/Unity绘制地图
    腾讯位置服务打车乘客端小车平滑移动-安卓篇
    腾讯位置服务定位打卡功能实现
    腾讯位置服务GPS轨迹回放-安卓篇
    腾讯位置服务个性化图层创建及发布
  • 原文地址:https://www.cnblogs.com/dongguacai/p/6434453.html
Copyright © 2011-2022 走看看