乐趣区

Spring源码1容器的基本实现

背景

使用了很久 Spring,但对 Spring 的内在实现并不了解,正好最近有些时间,研究下 Spring 的源码

首先写一个测试方法

        BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beans.xml"));
        User userTest = (User) bf.getBean("testBean");
        System.out.println(userTest.getEmail());

和对应的配置文件 beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:myName="http://www.wjs.com/schema/user"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.wjs.com/schema/user http://www.wjs.com/schema/user.xsd">

            <bean id = "testBean" class="com.zero.pojo.User">
                <property name="userName" value="zhang"></property>
                <property name="email" value = "18"></property>

            </bean>
</beans>

以上代码的功能使用过 Spring 的都能猜出来

  • 读取配置文件 beans.xml
  • 根据 beans.xml 中的配置找到对应的类,并进行实例化
  • 调用实例化后的类

现在开始调试代码

1.1 初始化 ClassPathResource

new ClassPathResource(“beans.xml”)

进入代码时首先执行了new ClassPathResource(“beans.xml”),在这里加载了配置文件路径到 ClassPathResource 的 path 属性中。

    public ClassPathResource(String path) {this(path, (ClassLoader)null);
    }

    public ClassPathResource(String path, @Nullable ClassLoader classLoader) {Assert.notNull(path, "Path must not be null");
        String pathToUse = StringUtils.cleanPath(path);
        if (pathToUse.startsWith("/")) {pathToUse = pathToUse.substring(1);
        }

        this.path = pathToUse;
        this.classLoader = classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader();}

1.2 初始化 XmlBeanFactory

new XmlBeanFactory(new ClassPathResource(“beans.xml”))

初始化 ClassPathResource 后,开始初始化 XmlBeanFactory,在构造函数内部再调用构造函数,this.reader.loadBeanDefinitions(resource);这个才是资源加载的真正实现

    public XmlBeanFactory(Resource resource) throws BeansException {// 调用 XmlBeanFactory(Resource, BeanFactory)构造方法
        this(resource, (BeanFactory)null);
    }

    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {super(parentBeanFactory);
        this.reader = new XmlBeanDefinitionReader(this);
        this.reader.loadBeanDefinitions(resource);
    }

1.2.1 调用 AbstractAutowireCapableBeanFactory 的构造方法

super(parentBeanFactory);

不过在此之前还有一个 super(parentBeanFactory); 它会一直调用到 AbstractAutowireCapableBeanFactory 的构造方法中,这里的 ignoreDependencyInterface 的主要功能是忽略给定接口的自动装配功能,当有忽略的接口类,自动装配会忽略这部分类的初始化装配,因为某种情况下,此时的接口实现类不能初始化

    public AbstractAutowireCapableBeanFactory() {this.instantiationStrategy = new CglibSubclassingInstantiationStrategy();
        this.parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
        this.allowCircularReferences = true;
        this.allowRawInjectionDespiteWrapping = false;
        this.ignoredDependencyTypes = new HashSet();
        this.ignoredDependencyInterfaces = new HashSet();
        this.currentlyCreatedBean = new NamedThreadLocal("Currently created bean");
        this.factoryBeanInstanceCache = new ConcurrentHashMap();
        this.factoryMethodCandidateCache = new ConcurrentHashMap();
        this.filteredPropertyDescriptorsCache = new ConcurrentHashMap();
        this.ignoreDependencyInterface(BeanNameAware.class);
        this.ignoreDependencyInterface(BeanFactoryAware.class);
        this.ignoreDependencyInterface(BeanClassLoaderAware.class);
    }

1.2.2 设置变量 reader

this.reader = new XmlBeanDefinitionReader(this);

