背景

使用了很久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);        }    }