spring 源码分析(1)-xml文件解析
我们在最开始接触spring的时候,看到不少书spring入门的例子如下
ApplicationContext atx = new ClassPathXmlApplicationContext("application.xml"); atx.getBean("benefitService");
上面这个例子第一行是表示如何初始化一个spring 容器,第二表示如何从一个已经初始化后的spring容器中按bean id得到这个bean, 绝大部分spring应用中,bean都是按业务模板和层次分别配置在不同的xml文件中, spring容器根据配置的xml文件名路径去分别解析这些xml 配置文件,生成相应的BeanDefinition 实例,一个bean对应一个BeanDefinition, 解析完成bean 的xml配置文件之后,spring容器就开始初始bean,大概的过程如下:
这篇文章主要分析第一个阶段,即xml配置文件 ---->BeanDefinition这个过程,首先根据IDE工具看一下ClassPathXmlApplicationContext 这个类的继承关系:
通过这个继续关系,发现ClassPathXmlApplicationContext也是间接实现了ResourceLoader这个接口, ResourceLoader的实现类主要用于根据给定的资源文件地址返回对应的Resource,在本例中,这个资源文件就是application.xml;
接着往下看
public ClassPathXmlApplicationContext(String configLocation) throws BeansException { this(new String[] {configLocation}, true, null); } public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }
代码到了含有三个参数的构造方法,主要有三个步骤
super(parent)
这个步骤主要是调父类的构造器初始化容器的parent对象,这示例中,parent这个参数为空,其次是初始化资源模式解析器resourcePatternResolver,是一个实现了ResourceLoader的类,源码如下 :
public AbstractApplicationContext(ApplicationContext parent) { this.parent = parent; this.resourcePatternResolver = getResourcePatternResolver(); } protected ResourcePatternResolver getResourcePatternResolver() { return new PathMatchingResourcePatternResolver(this); } public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) { Assert.notNull(resourceLoader, "ResourceLoader must not be null"); this.resourceLoader = resourceLoader; }
setConfigLocations(configLocations)
这行代码,主要就是初始化configLocations这个数组字段,源码如下:
private String[] configLocations; public void setConfigLocations(String[] locations) { if (locations != null) { Assert.noNullElements(locations, "Config locations must not be null"); this.configLocations = new String[locations.length]; for (int i = 0; i < locations.length; i++) { this.configLocations[i] = resolvePath(locations[i]).trim(); } } else { this.configLocations = null; } }
其中resolvePath主要解析并填充资源路径中的一些系统占位符,
如开始符:${,
结束符: }
分割符: :
refresh()
前面两步基本上都是容器本身的设置初始化,这个步骤才是spring 容器解析,创建初始化bean的关键步骤,点时去,我们发现这个方法长,只分析xml解析的过程,其它的在这里不一 一细说,在refresh方法中,第二代码是这样的:
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
直接进入org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory()这个方法,看一下这个方法体:
@Override protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { 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); } }
这个方法的大意是这样的:
- 先判断有没有beanFactory, 如果已经存在beanFactory,就先销毁beanFactory中所有的bean,再关闭这个beanFactory
- 创建一个DefaultListableBeanFactory 类型实例的beanFactory;
-
设置beanFactory的id,其中id是根据类名生成的,具体代码是:
obj.getClass().getName() + "@" + getIdentityHexString(obj)
- 定置化beanFactory, 主要是设置: Bean是否需求覆盖重写,是否允bean循环引用,参数自动发现和注释字段bean匹配解析,例如Autowired注解;
5.解析xml文档,并生成BeanDefinition对象实例集合;
重点看第5个步骤,即loadBeanDefinitions(beanFactory)这行代码,根据继承关系,直接进入org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(DefaultListableBeanFactory)这个方法
@Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setResourceLoader(this); 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); loadBeanDefinitions(beanDefinitionReader); }
这个方法的逻辑也很清晰
- 创建一个XmlBeanDefinitionReader 对象,这个类主要定义读取的Document,并注册BeanDefinition的功能;
- 设置beanDefinitionReader 对象的ResourceLoader,即ClassPathXmlApplicationContext类对象;
- 设置beanDefinitionReader 对象的解析对象:ResourceEntityResolver
- 初始化beanDefinitionReader 对象,在spring框架中,这个方法为空,是一个重写spring框架的扩展点;
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {}
- 正式解析xml文件,并生成BeanDefinitions, 方法体如下:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException { String[] configLocations = getConfigLocations(); if (configLocations != null) { for (String configLocation : configLocations) { reader.loadBeanDefinitions(configLocation); } } }
针对每个资源文件,重点看下面这两行代码:
Resource resource = resourceLoader.getResource(location); int loadCount = loadBeanDefinitions(resource);
第一行代码主是获取路径中以classpath:开头的xml文件,当然也有很多其它前缀开头的资源,org.springframework.util.ResourceUtils 这个类中有详细的说明
第二主要是生成加载BeanDefinitions
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); }
前面都是一些准备过程,接下来,到了真正执行loadBeanDefinitions
- getValidationModeForResource这个方法主要是获取xml文件的验证模式,主要有DTD和XSD两种验证模式,这两种验证模式,spring都支持,方法源码如下:
DTD模式示例:
XSD模式示例:
- 加载xml,并得到转换成对应的Document实例
- 解析并注册xml文件定义相关的bean;
重点看一下步骤3:
相关的方法源码如下:
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; }
重点分析 documentReader.registerBeanDefinitions(doc, createReaderContext(resource))这行代
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); BeanDefinitionParserDelegate delegate = createHelper(readerContext, root); preProcessXml(root); parseBeanDefinitions(root, delegate); postProcessXml(root); }
registerBeanDefinitions这个方法的逻辑也比较清晰
- 赋值readerContext
- 获取xml对象加载包装后对应Document对象
- 创建解析器 BeanDefinitionParserDelegate 类型的对象实例:delegate
- 处理xml文件的前置处理,默认为空,这是一个扩展点;
- 解析xml文件,并注册到spring容器中
- 处理xml文件的后轩处理,默认为空,这也是一个扩展点;
重点看一下parseBeanDefinitions这个方法,方法实现源码如下:
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)) { //默认标签bean的解析 //<bean id="benefitService" class="com.tmall.xxx"> parseDefaultElement(ele, delegate); } else { //自定义标签bean的解析 <tx:annotation-driven /> <dubbo:service timeout="3000" interface="com.tmall.xxx" ref="xxxx"/> <context:component-scan base-package="com.tmall.xxx"/> delegate.parseCustomElement(ele); } } } } else { //自定义标签bean的解析 <context:component-scan base-package="com.tmall.xxx"/> delegate.parseCustomElement(root); } }
这个方法主要是针对bean的xml配置文件中默认标签和自定义标签分别进行解析
默认标签的解析方法parseDefaultElement的方法体如下:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // 对import标签的解析处理 importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // 对alias标签的解析处理 processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {//对bean标签的解析处理 processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {//对beanS标签的解析处理 processBeanDefinition(ele, delegate); } }
到这个方法,我们终于看到与我们平时写spring bean配置文件相关的代码,下面结bean标签的解析处理代码进行分析
bean标签的解析最终会到parseBeanDefinitionElement方法,这个方法的部分代码如下:
这个方法的主要工作内容包括:
- 提供元素中的id和name属性
- 进一步解析其他所有属性并统一封装到AbstractBeanDefinition类型的实例中
- 如果检测到bean没有指定的beanName,那么使用默认的规则为此Bean生成beanName
4.将获取到的信息封装到BeanDefinitionHolder的实例中。
步骤2中对其它标签的解析过程部分源码如下:
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, BeanDefinition containingBean, AbstractBeanDefinition bd) { if (ele.hasAttribute(SCOPE_ATTRIBUTE)) { // scope属性 // Spring 2.x "scope" attribute bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE)); if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { // singleton 属性 error("Specify either 'scope' or 'singleton', not both", ele); } } else if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { // Spring 1.x "singleton" attribute bd.setScope(TRUE_VALUE.equals(ele.getAttribute(SINGLETON_ATTRIBUTE)) ? BeanDefinition.SCOPE_SINGLETON : BeanDefinition.SCOPE_PROTOTYPE); } else if (containingBean != null) { // Take default from containing bean in case of an inner bean definition. bd.setScope(containingBean.getScope()); } if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) { // abstract属性 bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE))); } String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE); // lazy-init属性 if (DEFAULT_VALUE.equals(lazyInit)) { lazyInit = this.defaults.getLazyInit(); } bd.setLazyInit(TRUE_VALUE.equals(lazyInit)); String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE); //autowire属性 bd.setAutowireMode(getAutowireMode(autowire)); String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE); bd.setDependencyCheck(getDependencyCheck(dependencyCheck)); if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) { // depends-on属性 String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE); bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, BEAN_NAME_DELIMITERS)); } // autowire-candidate 属性 String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE); if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) { String candidatePattern = this.defaults.getAutowireCandidates(); if (candidatePattern != null) { String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern); bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName)); } } else { bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate)); } // primary 属性 if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) { bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE))); } // init-method属性 if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) { String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE); if (!"".equals(initMethodName)) { bd.setInitMethodName(initMethodName); } } else { if (this.defaults.getInitMethod() != null) { bd.setInitMethodName(this.defaults.getInitMethod()); bd.setEnforceInitMethod(false); } } // destroy-method属性 if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) { String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE); if (!"".equals(destroyMethodName)) { bd.setDestroyMethodName(destroyMethodName); } } else { if (this.defaults.getDestroyMethod() != null) { bd.setDestroyMethodName(this.defaults.getDestroyMethod()); bd.setEnforceDestroyMethod(false); } } // factory-method 属性 if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) { bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE)); } // factory-bean 属性 if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) { bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE)); } return bd; } // 解析购造函数constructor-arg标签元素 public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) { parseConstructorArgElement((Element) node, bd); } } } //解析property属性 public void parsePropertyElements(Element beanEle, BeanDefinition bd) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) { parsePropertyElement((Element) node, bd); } } }
其它的标签解析过程请参考下面这个类org.springframework.beans.factory.xml.BeanDefinitionParserDelegate
上面解析分析了默认标签bean的源码解析过程,下面再来看一下自定标签的解析过程,方法位置:org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(Element, BeanDefinition)源码如下:
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { //获取对应的命名空间 String namespaceUri = getNamespaceURI(ele); // 根据命名空间得到对相应的NamespaceHandler NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } // 调用自定义的NamespaceHandler handler进行解析 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
由于spring自定义标签大家在平时用得比较少,由于时间关系和篇幅关系,本来就不对自定义标签进行详细分析,下一篇文章会结合spring源码对自定义标签的使用和解析原理进行详细的介绍分析
由于时间关系,文中有些地方没有写细致,讲得不够清楚,可能不少地方还会出现低级的错误,请大家指正。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Python全栈工程师(Python3 所有基础内容都在这了 0-0)
ParisGabriel 每天坚持手写一天一篇 决定坚持几年 为了梦想为了信仰 开局一张图 Python一个月的基础语法 基本就到这咯 接下来是数据库了 东西太多了 简单的整理一下 大多数是关键字 部分单词 没有分类整理 按照顺序整理的 statements 语句print 输出quit() 退出exit() 退出ctrl + d (输入文件结束符)结束输入并退出int 整型数float 浮点型数complex 复数bool 布尔True 真False 假str 字符串list 列表None 空值expression 表达式del 删除help() 帮助is 是not is 不是id( ) 查询内存地址-5~256 小整数池refrence count 引用计数abs 取绝对值round vc四舍五入pow 幂运算input 输入sep 两值之间分隔符end 结束(内容)if 如果elif 否则如...
- 下一篇
java源码-Hashtable
开篇 Hashtable作为jdk提供的最原始的key/value存储结构,属于线程安全系列的,所以这边顺便把这个类分析一下,基本上如果你看过了HashMap相关的数据结构,这个看起来就是小菜一碟了。 Hashtable类关系图 Hashtable类关系图 Hashtable源码分析 Hashtable类成员变量 Hashtable中核心的存储结构是table,table中存储的数据结构是Entry对象 public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable { // 实际key/value存储的table数组 private transient Entry<?,?>[] table; private transient int count; private int threshold; private float loadFactor; // modCount private tr...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS8编译安装MySQL8.0.19
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS关闭SELinux安全模块
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS8安装Docker,最新的服务器搭配容器使用