前情回顾

上篇《Spring读书笔记——bean加载》我们从代码角度介绍了有哪些类负责解析XML文件,又是如何一步步从XML格式脱变成我们熟悉的bean的,直到DefaultBeanDefinitionDocumentReader的registerBeanDefinitions方法。我们抽丝剥茧,终于快看到Spring是如何解析bean的代码了。

在此之前,我们回一下上篇看到过的主要类和方法

XmlBeanFactory(XmlFactory) ->

XmlBeanDefinitionReader(loadBeanDefinitions)->

XmlBeanDefinitionReader(doLoadBeanDefinitions)->

XmlBeanDefintionReader(registerBeanDefinitions)

DefaultBeanDefinitionDocumentReader(registerBeanDefinitions)->

DefaultBeanDefinitonDocumentReader(doRegisterBeanDefinitions)

解析的类型

Spring的标签分为默认标签和自定义标签两种。

默认标签主要包括:import、alias、bean和beans

自定义标签:顾名思义就是自定义的标签,比如

DefaultBeanDefinitionDocumentReader

从XmlBeanDefinitionReader的registerBeanDefinitions开始,我们第一步要做的就是创建一个Xml文档的解析类

  1. BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();

createBeanDefinitionDocumentReader方法创建了类DefaultBeanDefinitionDocumentReader。

从DefaultBeanDefinitionDocumentReader的registerBeanDefinitions到doRegisterBeanDefinitions,下面我们着重从doRegisterBeanDefinitions的parseBeanDefintions方法开始往下看。

代理类BeanDefinitionParserDelegate

负责解析bean的并不是DefaultBeanDefinitionDocumentReader,而是委托给了代理类BeanDefinitionParserDelegate,后面我们会看到BeanDefinitionParserDelegate这个功臣是如何施展拳脚解析bean的。

  1. parseBeanDefinitions(root, this.delegate);

进入parseBeanDefinitions我们看到其具体实现如下

  1. protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
  2. if (delegate.isDefaultNamespace(root)) {
  3. NodeList nl = root.getChildNodes();
  4. for (int i = 0; i < nl.getLength(); i++) {
  5. Node node = nl.item(i);
  6. if (node instanceof Element) {
  7. Element ele = (Element) node;
  8. if (delegate.isDefaultNamespace(ele)) {
  9. parseDefaultElement(ele, delegate);
  10. }
  11. else {
  12. delegate.parseCustomElement(ele);
  13. }
  14. }
  15. }
  16. }
  17. else {
  18. delegate.parseCustomElement(root);
  19. }
  20. }
  • 判断是否是默认命名空间的,如果是则遍历root下所有的子标签,如果不是,则进行自定义解析
  • 对于是默认命名空间下的标签,在遍历得到每一个子标签后开始调用parseDefaultElement方法开始我们之前提到的默认标签的解析。

默认标签解析

前面已经提到过默认标签有四种,我们看看代码也是一目了然,下面是解析默认标签的parseDefaultElement的方法实现

  1. private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
  2. if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
  3. importBeanDefinitionResource(ele);
  4. }
  5. else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
  6. processAliasRegistration(ele);
  7. }
  8. else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
  9. processBeanDefinition(ele, delegate);
  10. }
  11. else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
  12. // recurse
  13. doRegisterBeanDefinitions(ele);
  14. }
  15. }
  • if...else语句分别判定了import、alias、bean和beans的情况,并且不同情况作了不同的方法实现
  • 首先要看的就是我们再熟悉不过的bean标签的解析processBeanDefinition方法

bean标签的解析

processBeanDefinition的方法实现如下

  1. protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
  2. BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
  3. if (bdHolder != null) {
  4. bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
  5. try {
  6. // Register the final decorated instance.
  7. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
  8. }
  9. catch (BeanDefinitionStoreException ex) {
  10. getReaderContext().error("Failed to register bean definition with name '" +
  11. bdHolder.getBeanName() + "'", ele, ex);
  12. }
  13. // Send registration event.
  14. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
  15. }
  16. }

方法并不复杂,大致看下主要干了下面几件事

  • 前面介绍的BeanDefinitionParserDelegate开始排上用场,用于对bean元素解析,并得到BeanDefinitionHolder类型的bdHolder对象
  • 对于解析得到结果不为空的情况,首先还要对于需要修饰的情况(默认标签包含自定义标签)进行处理
  • 然后借助BeanDefinitionReaderUtils的registerBeanDefinition对bdHolder进行注册
  • 最后发送响应事件,通知响应的监听器(Spring3x版本这块还没有具体实现,只是留了个壳)

delegate.parseBeanDefinitionElement