执行完父类的构造方法后,开始设置变量this.reader = new XmlBeanDefinitionReader(this);

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        // 执行 AbstractBeanDefinitionReader 构造方法
        super(registry);
        this.errorHandler = new SimpleSaxErrorHandler(this.logger);
        this.validationModeDetector = new XmlValidationModeDetector();
        this.resourcesCurrentlyBeingLoaded = new NamedThreadLocal("XML bean definition resources currently being loaded");
    }
    
    // 设置 resourceLoader 和 environment
    protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        this.registry = registry;
        // 设置 resourceLoader
        if (this.registry instanceof ResourceLoader) {this.resourceLoader = (ResourceLoader)this.registry;
        } else {this.resourceLoader = new PathMatchingResourcePatternResolver();
        }
        // 设置 environment
        if (this.registry instanceof EnvironmentCapable) {this.environment = ((EnvironmentCapable)this.registry).getEnvironment();} else {this.environment = new StandardEnvironment();
        }

    }

1.2.3 资源加载

this.reader.loadBeanDefinitions(resource);

设置完变量后开始执行 this.reader.loadBeanDefinitions(resource) 进行资源加载

方法 pkg:org.springframework.beans.factory.xml

  • 封装资源文件,当进入 XmlBeanDefinitionReader 后首先对参数 Resource 使用 EncodedResource 类进行封装
  • 获取输入流,从 Resource 中获取对应 InputStream 并构造 InputSource
  • 通过构造的 InputSource 实例和 Resource 实例继续调用函数 doLoadBeanDefinitions

loadBeanDefinitions 具体实现过程

构造好 encodedResource 对象后,再次转入 loadBeanDefinitions,这个方法内部才是真正的数据准备阶段
对传入的 resource 参数做封装,目的是考虑到 Resource 可能存在编码要求的情况,其次,通过 SAX 读取 XML 文件的方式来准备 InputSource 对象,最后将准备的数据通过参数传入真正的核心处理部分this.doLoadBeanDefinitions(inputSource, encodedResource.getResource())

    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {return this.loadBeanDefinitions(new EncodedResource(resource));
    }

    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());
        }

        // 通过属性记录已加载的资源
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {currentResources = new HashSet<>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException("Detected cyclic loading of" + encodedResource + "- check your import definitions!");
        }
        try {
             // 从 encodedResource 中获取已经封装的 Resource 对象并再次从 Resource 中获取其中的 inputStream
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                  //InputSource 这个类不来自 Spring,全路径是 org.xml.sax.InputSource
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {inputSource.setEncoding(encodedResource.getEncoding());
                }
                // 核心方法
                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();
            }
        }
    }

1.3 加载 XML 文件根据得到的 Document 信息注册 Bean

return doLoadBeanDefinitions(inputSource, encodedResource.getResource());

doLoadBeanDefinitions

  • 加载 XML 文件,并得到对应的 Document Document doc = doLoadDocument(inputSource, resource);
  • 根据返回的 Document 注册 Bean 信息 return registerBeanDefinitions(doc, resource)

    这些步骤支撑整个 Spring 容器部分的实现

    /**
     * Actually load bean definitions from the specified XML file.
     * @param inputSource the SAX InputSource to read from
     * @param resource the resource descriptor for the XML file
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     * @see #doLoadDocument
     * @see #registerBeanDefinitions
     */
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {Document doc = doLoadDocument(inputSource, resource);
            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);
        }
    }

doLoadDocument

  • 获取 XML 文件的验证模式 DTD(文档类型定义)还是 XSD (XML Schema 语言) this.getValidationModeForResource(resource)
  • 加载 XML 文件,得到对应 Document

        protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {return this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware());
        }

1.3.1 获取资源的验证模式

this.getValidationModeForResource(resource)

    /**
     * Gets the validation mode for the specified {@link Resource}. If no explicit
     * validation mode has been configured then the validation mode is
     * {@link #detectValidationMode detected}.
     * <p>Override this method if you would like full control over the validation
     * mode, even when something other than {@link #VALIDATION_AUTO} was set.
     */
    protected int getValidationModeForResource(Resource resource) {int validationModeToUse = getValidationMode();
        // 如果手动指定了使用手动指定的验证模式类型的
        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).
        return VALIDATION_XSD;
    }

