Spring加载BeanDefinition过程详解(一)

547 阅读3分钟

今天我们继续Spring源码的学习,上一篇文章分析了 BeanDefinitionReader 的创建过程,本篇文章分析 BeanDefinitionReader 加载BeanDefinition过程。

/**
 * Load bean definitions from the specified XML file.
 * @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
 */
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(new EncodedResource(resource));
}

考虑到Resource的读取可能存在编码要求,首先将Resource封装成EncodedResource,同样的EncodedResource 也实现了InputStreamSource接口。

EncodedResource 封装指定编码的Resource,EncodedResource 内部维护了 Resource 的引用,将 Resource 和编码封装在一起。

public class EncodedResource implements InputStreamSource {

	private final Resource resource;

	@Nullable
	private final String encoding;

	@Nullable
	private final Charset charset;
    
    /**
	 * Create a new {@code EncodedResource} for the given {@code Resource},
	 * not specifying an explicit encoding or {@code Charset}.
	 * @param resource the {@code Resource} to hold (never {@code null})
	 */
	public EncodedResource(Resource resource) {
		this(resource, null, null);
	}
    
    private EncodedResource(Resource resource, @Nullable String encoding, @Nullable Charset charset) {
		super();
		Assert.notNull(resource, "Resource must not be null");
		this.resource = resource;
		this.encoding = encoding;
		this.charset = charset;
	}
    
    /**
	 * Open a {@code java.io.Reader} for the specified resource, using the specified
	 * {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding}
	 * (if any).
	 * @throws IOException if opening the Reader failed
	 * @see #requiresReader()
	 * @see #getInputStream()
	 */
	public Reader getReader() throws IOException {
		if (this.charset != null) {
			return new InputStreamReader(this.resource.getInputStream(), this.charset);
		}
		else if (this.encoding != null) {
			return new InputStreamReader(this.resource.getInputStream(), this.encoding);
		}
		else {
			return new InputStreamReader(this.resource.getInputStream());
		}
	}
    
    /**
	 * Open an {@code InputStream} for the specified resource, ignoring any specified
	 * {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding}.
	 * @throws IOException if opening the InputStream failed
	 * @see #requiresReader()
	 * @see #getReader()
	 */
	@Override
	public InputStream getInputStream() throws IOException {
		return this.resource.getInputStream();
	}
    
    /**
	 * Return the encoding to use for reading from the {@linkplain #getResource() resource},
	 * or {@code null} if none specified.
	 */
	@Nullable
	public final String getEncoding() {
		return this.encoding;
	}
    
    // 其他方法,此处省略
}    

loadBeanDefinitions 内部调用了重载方法 loadBeanDefinitions(EncodedResource encodedResource)

/**
 * Load bean definitions from the specified XML file.
 * @param encodedResource the resource descriptor for the XML file,
 * allowing to specify an encoding to use for parsing the file
 * @return the number of bean definitions found
 * @throws BeanDefinitionStoreException in case of loading or parsing errors
 */
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    Assert.notNull(encodedResource, "EncodedResource must not be null");
    if (logger.isTraceEnabled()) {
        logger.trace("Loading XML bean definitions from " + encodedResource);
    }
	// 判断资源文件是否循环加载.
    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 {
        // 从Resource 中获取输入流InputStream.
        InputStream inputStream = encodedResource.getResource().getInputStream();
        try {
            // 将Resource中的InputStream封装成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();
        }
    }
}

InputStream 类是JDK提供的位于org.xml.sax包下,Spring采用SAX解析XML配置文件。

loadBeanDefinitions 方法内部有个值得关注的地方,Spring是如何判断资源文件循环加载的(通过import标签导入自身)呢?

Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

resourcesCurrentlyBeingLoaded 成员变量是ThreadLocal的子类NamedThreadLocal,内部通过 HashSet 判断资源文件的循环加载。(考点,HashSet 底层是 HashMap实现的,EncodedResource 内部重写了equals 和 hashCode 方法)。使用ThreadLocal最后一定要remove,避免内存泄漏(zhuanlan.zhihu.com/p/354153342…

InputStream inputStream = encodedResource.getResource().getInputStream(); 

这行代码解释了【Spring源码】ClassPathResource 资源文件源码分析 最后留下的问题,ClassPathResource 类的 getInputStream() 方法在什么地方被执行的呢?

继续跟踪代码,实际执行从指定的XML文件加载BeanDefinition的方法。

/**
 * 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 {
        // 将XML文件解析成Document对象
        Document doc = doLoadDocument(inputSource, resource);
        // 注册BeanDefinition
        int count = registerBeanDefinitions(doc, resource);
        if (logger.isDebugEnabled()) {
            logger.debug("Loaded " + count + " bean definitions from " + resource);
        }
        return count;
    }
    // 省略异常捕获部分代码
}

通过BeanDefinitionDocumentReader 注册BeanDefinition。

/**
 * 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 documentReader = createBeanDefinitionDocumentReader();
    // 获取之前容器中的BeanDefinition数量
    int countBefore = getRegistry().getBeanDefinitionCount();
    // BeanDefinitionDocumentReader 负责实际的Bean注册
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // 返回新增的BeanDefinition数量
    return getRegistry().getBeanDefinitionCount() - countBefore;
}


/**
 * 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 BeanUtils.instantiateClass(this.documentReaderClass);
}

getRegistry().getBeanDefinitionCount();

BeanDefinitionRegistry 的实现类 DefaultListableBeanFactory 中的成员变量 Map<String, BeanDefinition> beanDefinitionMap 中获取BeanDefinition的个数。

九转十八弯,流程最后转入BeanDefinitionDocumentReader 接口的实现类DefaultBeanDefinitionDocumentReader 中。

/**
 * 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;
    doRegisterBeanDefinitions(doc.getDocumentElement());
}

BeanDefinitionDocumentReader 内部实现我们下篇再分析,敬请期待。

更多精彩内容请关注公众号 geekymv,喜欢请分享给更多的朋友哦」