上一遍中详细的讲解了Spring启动的整个详细过程,这一边继续来针对不同的重点逐个击破。而在这一篇中,针对Spring配置文件的加载过程进行全方面的剖析。
根据之前的讲解,Spring在启动过程中最核心的类是AbstractRefreshableApplicationContext这个类,这个类中有个方法refreshBeanFactory(),其中的loadBeanDefinition(beanFactory);就是具体执行配置文件的加载、解析,然后根据配置文件中定义的各种对象及属性信息,来进行各种具体配置的执行。
配置文件的加载说起来也就是一句话的时候,但是有没有想过,这个配置文件到底要怎么样去做解析?配置文件中不仅定义了各种各样的Bean,每个Bean还有众多的属性值也要一并解析出来,如果让你去做解析的话,你会怎么处理呢?
可能会说使用SAX或Dom4J就可以对xml文件进行解析,解析工作说白了就是把文件中的内容读取出进行一个识别,那问题来了,怎么认识这些标签呢?怎么知道bean里面有id属性,class属性,而不是随手写的属性。所以一定会有对当前文档规范的定义,一种叫dtd,另一种是xsd。
虽然通过配置文件头可以到对应的网站进行解析,如果网络不通的情况下该怎么办?那就要使用本地的解析
接下来,就进入到loadBeanDefinitions(beanFactory)方法里面,对配置文件的加载一探究竟。
创建一个xml的解析对象,并通过回调设置到beanFactory中:
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
setEnvironment信息,它包含了当前系统的环境和某些属性值,方便对配置文件中的某些东西进行替换工作,比如对${…}表示中的值进行替换
beanDefinitionReader.setResourceLoader(this);
设置资源加载器,就是将当前的ClassPathXmlApplicationContext对象设置进去。
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
设置实体处理器,ResourceEntityResolver这个对象挺有意思,看它的父类方法中有一个DelegatingEntityResolver(@Nullable ClassLoader classLoader) 方法
this.dtdResolver = new BeansDtdResolver();
如果Bean标签中的定义信息使用的是dtd这种格式的,用BeansDtdResolver来解析
this.schemaResolver = new PluggableSchemaResolver(classLoader);
对schema约束的解析,而在PluggableSchemaResolver这个new出来的类中,指出了一个关键的文件路径DEFAULT_SCHEMA_MAPPINGS_LOCATION,也就是”META-INF/spring.schemas”这个路径下
这也就是刚刚上面说的当不使用网络,使用本地的解析规则文件的加载指定流程
initBeanDefinitionReader(beanDefinitionReader);
初始化当前BeanDefinitionReader对象,此处设置配置文件是否要进行验证
loadBeanDefinitions(beanDefinitionReader);
这个方法里面,对beanDefinitionReader进行真正的加载
Resource[] configResources = getConfigResources();
以Resource的方式获得配置文件的资源位置。这个方法一般情况是使用不到,取决于启动类传递的到底是怎么样的参数
String[] configLocations = getConfigLocations();
以String的方式获得配置文件的资源位置,获取的configLocations就是自定义的配置文件applicationContext.xml,那么此时已经将配置文件获取到了,通过reader.loadBeanDefinitions(configLocations)方法来解析。在这个解析的方法中,必然是循环处理每一个单一的配置文件,传入loadBeanDefinitions(String location)方法进行处理
进入这个方法,首先ResourceLoader resourceLoader = getResourceLoader()获取资源加载器对象,然后进行判断,这个资源加载器是否属于ResourcePatternResolver这个对象,然后调用DefaultResourceLoader的getResource完成具体的Resource定位:Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location),这个方法中就会看到通过特定的规则路径匹配,比如以”classpath*”开头的文件,或者有包含”classpath*”命名的文件等来匹配对应的配置文件进行加载解析
整个解析过程由String[] – String – Resource[] – Resource,最终将Resource读取成一个Document文档对象,根据文档的节点信息封装成一个个的BeanDefinition对象。现在xml文件已经被封装成了Document对象,下一步就要开始利用Document中的信息做文章了。
int count = registerBeanDefinitions(doc, resource);
进入这个方法,首先创建一个对Document解析的对象:BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(),开始准备解析documentReader.registerBeanDefinitions(doc, createReaderContext(resource)),F7进入方法看到doRegisterBeanDefinitions(doc.getDocumentElement()),以do*开头的必然是真正开始干活的,这个方法才是核心
parseBeanDefinitions(root, this.delegate);
解析Bean定义信息,将root根节点作为参数传入,将this.delegate解析器作为参数传入,也就是说,利用解析器从根节点开始解析元素。F7进入方法里面,这个方法写的并不多,但是读起来挺难受。首先会判断是不是默认的beans标签,验证通过进入下一步,获取子节点NodeList nl = root.getChildNodes(),然后再去遍历这些子节点进行解析。
parseDefaultElement(ele, delegate);
判断是不是默认的几种标签元素,根据不同的元素进行不同的解析
processBeanDefinition(ele, delegate);
利用delegate解析器对ele元素进行解析,进入方法中:
beanDefinition是beanDefinition对象的封装类,封装了Beandefinition这个Bean的名字和别名,用它来完成IoC容器的注册。得到这个BeanDefinitionHolder就意味着beanDefinition是通过BeanDefinitionParserDelegate对xml元素觉得信息按照Spring的Bean规则进行解析得到的。所以BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele)这个方法非常重要,进入方法内部
会发现那些默认的属性都在这边进行了解析获取,首先解析id和name,如果有别名的话,会对别名挨个进行遍历解析
checkNameUniqueness(beanName, aliases, ele);判断一下名字是否唯一
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
对Bean元素进行详细的解析,除了id和name以外,还要很多别的标签需要解析
className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); 解析class属性
parent = ele.getAttribute(PARENT_ATTRIBUTE); 解析parent属性
而当className和parent这两个属性都有之后,已经可以AbstractBeanDefinition bd = createBeanDefinition(className, parent)进行反射了
本质上是创建一个GenericBeanDefinition对象返回
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); 设置description信息
parseMetaElements(ele, bd); 解析元数据
parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); 解析lookup-method属性
parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); 解析replaced-method属性
parseConstructorArgElements(ele, bd); 解析构造参数
parsePropertyElements(ele, bd); 解析property子元素
parseQualifierElements(ele, bd); 解析qualifier子元素
当这些属性都设置完成以后,就可以进行Bean的实例化。而刚刚说的这些可以看到基本都是Spring自己定义的标签,如果我们想自己自定义标签的话该如何实现呢?其实很简单看了上述的过程之后,想要实现自定义标签只需:
1、创建一个对应的解析器处理类;
2、创建一个普通的SpringHandler配置文件,让应用程序能够完成加载;
3、创建对应标签的parser类,对当前标签的其它属性值进行解析
自定义标签扩展
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/111916.html