1.3.2 自动判断资源认证模式

自动判断实际就是获取资源的输入流,判断是否包含 DOCTPE

​ DTD 模式 XML 文件带有 <!DOCTYPE beans PU…. 而 XSD 不带

int detectedMode = detectValidationMode(resource);

    /**
     * Detect the validation mode for the XML document in the supplied {@link InputStream}.
     * Note that the supplied {@link InputStream} is closed by this method before returning.
     * @param inputStream the InputStream to parse
     * @throws IOException in case of I/O failure
     * @see #VALIDATION_DTD
     * @see #VALIDATION_XSD
     */
    public int detectValidationMode(InputStream inputStream) throws IOException {
        // Peek into the file to look for DOCTYPE.
        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;}
                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();
        }
    }

hasDoctype(content)

    /**
     * Does the content contain the DTD DOCTYPE declaration?
     */
    private boolean hasDoctype(String content) {return content.contains(DOCTYPE);
    }

1.4 加载 Document

  • 加载 Document

this.documentLoader.loadDocument(inputSource, this.getEntityResolver(), this.errorHandler, this.getValidationModeForResource(resource), this.isNamespaceAware());

    /**
     * Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
     * XML parser.
     */
    @Override
    public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
            ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
        if (logger.isDebugEnabled()) {logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
        }
        DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
        return builder.parse(inputSource);
    }

创建 DocumentBuilderFactory,再通过 DocumentBuilderFactory 创建 DocumentBuilder,进而解析 inputSource 来返回 Document

1.5 注册 Bean 信息

  • 根据返回的 Document 注册 Bean 信息

在将文件转换为 Document 后,接下来就是提取和注册 bean,当程序已经有了 XML 文件的 Document 实例和上下文信息后

int count = this.registerBeanDefinitions(doc, resource);

  /**
   * Register the bean definitions contained in the given DOM document.
   * Called by {@code loadBeanDefinitions}.
   * <p>Creates a new instance of the parser class and invokes
   * {@code registerBeanDefinitions} on it.
   * @param doc the DOM document
   * @param resource the resource descriptor (for context information)
   * @return the number of bean definitions found
   * @throws BeanDefinitionStoreException in case of parsing errors
   * @see #loadBeanDefinitions
   * @see #setDocumentReaderClass
   * @see BeanDefinitionDocumentReader#registerBeanDefinitions
   */
  public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
      // 使用 DefaultBeanDefinitionDocumentReader 实例化 BeanDefinitionDocumentReader
      BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
      // 记录统计前 BeanDefinition 的加载个数
      int countBefore = getRegistry().getBeanDefinitionCount();
      // 加载及注册 bean
      documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
      // 记录本次加载的 BeanDefinition 个数
      return getRegistry().getBeanDefinitionCount() - countBefore;
  }

1.5.1 实例化 createBeanDefinitionDocumentReader

createBeanDefinitionDocumentReader

BeanDefinitionDocumentReader 是一个接口,实例化工作是在 createBeanDefinitionDocumentReader 中执行,通过此方法,BeanDefinitionDocumentReader 已经转变成了 DefaultBeanDefinitionDocumentReader

createBeanDefinitionDocumentReader

    private Class<? extends BeanDefinitionDocumentReader> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;
        
    /**
     * Create the {@link BeanDefinitionDocumentReader} to use for actually
     * reading bean definitions from an XML document.
     * <p>The default implementation instantiates the specified "documentReaderClass".
     * @see #setDocumentReaderClass
     */
    protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
    }
    public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {Assert.notNull(clazz, "Class must not be null");
        if (clazz.isInterface()) {throw new BeanInstantiationException(clazz, "Specified class is an interface");
        } else {
            try {return instantiateClass(clazz.getDeclaredConstructor());
            } catch (NoSuchMethodException var3) {Constructor<T> ctor = findPrimaryConstructor(clazz);
                if (ctor != null) {return instantiateClass(ctor);
                } else {throw new BeanInstantiationException(clazz, "No default constructor found", var3);
                }
            } catch (LinkageError var4) {throw new BeanInstantiationException(clazz, "Unresolvable class definition", var4);
            }
        }
    }

