Spring源码分析--Ioc容器定位解析资源文件并注册BeanDefinition

来源:互联网 发布:钟恩淇的淘宝店怎么样 编辑:程序博客网 时间:2024/05/16 10:13

一、资源文件定位和解析流程

加载和解析资源文件是在ApplicationContext子类调用refresh()方法时执行的,整个过程就是将资源文件读入到内存中并且解析成Spring Bean对应的数据结构(BeanDefinition)。以ClassPathXmlApplicatinContext为例整个调用流程如下图:


二、解析流程详细说明

1AbstractApplicationContext调用refresh()方法(前面已经有对refresh方法的分析),方法内通过

ConfigurableListableBeanFactorybeanFactory =obtainFreshBeanFactory();方法进入到BeanFactory的刷新和创建。

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {//这个方法会进入到loadBeanDefinitions的调用refreshBeanFactory();ConfigurableListableBeanFactory beanFactory = getBeanFactory();if (logger.isDebugEnabled()) {logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);}return beanFactory;}

2AbstractApplicationContext子类实现refreshBeanFactory()方法。如AbstractRefreshableApplicationContext类的实现:

protected final void refreshBeanFactory() throws BeansException {if (hasBeanFactory()) {destroyBeans();closeBeanFactory();}try {//创建BeanFactoryDefaultListableBeanFactory beanFactory = createBeanFactory();beanFactory.setSerializationId(getId());//个性BeanFactorycustomizeBeanFactory(beanFactory);//这次分析的正主来了,加载BeanDefinitionsloadBeanDefinitions(beanFactory);synchronized (this.beanFactoryMonitor) {this.beanFactory = beanFactory;}}catch (IOException ex) {throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);}}

3AbstractApplicationContext调用loadBeanDefinitions(DefaultListableBeanFactory beanFactory) ,此方法根据首先创建XmlBeanDefinitionReader对象,然后配置该对象的上下文和资源加载环境,同时调用子类实现的initBeanDefinitionReaderXmlBeanDefinitionReader进行个性化配置,最近后入到initBeanDefinitionReader(beanDefinitionReader)的调用

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {//根据给定的BeanFactory创建XmlBeanDefinitionReader 对象XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);// 配置beanDefinitionReader的上下文和资源加载环境beanDefinitionReader.setEnvironment(this.getEnvironment());beanDefinitionReader.setResourceLoader(this);beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));// 调用子类实现的initBeanDefinitionReader对XmlBeanDefinitionReader进行个性化配置initBeanDefinitionReader(beanDefinitionReader);//调用载入Bean定义的方法,在当前类中只定义了抽象的loadBeanDefinitions方法,具体的实现调用子类容器loadBeanDefinitions(beanDefinitionReader);}

4AbstractApplicationContext调用loadBeanDefinitions(beanDefinitionReader),这个方法是取得资源或资源路径然后通过传入的reader去加载BeanDefinitions

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {                     //资源取得 Resource[] configResources = getConfigResources();if (configResources != null) {reader.loadBeanDefinitions(configResources);}//资源取得String[] configLocations = getConfigLocations();if (configLocations != null) {reader.loadBeanDefinitions(configLocations);}}

5BeanDefinitionReaderloadBeanDefinitions()方法调用,BeanDefinitionReader子类重载了loadBeanDefinitions()方法,其最终的目的就是通过Resource或者Location读取输入流转换成xml Document进行解析。


6BeanDefinitionReader读取到输入流后就要开始解析了,看看XmlBeanDefinitionReader.doLoadBeanDefinitions()都干了些啥

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {//文件解析成DocDocument doc = doLoadDocument(inputSource, resource);//启动资源解析的详细过程return registerBeanDefinitions(doc, resource);}……//省略的都是异常捕获了}

7XmlBeanDefinitionReader.registerBeanDefinitions()代码可以看出,XmlBeanDefinitionsReader说我把Xml解析成Doc了,Doc解析我就委托给BeanDefinitionDocumentReader来进行解析了。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();documentReader.setEnvironment(getEnvironment());int countBefore = getRegistry().getBeanDefinitionCount();documentReader.registerBeanDefinitions(doc, createReaderContext(resource));return getRegistry().getBeanDefinitionCount() - countBefore;}

8BeanDefinitionDocumentReader.registerBeanDefinitions(doc, createReaderContext(resource));一看DocReader竟然方法和XmlReader方法一样,其实这是委派模式的一种体现(Spring Ioc容器资源文件解析和BeanDefinition加载是到处可见委派模式的使用啊),其实真正做事的DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(Element root)方法

protected void doRegisterBeanDefinitions(Element root) {String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);if (StringUtils.hasText(profileSpec)) {String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);if (!getEnvironment().acceptsProfiles(specifiedProfiles)) {return;}}//创建委派类,DefaultBeanDefinitionDocumentReader表示他只能解析Spring Beans 模块xsd文件说明的元素,要解析其他的还得委派给其他对象BeanDefinitionParserDelegate parent = this.delegate;this.delegate = createDelegate(this.readerContext, root, parent);//留个子类实现的方法,个性化用preProcessXml(root);//解析资源parseBeanDefinitions(root, this.delegate);postProcessXml(root);this.delegate = parent;}

9parseBeanDefinitions(Elementroot, BeanDefinitionParserDelegate delegate)实现具体的解析,到这就是真刀真枪要把一个个Doc元素给解析成BeanDefinition

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;if (delegate.isDefaultNamespace(ele)) {parseDefaultElement(ele, delegate);}else {delegate.parseCustomElement(ele);}}}}else {delegate.parseCustomElement(root);}}