我们主要从processBeanDefintion的第一行代码开始,来到BeanDefinitionParserDelegate.parseBeanDefinitionElement

  1. public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
  2. String id = ele.getAttribute(ID_ATTRIBUTE);
  3. String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
  4. List<String> aliases = new ArrayList<String>();
  5. if (StringUtils.hasLength(nameAttr)) {
  6. String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
  7. aliases.addAll(Arrays.asList(nameArr));
  8. }
  9. String beanName = id;
  10. if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
  11. beanName = aliases.remove(0);
  12. if (logger.isDebugEnabled()) {
  13. logger.debug("No XML 'id' specified - using '" + beanName +
  14. "' as bean name and " + aliases + " as aliases");
  15. }
  16. }
  17. if (containingBean == null) {
  18. checkNameUniqueness(beanName, aliases, ele);
  19. }
  20. AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
  21. if (beanDefinition != null) {
  22. if (!StringUtils.hasText(beanName)) {
  23. try {
  24. if (containingBean != null) {
  25. beanName = BeanDefinitionReaderUtils.generateBeanName(
  26. beanDefinition, this.readerContext.getRegistry(), true);
  27. }
  28. else {
  29. beanName = this.readerContext.generateBeanName(beanDefinition);
  30. // Register an alias for the plain bean class name, if still possible,
  31. // if the generator returned the class name plus a suffix.
  32. // This is expected for Spring 1.2/2.0 backwards compatibility.
  33. String beanClassName = beanDefinition.getBeanClassName();
  34. if (beanClassName != null &&
  35. beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
  36. !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
  37. aliases.add(beanClassName);
  38. }
  39. }
  40. if (logger.isDebugEnabled()) {
  41. logger.debug("Neither XML 'id' nor 'name' specified - " +
  42. "using generated bean name [" + beanName + "]");
  43. }
  44. }
  45. catch (Exception ex) {
  46. error(ex.getMessage(), ele);
  47. return null;
  48. }
  49. }
  50. String[] aliasesArray = StringUtils.toStringArray(aliases);
  51. return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
  52. }
  53. return null;
  54. }
  • 有很多我们之前熟知的细节从这里的代码就可以找到根据,比如“bean如果不声明name属性,spring会默认创建一个name”,显然通过上面一点代码我们就看到Spring确实是这么实现的,而且我们看到如果没有声明name,默认的name值就是id的值
  • 紧接着,我们看下

    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);这行代码,进入parseBeanDefinitionElement我们会看到这个方法做了很多的解析的工作,比如解析parent属性、解析元数据、解析构造函数、解析property子元素等等

parseBeanDefinitionElement方法

  1. public AbstractBeanDefinition parseBeanDefinitionElement(
  2. Element ele, String beanName, BeanDefinition containingBean) {
  3. this.parseState.push(new BeanEntry(beanName));
  4. String className = null;
  5. if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
  6. className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
  7. }
  8. try {
  9. String parent = null;
  10. if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
  11. parent = ele.getAttribute(PARENT_ATTRIBUTE);
  12. }
  13. AbstractBeanDefinition bd = createBeanDefinition(className, parent);
  14. parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
  15. bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
  16. parseMetaElements(ele, bd);
  17. parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
  18. parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
  19. parseConstructorArgElements(ele, bd);
  20. parsePropertyElements(ele, bd);
  21. parseQualifierElements(ele, bd);
  22. bd.setResource(this.readerContext.getResource());
  23. bd.setSource(extractSource(ele));
  24. return bd;
  25. }
  26. catch (ClassNotFoundException ex) {
  27. error("Bean class [" + className + "] not found", ele, ex);
  28. }
  29. catch (NoClassDefFoundError err) {
  30. error("Class that bean class [" + className + "] depends on not found", ele, err);
  31. }
  32. catch (Throwable ex) {
  33. error("Unexpected failure during bean definition parsing", ele, ex);
  34. }
  35. finally {
  36. this.parseState.pop();
  37. }
  38. return null;
  39. }

我们挑其中一个解析属性函数parseConstructorArgElements看下具体实现。

parseConstructorArgElements用于解析ConstructArg标签。

我们常见的construct-arg大概是这样

  1. <bean id="user" class="com.jackie.User">
  2. <construct-arg index="0">
  3. ​ ​<value>jackie</value>
  4. </construct-arg>
  5. <construct-arg index="1">
  6. <value>male</value>
  7. </construct-arg>
  8. </bean>

