唐宋源码清 2019-12-29
相关背景及资源:
曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享
曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解
曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下
曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?
曹工说Spring Boot源码(5)-- 怎么从properties文件读取bean
工程结构图:
这次,我们打算讲一下,spring启动时,是怎么去读取xml文件的,bean的解析部分可能暂时涉及不到,因为放到一起,内容就太多了,具体再看吧。
给ClassPathXmlApplicationContext
设置xml文件的路径
refresh
内部的beanFactory
,其实这时候beanFactory
都还没创建,会先创DefaultListableBeanFactory
ClassPathXmlApplicationContext
调用其loadBeanDefinitions
,将新建DefaultListableBeanFactory
作为参数传入
ClassPathXmlApplicationContext
内部会持有一个XmlBeanDefinitionReader
,且XmlBeanDefinitionReader
内部是持有之前创建的DefaultListableBeanFactory
的,这时候就简单了,XmlBeanDefinitionReader
负责读取xml,将bean definition
解析出来,丢给DefaultListableBeanFactory
,此时,XmlBeanDefinitionReader
就算完成了,退出历史舞台
上面第四步完成后,DefaultListableBeanFactory
里面其实一个业务bean都没有,只有一堆的 bean definition
,后面ClassPathXmlApplicationContext
直接去实例化那些需要在启动过程中实例化的bean。
当前,这中间还涉及到使用beanDefinitionPostProcessor
和beanPostProcessor
对beanFactory进行处理,这都是后话了。
这次,我们主要讲的部分是,第4步。
位置:org.springframework.context.support.AbstractRefreshableApplicationContext @Override protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { //创建一个DefaultListableBeanFactory DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); // 加载 loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
上面调用了
AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) { // 创建一个从xml读取beanDefinition的读取器 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // 配置环境 beanDefinitionReader.setEnvironment(this.getEnvironment()); // 配置资源loader,一般就是classpathx下去获取xml beanDefinitionReader.setResourceLoader(this); // xml解析的解析器 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. initBeanDefinitionReader(beanDefinitionReader); // 核心方法,使用beanDefinitionReader去解析xml,并将解析后的bean definition放到beanFactory loadBeanDefinitions(beanDefinitionReader); }
// xml解析的解析器 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
这个类实现的接口就是jdk里面的org.xml.sax.EntityResolver
,这个接口,只有一个方法,主要负责xml里,外部实体的解析:
public interface EntityResolver { public abstract InputSource resolveEntity (String publicId, String systemId) throws SAXException, IOException; }
大家可能不太明白,我们看看怎么实现的吧:
public class DelegatingEntityResolver implements EntityResolver { /** Suffix for DTD files */ public static final String DTD_SUFFIX = ".dtd"; /** Suffix for schema definition files */ public static final String XSD_SUFFIX = ".xsd"; private final EntityResolver dtdResolver; private final EntityResolver schemaResolver; public DelegatingEntityResolver(ClassLoader classLoader) { this.dtdResolver = new BeansDtdResolver(); this.schemaResolver = new PluggableSchemaResolver(classLoader); } // 主要看这里,感觉就是对我们xml里面的那堆xsd进行解析 @override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { if (systemId != null) { if (systemId.endsWith(DTD_SUFFIX)) { return this.dtdResolver.resolveEntity(publicId, systemId); } else if (systemId.endsWith(XSD_SUFFIX)) { return this.schemaResolver.resolveEntity(publicId, systemId); } } return null; }
举个例子,我们xml里一般不是有如下代码吗:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 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">
上面的代码,应该就是去获取和解析上面这里的xsd
,方便进行语法校验的。毕竟,这个xml我们也不能随便乱写吧,比如,根元素就是,有且 只能有一个,下面才能有0到多个之类的元素。你要是写了多个根元素,肯定不合规范啊。
接下来,我们还是赶紧切入正题吧,看看XmlBeanDefinitionReader
是怎么解析xml的。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); //这个方法还在:AbstractXmlApplicationContext,获取资源位置,传给 XmlBeanDefinitionReader if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }
经过几个简单跳转,进入下面的方法:
org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(java.lang.String, java.util.Set<org.springframework.core.io.Resource>) public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader instanceof ResourcePatternResolver) { try { Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); // 从资源数组里load bean definition int loadCount = loadBeanDefinitions(resources); return loadCount; } } }
这里插一句,看看其类图:
总的来说,类似于模板设计模式,一些通用的逻辑和流程,放在AbstractBeanDefinitionReader
,具体的解析啊,都是放在子类实现。
我们这里也可以看到,其实现了一个接口,BeanDefinitionReader
:
package org.springframework.beans.factory.support; public interface BeanDefinitionReader { // 为什么需要这个,因为读取到bean definition后,需要存到这个里面去;如果不提供这个,我读了往哪放 BeanDefinitionRegistry getRegistry(); //资源加载器,加载xml之类,当然,作为一个接口,资源是可以来自于任何地方 ResourceLoader getResourceLoader(); //获取classloader ClassLoader getBeanClassLoader(); // beanName生成器 BeanNameGenerator getBeanNameGenerator(); // 从资源load bean definition int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException; int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException; int loadBeanDefinitions(String location) throws BeanDefinitionStoreException; int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException; }
我们切回前面,AbstractBeanDefinitionReader
实现了大部分方法,除了下面这个:
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
因为,它毕竟只是个抽象类,不负责干活啊;而且,为了能够从不同的resource读取,这个也理应交给子类。
比如,我们这里的XmlBeanDefinitionReader
就是负责从xml文件读取;我之前的文章里,也演示了如何从json读取,也是自定义了一个AbstractBeanDefinitionReader
的子类。
接着上面的方法往下走,马上就进入到了:
位置:org.springframework.beans.factory.xml.XmlBeanDefinitionReader,插入的参数,就是我们的xml public int loadBeanDefinitions(EncodedResource encodedResource) { ... try { // 读取xml文件为文件流 InputStream inputStream = encodedResource.getResource().getInputStream(); try { //读取为xml资源 InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // 解析bean definition去 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } } }
这里,提一句InputSource
,这个类的全路径为:org.xml.sax.InputSource
,是jdk里的类。
包名里包括了xml,知道大概是xml相关的类了,包名也包含了sax,大概知道是基于sax事件解析模型。
这方面我懂得也不多,回头可以单独讲解。我们继续正文:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) { try { int validationMode = getValidationModeForResource(resource); // 反正org.w3c.dom.Document也是jdk的类,具体不管 Document doc = this.documentLoader.loadDocument( inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware()); // 下边就开始正题了 return registerBeanDefinitions(doc, resource); } }
关于documentLoader.loadDocument
,大家只要简单知道,是进行初步的解析就行了,主要是基于xsd、dtd等,进行语法检查等。
我这里的堆栈,大家看下:
这里,先不深究xml解析的东西(主要是我也不怎么会啊,哈哈哈)
接着走:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); documentReader.setEnvironment(this.getEnvironment()); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
看得出来,XmlBeanDefinitionReader
可能觉得工作繁重,于是将具体的工作,交给了BeanDefinitionDocumentReader
。
public interface BeanDefinitionDocumentReader { void setEnvironment(Environment environment); void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) throws BeanDefinitionStoreException; }
核心方法肯定是
void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
两个参数,Document
我们理解,就是代表xml文件;
XmlReaderContext
是啥,看样子是个上下文,上下文,一般来说,就是个大杂烩,把需要用到的都会放在里面。
我们看看放了些啥:
public class XmlReaderContext extends ReaderContext { // 之前的核心类,把自己传进来了 private final XmlBeanDefinitionReader reader; // org.springframework.beans.factory.xml.NamespaceHandlerResolver,这个也是核心类! private final NamespaceHandlerResolver namespaceHandlerResolver; } public class ReaderContext { //xml资源本身 private final Resource resource; //盲猜是中间处理报错后,报告问题 private final ProblemReporter problemReporter; // event/listener机制吧,方便扩展 private final ReaderEventListener eventListener;` // 跳过 private final SourceExtractor sourceExtractor; }
看完了这个上下文的定义,要知道,它是作为一个参数,传给:
org.springframework.beans.factory.xml.BeanDefinitionDocumentReader void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) throws BeanDefinitionStoreException;
那,这个参数怎么构造的呢?
那,我们还得切回前面的代码:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); documentReader.setEnvironment(this.getEnvironment()); int countBefore = getRegistry().getBeanDefinitionCount(); // 这里,调用createReaderContext(resource)创建上下文 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
/** * Create the {@link XmlReaderContext} to pass over to the document reader. */ protected XmlReaderContext createReaderContext(Resource resource) { if (this.namespaceHandlerResolver == null) { // 创建一个namespacehandler this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver(); } return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, this.namespaceHandlerResolver); }
这里太核心了,我们看看namespaceHandlerResolver
是干嘛的:
public interface NamespaceHandlerResolver { /** * Resolve the namespace URI and return the located {@link NamespaceHandler} * implementation. * @param namespaceUri the relevant namespace URI * @return the located {@link NamespaceHandler} (may be {@code null}) */ // 比如解析xml时,我们可能有<bean>,这个是默认命名空间,其namespace就是<beans>;如果是<context:component-scan>,这里的namespace,就是context NamespaceHandler resolve(String namespaceUri); }
这个类的用途,就是根据传入的namespace,找到对应的handler。
大家可以去spring-beans.jar包里的META-INF/spring.handlers
找一找,这个文件打开后,内容如下:
http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
也可以再去spring-context.jar里找找,里面也有这个文件:
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
我列了个表格:
namespace | handler |
---|---|
http://www.springframework.org/schema/c | org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler |
http://www.springframework.org/schema/p | org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler |
http://www.springframework.org/schema/util | org.springframework.beans.factory.xml.UtilNamespaceHandler |
http://www.springframework.org/schema/context | org.springframework.context.config.ContextNamespaceHandler |
http://www.springframework.org/schema/task | org.springframework.scheduling.config.TaskNamespaceHandler |
http://www.springframework.org/schema/cache | org.springframework.cache.config.CacheNamespaceHandler |
比较重要的,我都列在上面了,剩下的jee
/lang
,我没用过,不知道大家用过没,先跳过吧。
大家可能也有感到奇怪的地方,怎么没有这个namespace的呢,因为它是默认的,所以在程序里是特殊处理的,一会才到它。
接着看核心逻辑:
DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createHelper(this.readerContext, root, parent); preProcessXml(root); parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent; }
恩。。。我们已经快疯了,怎么又出来一个类,DefaultBeanDefinitionDocumentReader
看来也是觉得自己工作压力太大了吧,这里又弄了个BeanDefinitionParserDelegate
。
这个类,没实现什么接口,就是个大杂烩,方法多的一批,主要是进行具体的解析工作,大家可以看看里面定义的字段:
public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans"; public static final String MULTI_VALUE_ATTRIBUTE_DELIMITERS = ",; "; /** * Value of a T/F attribute that represents true. * Anything else represents false. Case seNsItive. */ public static final String TRUE_VALUE = "true"; public static final String FALSE_VALUE = "false"; public static final String DEFAULT_VALUE = "default"; public static final String DESCRIPTION_ELEMENT = "description"; public static final String AUTOWIRE_NO_VALUE = "no"; public static final String AUTOWIRE_BY_NAME_VALUE = "byName"; public static final String AUTOWIRE_BY_TYPE_VALUE = "byType"; public static final String AUTOWIRE_CONSTRUCTOR_VALUE = "constructor"; public static final String AUTOWIRE_AUTODETECT_VALUE = "autodetect"; public static final String DEPENDENCY_CHECK_ALL_ATTRIBUTE_VALUE = "all"; public static final String DEPENDENCY_CHECK_SIMPLE_ATTRIBUTE_VALUE = "simple"; public static final String DEPENDENCY_CHECK_OBJECTS_ATTRIBUTE_VALUE = "objects"; public static final String NAME_ATTRIBUTE = "name"; public static final String BEAN_ELEMENT = "bean"; public static final String META_ELEMENT = "meta"; public static final String ID_ATTRIBUTE = "id"; public static final String PARENT_ATTRIBUTE = "parent"; public static final String CLASS_ATTRIBUTE = "class"; public static final String ABSTRACT_ATTRIBUTE = "abstract"; public static final String SCOPE_ATTRIBUTE = "scope"; public static final String SINGLETON_ATTRIBUTE = "singleton"; public static final String LAZY_INIT_ATTRIBUTE = "lazy-init"; public static final String AUTOWIRE_ATTRIBUTE = "autowire"; public static final String AUTOWIRE_CANDIDATE_ATTRIBUTE = "autowire-candidate"; public static final String PRIMARY_ATTRIBUTE = "primary"; public static final String DEPENDENCY_CHECK_ATTRIBUTE = "dependency-check"; public static final String DEPENDS_ON_ATTRIBUTE = "depends-on"; public static final String INIT_METHOD_ATTRIBUTE = "init-method"; public static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method"; public static final String FACTORY_METHOD_ATTRIBUTE = "factory-method"; public static final String FACTORY_BEAN_ATTRIBUTE = "factory-bean"; public static final String CONSTRUCTOR_ARG_ELEMENT = "constructor-arg"; public static final String INDEX_ATTRIBUTE = "index"; public static final String TYPE_ATTRIBUTE = "type"; public static final String VALUE_TYPE_ATTRIBUTE = "value-type"; public static final String KEY_TYPE_ATTRIBUTE = "key-type"; public static final String PROPERTY_ELEMENT = "property"; public static final String REF_ATTRIBUTE = "ref"; public static final String VALUE_ATTRIBUTE = "value"; public static final String LOOKUP_METHOD_ELEMENT = "lookup-method"; public static final String REPLACED_METHOD_ELEMENT = "replaced-method"; public static final String REPLACER_ATTRIBUTE = "replacer"; public static final String ARG_TYPE_ELEMENT = "arg-type"; public static final String ARG_TYPE_MATCH_ATTRIBUTE = "match"; public static final String REF_ELEMENT = "ref"; public static final String IDREF_ELEMENT = "idref"; public static final String BEAN_REF_ATTRIBUTE = "bean"; public static final String LOCAL_REF_ATTRIBUTE = "local"; ...
看出来了么,主要都是xml里面的那些元素的名称。
里面的方法,很多,我们用到了再说。
我们继续回到主线任务:
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions /** * 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) { // 一般来说,我们的根节点都是<beans>,这个是默认namespace的 if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); // 遍历xml <beans>下的每个元素 for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; // 判断元素是不是默认命名空间的,比如<bean>是,<context:component-scan>不是 if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { //<context:component-scan>,<aop:xxxx>走这边 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
这里,判断一个元素是不是默认命名空间,具体怎么做的呢:
BeanDefinitionParserDelegate#isDefaultNamespace(org.w3c.dom.Node) public boolean isDefaultNamespace(Node node) { //调用重载方法 return isDefaultNamespace(getNamespaceURI(node)); } public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans"; public boolean isDefaultNamespace(String namespaceUri) { return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri)); }
DefaultBeanDefinitionDocumentReader#parseDefaultElement 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)) { // recurse doRegisterBeanDefinitions(ele); } }
第一次发现,原来默认命名空间下,才这么几个元素:
import、alias、bean、(NESTED_BEANS_ELEMENT)beans
具体的解析放到下讲,这讲内容已经够多了。
主要是处理:aop、context
等非默认namespace
。
BeanDefinitionParserDelegate public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { String namespaceUri = getNamespaceURI(ele); // 这里,就和前面串起来了,根据namespace找handler 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)); }
我们挑一个大家最熟悉的org.springframework.context.config.ContextNamespaceHandler
,大家先看看:
public class ContextNamespaceHandler extends NamespaceHandlerSupport { 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()); } }
总之,到了这里,就是根据具体的元素,找对应的处理器了。这些都后面再说了。内容太多了。
大家可以回头再去看看第二章的整体流程,会不会清晰一些了呢?
主要是几个核心类:
XmlBeanDefinitionReader
BeanDefinitionDocumentReader
XmlReaderContext
namespaceHandlerResolver
BeanDefinitionParserDelegate
内容有点多,大家不要慌,我们后面还会进一步讲解的。觉得有帮助的话,点个赞哦。