IOC容器初始化之创建BeanFactory时的loadBeanDefinition【XML方式】(二)

BeanDefinitionReader来读取beanDefinition信息,在XML的容器中,通过XMLBeanDefinitionReader来读取beanDefinition的信息。

1. 抽象方法与子类方法的命名规则

抽象类与子类有关方法命名上,子类中具体做抽象方法所定义的行为的方法名前加do。

/**
	 * 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.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 {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				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();
			}
		}
	}

2. 简化方法参数

以ReaderContext为例。在Spring做loadBeanDefinition过程中,会用到比如解析xml的namespace,实际解析XML文件的BeanDefinitionDocumentReader以及其他相关的事件等。这些参数如果不把他们放到一个Context里,在相关方法会造成方法参数繁琐,不易阅读。【简化方法参数,就是为多个参数构造一个对应的Context对象】,在方法的深层次调用中,都可以只传入Context,且用到哪个对象,直接从Context中去取。

/**
	 * 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 {
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		int countBefore = getRegistry().getBeanDefinitionCount();
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}
	/**
	 * Create the {@link XmlReaderContext} to pass over to the document reader.
	 */
	public XmlReaderContext createReaderContext(Resource resource) {
		return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
				this.sourceExtractor, this, getNamespaceHandlerResolver());
	}

	/**
	 * Lazily create a default NamespaceHandlerResolver, if not set before.
	 * @see #createDefaultNamespaceHandlerResolver()
	 */
	public NamespaceHandlerResolver getNamespaceHandlerResolver() {
		if (this.namespaceHandlerResolver == null) {
			this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
		}
		return this.namespaceHandlerResolver;
	}

3. 委托类的命名Delegate

以BeanDefinition读取为例,讲述委托类的产生。
XMLBeanDefinitionReader做loadBeanDefinition时,是将String类型的configLocation转换成EncodedResource的configLocation【将该位置的IOC容器的xml文件解析成Resource】,在其doLoadBeanDefinition中将持有InputStream输入流的InputSource转换成Document文档【XML文件解析成Document文档】,然后注册BeanDefinition【registerBeanDefinitions(…)】。在这里创建对Document的BeanDefinitionReader【BeanDefinitionDocumentReader】,由该对象注册Spring容器中的BeanDefinition信息【documentReader.registerBeanDefinitions(doc, createReaderContext(resource));】。具体解析xml文件中的标签信息由委托类创建,而其本身DefaultBeanDefinitionDocumentReader仅仅是做了对Xml文件定义的标签解析的分类(什么样的标签决定了调用委托类的什么解析方法)
解析方法分为两种:
一、一种是defaultNamespace[解析bean标签,beans,import,alias],parseDefaultElement(ele, delegate),该方法仍是DefaultBeanDefinitionDocumentReader的,在这个方法里,也对这几个标签做了解析分类,只有解析bean的动作交由委托类去完成。
二、另一种是自定义的[context:component-scan等扩展spring功能的namespace],这个就是交由委托类去做具体解析的delegate.parseCustomElement(ele);。
总结:委托类做了具体解析自定义元素的分类【由此产生下述的Handler】,而直接委托的类是做了对解析元素的分类。

4.BeanDefinitionParseDelegate的Handler

自定义解析方法parseCustomElement,相当于一个总纲,该总纲的作用仅仅是定义了自定义解析的行为,具体的自定义标签元素由该标签对应的解析类去解析。也即是,我把自定义解析的行为放开了,但又不能影响解析的流程,又想在不改动解析流程的情况下,让自定义的解析行为生效,就在总纲里继续做流程解析。

 	@Nullable
	public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
		String namespaceUri = getNamespaceURI(ele);
		if (namespaceUri == null) {
			return null;
		}
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
			return null;
		}
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}

【在idea debug调试期间,大家会发现不知道什么时候DefaultNamespaceHandlerResolver中的handlerMappings就被复制。这是因为idea开启了一个线程自动调用了toString方法,toString()中有调用getHandlerMappings()。实际不会,直接在解析标签会被调用。附加现象验证(Spring源码编译下的环境)】
在这里插入图片描述
重写toString方法后的无赋值,在调用resolve后去赋值的:
在这里插入图片描述

5. 在getHandlerMappings()时用到了单例模式的double-check

这里为什么要使用double-check?
首先要明确一点的就是判断对象是否为空跟为空创建对象的这两步操作不是一个原子操作,那么在多线程情况下,使用这个对象就不是线程安全的,if判断那个对象要加volatile修饰。
详述单例模式的double-check参见单例模式的double-check各种情况分析

6. 如何扩展Spring功能自定义namespace

  1. 在扩展应用meta-inf目录下新建spring.handlers和spring.schemas和两个文件,如druid,mybatis,redis等
    在这里插入图片描述
  2. 扩展NamespaceHandlerSupport,注册自定义的解析器并注册到beanFactory中
  3. spring中的标签命名namespace:parser/,以context:component-scan/为例,context ->ContextNamespaceHandler, component-scan对应的是该命名空间下的ComponentBeanDefinitionParser解析器,该命名空间下有多种解析器,如下:
/**
 * {@link org.springframework.beans.factory.xml.NamespaceHandler}
 * for the '{@code context}' namespace.
 *
 * @author Mark Fisher
 * @author Juergen Hoeller
 * @since 2.5
 */
public class ContextNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
		registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
		registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
		registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
	}

}

与上述无关的话,在xml文件配置中,配置了context:component-scan可以不用配置context:annotation-driver,默认这个就是开启的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值