construct-arg标签的解析过程如下所示

  1. public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
  2. String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
  3. String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
  4. String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
  5. if (StringUtils.hasLength(indexAttr)) {
  6. try {
  7. int index = Integer.parseInt(indexAttr);
  8. if (index < 0) {
  9. error("'index' cannot be lower than 0", ele);
  10. }
  11. else {
  12. try {
  13. this.parseState.push(new ConstructorArgumentEntry(index));
  14. Object value = parsePropertyValue(ele, bd, null);
  15. ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
  16. if (StringUtils.hasLength(typeAttr)) {
  17. valueHolder.setType(typeAttr);
  18. }
  19. if (StringUtils.hasLength(nameAttr)) {
  20. valueHolder.setName(nameAttr);
  21. }
  22. valueHolder.setSource(extractSource(ele));
  23. if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
  24. error("Ambiguous constructor-arg entries for index " + index, ele);
  25. }
  26. else {
  27. bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
  28. }
  29. }
  30. finally {
  31. this.parseState.pop();
  32. }
  33. }
  34. }
  35. catch (NumberFormatException ex) {
  36. error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
  37. }
  38. }
  39. else {
  40. try {
  41. this.parseState.push(new ConstructorArgumentEntry());
  42. Object value = parsePropertyValue(ele, bd, null);
  43. ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
  44. if (StringUtils.hasLength(typeAttr)) {
  45. valueHolder.setType(typeAttr);
  46. }
  47. if (StringUtils.hasLength(nameAttr)) {
  48. valueHolder.setName(nameAttr);
  49. }
  50. valueHolder.setSource(extractSource(ele));
  51. bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
  52. }
  53. finally {
  54. this.parseState.pop();
  55. }
  56. }
  57. }
  • 如果constrcut-arg没有指定index函数,默认按照声明属性的顺序来解析,如果声明则按照index的值解析
  • 使用ConstrcutorArgumentValues的ValueHolder来封装解析得到的type、name等属性值
  • 最后将上面的valueHolder对象按照index添加到BeanDefinition的indexedArgument属性上
  • 注意,对于没有index声明的,最终解析后是存放到genericArgumentValues属性上

bean标签的注册

上面层层剥开,一直到解析到具体的属性,我们现在再一层层的往回走。前面所做的工作,无非是将Xml的元素解析出来并与Spring中的BeanDefinition,也就是换了一种形式在Spring中存在。

我们最终得到的是BeanDefinitionHolder

  1. public BeanDefinitionHolder(BeanDefinition beanDefinition, String beanName, String[] aliases) {
  2. Assert.notNull(beanDefinition, "BeanDefinition must not be null");
  3. Assert.notNull(beanName, "Bean name must not be null");
  4. this.beanDefinition = beanDefinition;
  5. this.beanName = beanName;
  6. this.aliases = aliases;
  7. }

解析转换工作完成了,现在需要开始bean的注册了

我们回到DefaultBeanDefinitionDocumentReader的processBeanDefinition,看看注册是如何实现的。

从DefaultBeanDefinitionDocumentReader.processBeanDefinition->BeanDefinitionReaderUtils.registerBeanDefinition->DefaultLisableBeanFactory.registerBeanFactory。

  1. public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
  2. throws BeanDefinitionStoreException {
  3. Assert.hasText(beanName, "Bean name must not be empty");
  4. Assert.notNull(beanDefinition, "BeanDefinition must not be null");
  5. if (beanDefinition instanceof AbstractBeanDefinition) {
  6. try {
  7. ((AbstractBeanDefinition) beanDefinition).validate();
  8. }
  9. catch (BeanDefinitionValidationException ex) {
  10. throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
  11. "Validation of bean definition failed", ex);
  12. }
  13. }
  14. synchronized (this.beanDefinitionMap) {
  15. Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
  16. if (oldBeanDefinition != null) {
  17. if (!this.allowBeanDefinitionOverriding) {
  18. throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
  19. "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
  20. "': There is already [" + oldBeanDefinition + "] bound.");
  21. }
  22. else {
  23. if (this.logger.isInfoEnabled()) {
  24. this.logger.info("Overriding bean definition for bean '" + beanName +
  25. "': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
  26. }
  27. }
  28. }
  29. else {
  30. this.beanDefinitionNames.add(beanName);
  31. this.frozenBeanDefinitionNames = null;
  32. }
  33. this.beanDefinitionMap.put(beanName, beanDefinition);
  34. }
  35. resetBeanDefinition(beanName);
  36. }
  • 如果beanName已经注册又没有声明可以覆盖,则会抛出异常
  • 符合条件的bean最终会被加入一个map结合
  • map集合的key是beanName,value就是前面解析得到的BeanDefinition实例对象
  • 清楚解析之前留下对应的beanName的缓存

至此,我们看到了Spring是如何解析标签(我们还有自定义标签没说),如何解析bean标签(我们还有import、alias和beans没说),如何解析标签属性(我们还有太多的属性没有介绍,但是都是类似的过程),最终完成了bean的注册。

如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

