avatar

20.Spring源码深度解析读书笔记(2)

0x00 概览

在这篇博客中,会为大家带来《Spring源码深度解析》一书中的第二章的读书笔记,即“容器的基本实现”

0x01 简单的Spring使用例子

  1. 对于Spring的使用,我们已经熟的不能再烂了。即使用ClassPathXmlApplicationContext加载xml,然后使用getBean()方法获取我们想要的bean,然后进行我们下一步操作。但是,在这里我们使用另外一种获取Bean的方式。即使用XmlBeanFactory.详见以下代码:
    1
    2
    3
    4
    5
    6
    7
    8
    public void test() {
    //加载资源。把我们写好的xml传进去,得到一个Resource
    ClassPathResource resource = new ClassPathResource("testSpring.xml");
    //创建XmlBeanFactory
    XmlBeanFactory factory = new XmlBeanFactory(resource);
    //获取Bean
    factory.getBean("xxx",String.class);
    }
  2. 上面的代码对于我们并不陌生,我们可以通过简单的3行代码,就可以得到我们想要的Bean。看似简单,实际上Spring在背后为我们做了太多事。下面来就一起来初略的看看Spring为我们做了些什么事情吧~

0x02 容器的基础–XmlFactoryBean

  1. 在进入正题前,我们不妨猜测下上面的源码大致干了一些什么事情。

    1. new ClassPathResource("testSpring.xml"):这一步很好理解,无非就是把我们放在ClassPath下面对应的xml文件加载到内存,封装成Resource对象。
    2. new XmlBeanFactory(resource);:这一步也不难,大致想一下,这里把Resource对象传递过去,肯定是得先验证xml文件语法正确与否,然后就是解析xml文件。得到我们写在xml中的<bean>标签,再然后就是根据我们定义的<bean>标签去实例化对象,放在容器中。事实上,Spring就是这样玩的。
    3. factory.getBean("xxx",String.class):这个就更加简单了。从容器中获取bean就不用多说了。
    4. 总结下(时序图),画的不太正规,凑合着看看吧:

      接下来我们一点一点的去分析。
  2. 配置文件的封装

    • 在上面的第一步,是创建一个ClassPathResource对象。也就是把我们的xml读入内存,暴露获取InputStream的方法,供后面使用。我们来一起看看ClassPathResource的层次结构图:
    • 我们都知道,在Java中会将不同来源的资源抽象成URL,通过注册不同的Handler(URLStreamHandler)来处理不同来源的资源的读取逻辑,一般handler的类型使用不同的前缀(协议)来标识。如“file:”、“http:”、“jar:”等,然而URL没有默认定义相对ClassPath或ServletContext等资源的handler,虽然可以注册自己的URLStreamHandler来解析特定的URL前缀,比如"classpath:",然而这需要了解URL的实现机制,而且URL也没有提供一些基本方法,如检查当前资源是否存在、检查当前资源是否可读等方法。因而Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口来封装底层资源。(以上摘自《Spring源码深度解析》)。Resource源码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public interface InputStreamSource {
    InputStream getInputStream() throws IOException;
    }

    public interface Resource extends InputStreamSource {
    boolean exists(); //文件是否存在
    boolean isReadable; //是否可读
    boolean isOpen(); //是否打开
    URL getURL() throws IOException; //获取URL
    URI getURI() throws IOException; //获取URI
    File getFile() throws IOException; //获取文件
    long contentLength() throws IOException;
    long lastModified() throws IOException;
    Resource createRelative(String relativePath) throws IOException;
    String getFilename(); //获取文件名(不带路径)
    String getDescription();
    }
    • 在有了Resource接口后,对于资源文件就可以统一处理。而且其实现也是简单。这里拿ClassPathResourcegetInputStream()方法来说。源码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public InputStream getInputStream() throws IOException {
    InputStream is;
    //如果clazz不为空,就使用class获取资源文件
    if (this.clazz != null) {
    is = this.clazz.getResourceAsStream(this.path);
    }
    //如果classloader不为空,就使用classloader获取资源文件
    else if (this.classLoader != null) {
    is = this.classLoader.getResourceAsStream(this.path);
    }
    else {
    is = ClassLoader.getSystemResourceAsStream(this.path);
    }
    if (is == null) {
    throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
    }
    return is;
    }

    通过源码发现,ClassPathResource就是通过class或者ClassLoader提供的底层方法获取InputStream

    • 对于其他来源的资源文件,Spring都有相应的实现,层次结构图如下:

      对于资源文件的解析就到这结束了。接下来就让我们看看第2步做了些什么吧。
  3. 加载Bean

    1. 第2步是new XmlFactory(resource).那么,我们就从XmlFactory的构造方法开始吧~
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      public XmlBeanFactory(Resource resource) throws BeansException {
      //调用重载的构造方法
      this(resource, null);
      }
      public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
      //执行父类的构造方法
      super(parentBeanFactory);
      //通过resource加载Bean的定义
      this.reader.loadBeanDefinitions(resource);
      }
    2. 我们先看XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory)在执行父类的构造方法中做了什么。
    1
    2
    3
    4
    5
    6
    public AbstractAutowireCapableBeanFactory() {
    super();
    ignoreDependencyInterface(BeanNameAware.class);
    ignoreDependencyInterface(BeanFactoryAware.class);
    ignoreDependencyInterface(BeanClassLoaderAware.class);
    }

    这里得介绍下ignoreDependencyInterface()。该方法的作用是忽略给定接口的自动装配功能。在Spring中,加入加载A类的时候,发现A类中使用B类作为属性,而加载A类的时候发现B类没有加载,那么Spring会自动加载B类。这也是Spring中的一个特性。那么,这关ignoreDependencyInterface()什么事呢?我们在有些时候,不希望B类加载,也就是B类不会初始化。这时候B类就可以实现上面的这些接口,如:BeanNameAware/BeanFactoryAware/BeanClassLoaderAware

    1. 调用父类构造函数我们已经分析完了,接下来就分析下核心的操作this.reader.loadBeanDefinitions(resource),直接上源码吧。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    //调用重载的方法。new EncodedResource():把resource转成带编码格式的resource对象
    return loadBeanDefinitions(new EncodedResource(resource));
    }

    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    //断言resource不为空
    Assert.notNull(encodedResource, "EncodedResource must not be null");
    if (logger.isInfoEnabled()) {
    logger.info("Loading XML bean definitions from " + encodedResource.getResource());
    }
    //获取当前正在加载的XML的bean定义资源
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
    currentResources = new HashSet<EncodedResource>(4);
    this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }
    //如果该Resource已经存在,则抛出异常
    if (!currentResources.add(encodedResource)) {
    throw new BeanDefinitionStoreException(
    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }
    try {
    //获取输入流
    InputStream inputStream = encodedResource.getResource().getInputStream();
    try {
    //构造InputSource(注意,该InputSource不是Spring的类),判断Resource是否有设置encoding,如果有设置,则对相应的InputSource也设置encoding
    InputSource inputSource = new InputSource(inputStream);
    if (encodedResource.getEncoding() != null) {
    inputSource.setEncoding(encodedResource.getEncoding());
    }
    //真正处理加载Bean的方法,返回的是加载bean的个数
    return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    }
    finally {
    inputStream.close();
    }
    }
    catch (IOException ex) {
    throw new BeanDefinitionStoreException(
    "IOException parsing XML document from " + encodedResource.getResource(), ex);
    }
    finally {
    currentResources.remove(encodedResource);
    if (currentResources.isEmpty()) {
    this.resourcesCurrentlyBeingLoaded.remove();
    }
    }
    }

    该方法我们粗略的分析了下。梳理下大致操作。
    - 首先进入loadBeanDefinitions(Resource resource)方法的时候,就开始对resource进行进一步的封装,变成EncodedResource
    - 判断该resource是否已经被处理过。如果被处理过则抛出异常。
    - 获取输入流,构造InputSource(注意该InputSource是Sax解析中的),并设置编码(如果存在的话)
    - 调用真正处理加载Bean的方法doLoadBeanDefinitions

    1. 分析了这么久,还没有到达关键地方,到现在,都还是在准备阶段,准备了这么久。接下来他要干些什么呢?详见doLoadBeanDefinitions()方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
    throws BeanDefinitionStoreException {
    try {
    //获取xml的验证模式
    int validationMode = getValidationModeForResource(resource);
    //加载xml文档。使用委托设计模式,把操作委托给documentLoader
    Document doc = this.documentLoader.loadDocument(
    inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
    //注册Bean
    return registerBeanDefinitions(doc, resource);
    }
    catch (BeanDefinitionStoreException ex) {
    throw ex;
    }
    catch (SAXParseException ex) {
    throw new XmlBeanDefinitionStoreException(resource.getDescription(),
    "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
    }
    catch (SAXException ex) {
    throw new XmlBeanDefinitionStoreException(resource.getDescription(),
    "XML document from " + resource + " is invalid", ex);
    }
    catch (ParserConfigurationException ex) {
    throw new BeanDefinitionStoreException(resource.getDescription(),
    "Parser configuration exception parsing XML from " + resource, ex);
    }
    catch (IOException ex) {
    throw new BeanDefinitionStoreException(resource.getDescription(),
    "IOException parsing XML document from " + resource, ex);
    }
    catch (Throwable ex) {
    throw new BeanDefinitionStoreException(resource.getDescription(),
    "Unexpected exception parsing XML document from " + resource, ex);
    }
    }

    纵观源码,发现除了异常外。该方法就执行了三个操作。即:

    • 获取xml的验证方式
    • 加载xml文档,得到Sax解析的Document对象
    • 注册Bean
      到此,我们越来越接近真相了。嘿嘿(。◕ˇ∀ˇ◕)。这三个操作支撑着整个Spring容器部分的实现基础,尤其是注册Bean信息,逻辑相当复杂。接下来我们一个一个去攻克。一个一个的去分析。

0x03 获取xml的验证方式

  1. Xml的两种验证方式
    1. DTD
      • 全称是Document Type Definition,即文档类型定义,是一种xml约束模式语言。是xml文件的验证机制,属于xml文档的一部分
      • 一个DTD文档包含如下部分:
        • 元素的定义规则
        • 元素间关系的定义规则
        • 元素可使用的属性
        • 可使用的实体或符号规则
      • 使用DTD验证模式需要在xml文件的头部进行声明。以下是Spring3.x的声明
      1
      2
      <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
      "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
    2. XSD
      • 全称是XML Schemas Definition。Xml schemas描述了XML文档的结构。
      • Xml schemas本身就是一个xml文档,符合Xml的语法结构
      • Xml schemas的声明除了要声明命名空间外(xmlns=xxxxxx),还必须指定该名称空间所对应的Xml schemas文档的存储位置。而存储位置又由两部分组成。即:
        • 名称空间的URI
        • 该名称空间所对应的Xml schemas文件位置或URL地址。
        • 整个的文档存储位置声明如下:
  2. 上面粗略的介绍了下xml的两种验证方式,下面就让我们进入正题,看看Spring是怎么得到该xml是哪种验证方式吧。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    protected int getValidationModeForResource(Resource resource) {
    int validationModeToUse = getValidationMode();
    //如果配置了xml验证模式,就使用配置的
    if (validationModeToUse != VALIDATION_AUTO) {
    return validationModeToUse;
    }
    //如果没指定,则使用自动检测
    int detectedMode = detectValidationMode(resource);
    if (detectedMode != VALIDATION_AUTO) {
    return detectedMode;
    }
    // Hmm, we didn't get a clear indication... Let's assume XSD,
    // since apparently no DTD declaration has been found up until
    // detection stopped (before finding the document's root tag).
    //默认为xsd模式
    return VALIDATION_XSD;
    }
    • 总的来说,Spring会去检查是否我们自己手动配置了xml的验证模式,如果没有就会去自动检测。如果再得不到,那就默认返回xsd模式验证。自动检测的代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    protected int detectValidationMode(Resource resource) {
    //如果该资源打开了,就抛出异常。即如果文件打开了,就得等它关闭后再操作
    if (resource.isOpen()) {
    throw new BeanDefinitionStoreException(
    "Passed-in Resource [" + resource + "] contains an open stream: " +
    "cannot determine validation mode automatically. Either pass in a Resource " +
    "that is able to create fresh streams, or explicitly specify the validationMode " +
    "on your XmlBeanDefinitionReader instance.");
    }

    InputStream inputStream;
    try {
    inputStream = resource.getInputStream();
    }
    catch (IOException ex) {
    throw new BeanDefinitionStoreException(
    "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
    "Did you attempt to load directly from a SAX InputSource without specifying the " +
    "validationMode on your XmlBeanDefinitionReader instance?", ex);
    }

    try {
    //委托设计模式
    return this.validationModeDetector.detectValidationMode(inputStream);
    }
    catch (IOException ex) {
    throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
    resource + "]: an error occurred whilst reading from the InputStream.", ex);
    }
    }
    • 这里把检测验证模式的任务委托给了一个专一类。那我们就去看看这个detectValidationMode方法把。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      	public int detectValidationMode(InputStream inputStream) throws IOException {
      // Peek into the file to look for DOCTYPE.
      //通过DOCTYPE来判断是否是DTD约束模式。
      BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
      try {
      boolean isDtdValidated = false;
      String content;
      while ((content = reader.readLine()) != null) {
      //去掉<!-- -->
      content = consumeCommentTokens(content);
      //如果是注释内的或者为空就忽略
      if (this.inComment || !StringUtils.hasText(content)) {
      continue;
      }
      //如果有DOCTYPE就是DTD。否则就是XSD
      if (hasDoctype(content)) {
      isDtdValidated = true;
      break;
      }
      if (hasOpeningTag(content)) {
      // End of meaningful data...
      break;
      }
      }
      return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
      }
      catch (CharConversionException ex) {
      // Choked on some character encoding...
      // Leave the decision up to the caller.
      return VALIDATION_AUTO;
      }
      finally {
      reader.close();
      }
      }
    这个方法的逻辑很简单,说到底,就是检测下xml文档有没有声明<!DOCTYPE>.如果有就是DTD,如果没有就是XSD
  3. xml验证模式得到了,接下来就是加载xml文档,封装成Document对象了。这也是委托给一个专门的类进行实现,代码见下:
    1
    Document doc = this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
    • 这里我们说下loadDocument方法的第二个参数getEntityResolver()是干嘛用的。
      • 我们知道,两种验证方式都会带上该schema的地址。大多数情况下,这个地址都是需要连接网络才能下载下来,然后对xml进行验证。一旦涉及网络,就会有一大堆的问题浮现。例如:传输失败、传输速度慢等。而一旦download schema出现问题,那么验证就会失败,整个项目也会运行失败。于是为了能够快速的定位到schema的位置,如本地。这样做就不会出现上面所述的问题。
      • Sax就是使用EntityResolver接口中的resolveEntity()方法来实现自定义如何寻找DTD的方法,即我们可以自己定义怎么去找DTD。下面就是getEntityResolver()方法的代码:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      protected EntityResolver getEntityResolver() {
      //EntityResolver的作用就是为了能够更好的找到DTD声明。
      //xml在解析的时候,sax解析会先读取该文件的声明,根据声明去找DTD定义,以便对文档进行验证。
      //而默认的寻找规则是通过网络寻找,如果计算机不能上网,那就会GG。所以可以通过EntityResolve定义寻找DTD定义的规则。
      //也就是说,sax在解析的时候,会通过我们定义的EntityResolver寻找到DTD定义,这样就不用去网上download。
      if (this.entityResolver == null) {
      // Determine default EntityResolver to use.
      //获取资源加载器
      ResourceLoader resourceLoader = getResourceLoader();
      if (resourceLoader != null) {
      this.entityResolver = new ResourceEntityResolver(resourceLoader);
      }
      else {
      //Spring默认使用
      this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
      }
      }
      return this.entityResolver;
      }
      • 在Spring中,默认使用的是DelegatingEntityResolverresolveEntity()方法代码如下:
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
        if (systemId != null) {
        //通过systemId判断是DTD约束还是XSD约束,执行不同的方法
        if (systemId.endsWith(DTD_SUFFIX)) {
        return this.dtdResolver.resolveEntity(publicId, systemId);
        }
        else if (systemId.endsWith(XSD_SUFFIX)) {
        return this.schemaResolver.resolveEntity(publicId, systemId);
        }
        }
        return null;
        }
      • 这里具体就不细看了。粗略的说下,如果加载了DTD类型,则是使用BeansDtdResolver直接截取systemId后面的xx.dtd去当前路径下找,而如果加载了XSD类型,则是使用PluggableSchemaResolverMETA-INF/Spring.schemas文件中找到systemId相对应的XSD文件进行加载。有兴趣的可以看下代码。