三、解析具体实现

1BeanDefinition解析流程已经到了解析每个元素的时候,元素解析首先是判断命名空间,如果是默认命名空间的元素(即:http://www.springframework.org/schema/beans)则直接解析,默认命名空间能解析的是Beanimportaliasbeans代码如下:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {importBeanDefinitionResource(ele);}else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {processAliasRegistration(ele);}else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {processBeanDefinition(ele, delegate);}else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {// recursedoRegisterBeanDefinitions(ele);}}

2、默认命名空间只能解析默认的元素,那么如果配置了事务,配置了注解扫描这些该如何处理,平时用Spring这些照样是得加载出来的,BeanDefinitionParserDelegate.parseCustomElement()就是来干这些的

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {//取得命名空间String namespaceUri = getNamespaceURI(ele);//取得命名空间对应的handlerNamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}//handler解析对应命名空间的元素,让handler飞吧return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));}

handler我用context对应的命名空间(http://www.springframework.org/schema/context)来分析,其他的肯定原理是一样的,只是内部做的具体事不一样。对应的handlerorg.springframework.context.config.ContextNamespaceHandler

3ContextNamespaceHandler.parse()方法,原来它也是找对应的Parser帮手来解析

public BeanDefinition parse(Element element, ParserContext parserContext) {//找到对应的Parser然后解析return findParserForElement(element, parserContext).parse(element, parserContext);}private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {String localName = parserContext.getDelegate().getLocalName(element);//这个命名空间可以看看都用那些解析器BeanDefinitionParser parser = this.parsers.get(localName);if (parser == null) {parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element);}return parser;}

context的解析器是这些,

{property-override=org.springframework.context.config.PropertyOverrideBeanDefinitionParser,//属性重载

 annotation-config=org.springframework.context.annotation.AnnotationConfigBeanDefinitionParser,//注解解析

 mbean-server=org.springframework.context.config.MBeanServerBeanDefinitionParser,//元数据解析

 component-scan=org.springframework.context.annotation.ComponentScanBeanDefinitionParser,//组建扫描

 load-time-weaver=org.springframework.context.config.LoadTimeWeaverBeanDefinitionParser,

 property-placeholder=org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser,

 spring-configured=org.springframework.context.config.SpringConfiguredBeanDefinitionParser,

 mbean-export=org.springframework.context.config.MBeanExportBeanDefinitionParser}

这里看一个通常配置比较多的component-scan上面所有的都在Spring Beans模块下,AnnotationConfigBeanDefinitionParser就进入了Spring context模块了,AnnotationConfigBeanDefinitionParser就是context模块对应的Annotation包底下支持注解解析的类,它的parse()方法的代码如下:

public BeanDefinition parse(Element element, ParserContext parserContext) {//找到要扫描的包String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);// 真正进行bean扫描并且注册beanDefinitionsClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);registerComponents(parserContext.getReaderContext(), beanDefinitions, element);//返回是空,这个可能是Spring Bean设计的遗留问题导致的,Spring Bean解析一个元素的方法都是返回单个BeanDefinition ,扫描是能够扫描到多个的(具体看配置),但这除了使得AbstractBeanDefinitionReader. loadBeanDefinitions(String... locations)的返回值跟真实解析到的BeanDefinition个数不一致外没其他影响。BeanDefinition 在调用registerComponents()方法就注册到传到的改方法的上下文了return null;}






0 0