Spring读书笔记——bean解析的更多相关文章

  1. Spring读书笔记——bean创建(上)

    通过<Spring读书笔记--bean加载>和<Spring读书笔记--bean解析>,我们明白了两件事. Spring如何加载消化一个xml配置文件 Spring如何将xml ...

  2. Spring读书笔记——bean创建(下)

    有关Spring加载bean系列,今天这是最后一篇了,主要接上篇对于从Spring容器中获取Bean的一些细节实现的补充. <Spring读书笔记--bean加载>--Spring如何加载 ...

  3. Spring读书笔记——bean加载

    我们的日常开发几乎离不开Spring,他为我们的开发带来了很大的便捷,那么Spring框架是如何做到方便他人的呢.今天就来说说bean如何被加载加载. 我们在xml文件中写过太多类似这样的bean声明 ...

  4. (转) Spring读书笔记-----Spring的Bean之配置依赖

    前一篇博客介绍了Spring中的Bean的基本概念和作用域(Spring读书笔记-----Spring的Bean之Bean的基本概念),现在介绍Spring Bean的基本配置. 从开始我们知道Jav ...

  5. Spring读书笔记-----Spring的Bean之设置Bean值

    [java] view plaincopyprint? Java实例的属性值可以有很多种数据类型.基本类型值.字符串类型.java实例甚至其他的Bean实例.java集合.数组等.所以Spring允许 ...

  6. Spring读书笔记-----Spring的Bean之Bean的基本概念

    从前面我们知道Spring其实就是一个大型的工厂,而Spring容器中的Bean就是该工厂的产品.对于Spring容器能够生产那些产品,则取决于配置文件中配置. 对于我们而言,我们使用Spring框架 ...

  7. (转)Spring读书笔记-----Spring的Bean之Bean的基本概念

    从前面我们知道Spring其实就是一个大型的工厂,而Spring容器中的Bean就是该工厂的产品.对于Spring容器能够生产那些产品,则取决于配置文件中配置. 对于我们而言,我们使用Spring框架 ...

  8. (转)Spring读书笔记-----Spring核心机制:依赖注入

    Java应用(从applets的小范围到全套n层服务端企业应用)是一种典型的依赖型应用,它就是由一些互相适当地协作的对象构成的.因此,我们说这些对象间存在依赖关系.加入A组件调用了B组件的方法,我们就 ...

  9. Spring读书笔记-----Spring核心机制:依赖注入

    spring框架为我们提供了三种注入方式,分别是set注入,构造方法注入,接口注入.今天就和大家一起来学习一下 依赖注入的基本概念 依赖注入(Dependecy Injection),也称为IoC(I ...

随机推荐

  1. oracle 数据的导入导出

    一.数据导出 1.为输出路径建立一个数据库的directory对象. create or replace directory dumpdir as 'd:\'; 可以通过:select * from ...

  2. 自制ACL+DHCP实验(初版)

    (实验用gns模拟器) ACL 实验拓扑: 实验要求: 1.1.1.1→3.3.3.3 不通 11.11.11.11→3.3.3.3 通 2.2.2.2→3.3.3.3 通 实验步骤: 步骤一:基本配 ...

  3. CCNP-3.vlan间路由及三层交换机的配置

  4. 【Beta】阶段 第一次Daily Scrum Meeting

    每日任务 1.本次会议为第一次 Meeting会议: 2.本次会议在周一下午16:40,课间休息时间在禹州楼召开,召开本次会议为10分钟. 一.今日站立式会议照片 二.每个人的工作 (有work it ...

  5. 201521123066 《Java程序设计》第四周学习总结

    1. 本周学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 1.2 使用常规方法总结其他上课内容. 1.多态性: (1)概念:相同的方法名,不同的实现方法 (2)instanceof运算符:判 ...

  6. Java-Preferences用法-入门

    Properties提供的应用程序解决方案主要存在两个问题: (1)配置文件不能放在主目录中,因为某些OS(如Win9X)没有主目录的概念: (2)没有标准的文件命名规则,存在文件名冲突的可能性. J ...

  7. 201521123098 《Java程序设计》第13周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 1. TCP的特征: ①可靠:具有失败重传的功能: ②开销大:需要建立通路以维持通信: ③控制能力较强:有 ...

  8. 201521123098 《Java程序设计》第12周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. 1. Input Stream -- 数据提供者可从其中读数据输出流:Output Stream -- 数据接 ...

  9. 多线程:head first Thread.join()

    不使用Thread.join() 测试线程 先上代码: /** * Created by Zero on 2017/8/23. */ public class TestJoin implements ...

  10. MySQL的JOIN(五):JOIN优化实践之排序

    这篇博文讲述如何优化JOIN查询带有排序的情况.大致分为对连接属性排序和对非连接属性排序两种情况.插入测试数据. CREATE TABLE t1 ( id INT PRIMARY KEY AUTO_I ...