0x04 解析并注册Bean

  1. 上面把加载xml文档说完了。到现在为止,我们已经拿到了解析后的Document对象。没错,接下来就是重头戏–解析并注册Bean。
  2. 在加载完xml后,随即就会执行return registerBeanDefinitions(doc, resource);,我们看下registerBeanDefinitions()方法执行了什么逻辑。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    //默认创建DefaultBeanDefinitionDocumentReader
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    //设置环境
    documentReader.setEnvironment(getEnvironment());
    //返回注册表中定义的bean的数量。
    int countBefore = getRegistry().getBeanDefinitionCount();
    //从给定的DOM文档中读取bean定义,并在给定的阅读器上下文中向注册中心注册它们。
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    //记录本次注册的BeanDefinition的数量
    return getRegistry().getBeanDefinitionCount() - countBefore;
    }
    这里的doc参数就是上面我们经过重重分析后,loadDocument后得到的Document对象。这个方法里面最重要的步骤是documentReader.registerBeanDefinitions(doc, createReaderContext(resource));,其中documentReader默认创建的是DefaultBeanDefinitionDocumentReader,为什么这样说呢?大家点进去就知道了,这里就不过多描述。我们接下来看registerBeanDefinitions()方法。
  3. registerBeanDefinitions()方法看字面意思就是注册bean的定义。他是怎么玩的呢?里面又有何种套路呢?
    1
    2
    3
    4
    5
    6
    7
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    logger.debug("Loading bean definitions");
    Element root = doc.getDocumentElement();
    //加载bean的定义
    doRegisterBeanDefinitions(root);
    }
    他在这里获取了xml的root,然后继续去加载bean。
  4. 接下来越来越到最核心了。是不是有点兴奋呢?经过这么多分析才到达这。来一起看看doRegisterBeanDefinitions()方法把
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    protected void doRegisterBeanDefinitions(Element root) {
    //从beans中的属性中看是否定义profile。使用profile可以方便的指定哪些配置是开发环境,哪些配置是生产环境
    String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
    //如果定义了profile,则从环境变量中获取当前激活的环境
    if (StringUtils.hasText(profileSpec)) {
    //分隔
    String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
    profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
    //与环境变量配置的active环境进行匹配。如果不一样。则忽略注册bean
    if (!getEnvironment().acceptsProfiles(specifiedProfiles)) {
    return;
    }
    }

    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(this.readerContext, root, parent);
    //解析前处理,这里preProcessXml是一个空方法,等子类去重写它
    preProcessXml(root);
    //解析bean
    parseBeanDefinitions(root, this.delegate);
    //解析后处理,这里postProcessXml是一个空方法,等子类去重写它
    postProcessXml(root);

    this.delegate = parent;
    }
    可以看到,一开始就从bean中看是否配置了profile标签。对于profile标签,我这里就不细说了。处理了profile标签后,下面就真正到了解析我们的bean了。一起再来看看parseBeanDefinitions()方法把
  5. parseBeanDefinitions()源码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    //判断是不是默认的namespace
    if (delegate.isDefaultNamespace(root)) {
    NodeList nl = root.getChildNodes();//获取子节点列表 <bean></bean>节点
    for (int i = 0; i < nl.getLength(); i++) {
    Node node = nl.item(i);
    if (node instanceof Element) {
    Element ele = (Element) node;
    //判断当前节点是否属于默认的namespace http://www.springframework.org/schema/beans
    if (delegate.isDefaultNamespace(ele)) { //是默认的namespace,则使用默认的解析
    parseDefaultElement(ele, delegate);
    }
    else { //否则使用自定的解析
    delegate.parseCustomElement(ele);
    }
    }
    }
    }
    else {
    //自定义命名空间解析
    delegate.parseCustomElement(root);
    }
    }
    逻辑一览无余,首先判断他的namespace,是默认的还是自定义的。如果是默认的namespace,则使用默认的解析器,如果是自定义的,则使用自定义的命名空间解析。至于里面的实现,咱们下一章再聊~~~

0x05 总结

  1. Spring源码深度解析第二章到此就结束了。在这章,我们可以知道简单的3行Spring使用代码,在底层做了些什么事,可谓是经历山路十八弯,最终终于柳暗花明又一村。
  2. 接下来就进入Spring源码深度解析第三章的阅读了。下一篇文章可能会有点晚。
  3. 本人才疏学浅,文章中难免会有不当或错误之处,望大家不吝指出。谢谢大家。

评论