1.5.2BeanDefinition 的注册

进入 DefaultBeanDefinitionDocumentReader 后,开始提取 root,并将提取到的 root 作为参数进行 BeanDefinition 的注册

documentReader.registerBeanDefinitions(doc, createReaderContext(resource));

    /**
     * This implementation parses bean definitions according to the "spring-beans" XSD
     * (or DTD, historically).
     * <p>Opens a DOM Document; then initializes the default settings
     * specified at the {@code <beans/>} level; then parses the contained bean definitions.
     */
    @Override
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();
        doRegisterBeanDefinitions(root);
    }

​ doRegisterBeanDefinitions(root);

这里就是逻辑的核心

​ doRegisterBeanDefinitions(root);

     /**
     * Register each bean definition within the given root {@code <beans/>} element.
     * 在给定的根 {@code <beans />} 元素中注册每个 bean 定义
     */
    protected void doRegisterBeanDefinitions(Element root) {
        // Any nested <beans> elements will cause recursion in this method. In
        // order to propagate and preserve <beans> default-* attributes correctly,
        // keep track of the current (parent) delegate, which may be null. Create
        // the new (child) delegate with a reference to the parent for fallback purposes,
        // then ultimately reset this.delegate back to its original (parent) reference.
        // this behavior emulates a stack of delegates without actually necessitating one.
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(getReaderContext(), root, parent);

        if (this.delegate.isDefaultNamespace(root)) {
            // 处理 profile 属性,使用 profile 来读取不同的配置文件,切换使用不同的环境(DEV/PRD)String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {if (logger.isInfoEnabled()) {
                        logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                "] not matching:" + getReaderContext().getResource());
                    }
                    return;
                }
            }
        }
        // 解析前处理,留给子类实现, 模板方法模式,如果继承自 DefaultBeanDefinitionDocumentReader 的子类需要在 Bean 解析前后做操作,只需要重写这两个类就行了
        preProcessXml(root);
        parseBeanDefinitions(root, this.delegate);
        // 解析后处理,留给子类实现
        postProcessXml(root);

        this.delegate = parent;
    }

1.5.3 解析 profile 属性

处理 profile 属性,使用 profile 来读取不同的配置文件,切换使用不同的环境(DEV/PRD)

root.getAttribute(PROFILE_ATTRIBUTE);

String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);

1.5.4 解析并注册 BeanDefinition

parseBeanDefinitions(root, this.delegate);

处理掉 profile 后,就可以开始进行 XML 的读取了,Spring 的 XML 配置文件中有两大类 Bean 声明,默认命名空间和自定义命名空间,接下来就是判断命名空间类型并进行相应解析

  • 默认命名空间
            <bean id = "testBean" class="com.zero.pojo.User">
                <property name="userName" value="zhang"></property>
                <property name="email" value = "18"></property>
            </bean>
  • 自定义命名空间
<myName:user id="testBean" userName="name" email="A"/>

parseBeanDefinitions(root, this.delegate);

    /**
     * Parse the elements at the root level in the document:
     * "import", "alias", "bean".
     * @param root the DOM root element of the document
     */
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {if (delegate.isDefaultNamespace(root)) {NodeList nl = root.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);
                if (node instanceof Element) {Element ele = (Element) node;
                      // 对 bean 的处理
                      // 默认命名空间
                    if (delegate.isDefaultNamespace(ele)) {parseDefaultElement(ele, delegate);
                    }
                      // 自定义命名空间
                    else {delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {delegate.parseCustomElement(root);
        }
    }
退出移动版