热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

javav.5起点,逐行阅读Spring5.X源码(一)BeanDefinition,起点

本篇博客你讲学到:1.如何理解BeanDefinition2.准备环境3.BeanDefinition接口讲解4.BeanDefinition的类继承关系5.IOC的

本篇博客你讲学到:

1. 如何理解BeanDefinition

2. 准备环境

3. BeanDefinition接口讲解

4. BeanDefinition的类继承关系

5. IOC的引出

6. BeanFactory工厂的引出

7. 后置处理器的引出

8. spring扩展性初探

1、为什么从BeanDefinition讲起?

spring源码太庞大了,精通spring源码确实不容易,讲懂更难。学习spring源码是件浩大的工程。很多读者从spring容器启动开始逐行debug源码进行分析,刚开始很容易理解,但是当你逐层debug源码深处时,就会感慨“身在此山中,不识真面目”。笔者在刚开始的时候也经历过这种痛苦,精读几遍之后笔者觉着spring源码不能从头读起,我们需要先搞懂spring中的基础组件及组件之间的关系,这就好比组装电脑,你得先了解CPU作用、内存的作用、主板的作用、硬盘的作用,然后你才知道如何讲他们组装到一起,从头阅读源码就跟你让一个学金融专业的学生组装电脑一样,效果可想而知。

既然BeanDefinition是个接口,那spring中肯定有他的实现类对不对,好,是时候看一下BeanDefinition的类继承图了。在这里笔者跟大家说一个问题,笔者发现很多人读源码的时候拿来一个类就读或者debug源码跟读,也不管这个类跟其他类的关系,读完后感觉很混乱,甚至吐槽spring源码写的毫无章法,拜托,spring源码是典型的面向接口编程,严格遵循开闭原则、依赖倒置原则和迪米特法则(软件设计7大基本原则,大家自行百度啦),是spring源码写的差还是你的水平差?spring每一个模块都有一个完整的类继承关系图,不然spring被业界称赞的高扩展性谈何而来?所以我们必须将每个模块的类继承关系了然于胸。初学,我们也不可能将继承体系中的每个类都搞懂,把这个继承图下载下来存到桌面,在以后的源码阅读中这个继承关系会被你一一攻破,学完你也就掌握了,而且不会忘,更能提高你的编程水平,读完spring你会发现的编程风格潜移默化的被spring影响了!对吧。

BeanDefinition的继承关系:

beanDefinition.setScope("singleton");

beanDefinition.setDescription("手动注入");

beanDefinition.setAbstract(false);

//将beanDefinition注册到spring容器中

context.registerBeanDefinition("interService",beanDefinition);

//加载或者刷新当前的配置信息

context.refresh();

BeanDefinition interServiceBeanDefinition = context.getBeanDefinition("interService");

System.out.println("——————InterService的附加属性如下:");

System.out.println("父类"+interServiceBeanDefinition.getParentName());

System.out.println("描述"+interServiceBeanDefinition.getDescription());

System.out.println("InterService在spring的名称"+interServiceBeanDefinition.getBeanClassName());

System.out.println("实例范围"+interServiceBeanDefinition.getScope());

System.out.println("是否是懒加载"+interServiceBeanDefinition.isLazyInit());

System.out.println("是否是抽象类"+interServiceBeanDefinition.isAbstract());

System.out.println("——————等等等等,读者自行编写");

}

}

其实笔者很喜欢这种代码方式完成spring配置工作,这样能让我们更深入的了解和应用spring,不过这种方式的缺点也很明显-繁琐易出错,spring为了简化我们的工作提供了xml配置方式,直到spring5.x注解方式的稳定成熟,spring全家桶得到了飞速的发展。但通过这个例子读者可以加深对BeanDefinition的理解。

IOC的引出

看上面这么一行代码:

context.registerBeanDefinition("interService",beanDefinition);

这行代码的意思是将我们手动封装的beanDefinition注册到容器中,同时给这个beanDefinition起了个名字“interService”,spring内部生成beanDefinitino时会默认起一个名字,改名字的规则就是业务类名字首字母小写。

那生成的BeanDefinition保存在哪里呢?既然我们是通过上面的方法将BeanDefinition注册到容器中,肯丢是在这个方法底层实现了保存,我们点进去看:

@Override

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)

throws BeanDefinitionStoreException {

// 将beanDefinition保存到spring容器中

this.beanFactory.registerBeanDefinition(beanName, beanDefinition);

}

继续跟进registerBeanDefinition方法,找到下面这行代码:

.....以上代码省略,以后详解

this.beanDefinitionMap.put(beanName, beanDefinition);

顾名思义,beanDefinitionMap就是一个Map呀!具体是啥map,我们ctrl+鼠标左键单击找到beanDefinitionMap定义处:

/** Map of bean definition objects, keyed by bean name. */

private final Map beanDefinitionMap &#61; new ConcurrentHashMap<>(256);

还需要我解释这行代码吗&#xff1f;有人说需要&#xff0c;我不懂ConcurrentHashMap&#xff0c;好吧&#xff0c;这是java并发包java.util.concurrent下的集合类&#xff0c;它就是一个Map&#xff0c;但是支持多线程并发访问&#xff0c;为啥使用ConcurrentHashMap而不是用HashMap&#xff0c;嗨&#xff0c;建议你好好补下java高并发知识(后续我会写一个java高并发编程底层原理&#xff0c;让你吊打面试官&#xff0c;欢迎大家关注)。总之&#xff0c;这就是beanDefinition存储的容器&#xff0c;这行代码所在的类名叫DefaultListableBeanFactory&#xff0c;它是bean的工厂&#xff0c;spring中所有的对象或者说bean都存在这个bean工厂中&#xff0c;业界叫它IOC&#xff0c;很多书或者视频都会讲IOC&#xff0c;相信读者也知道IOC是容器&#xff0c;但它就是一堆Map集合而已&#xff0c;beanDefinitionMap 知识众多Map中的一个而已&#xff0c;以后我会将其他的map容器&#xff0c;今天你只需要只到这么一个存放BeanDefinition的容器即可。

这下你搞懂IOC了吧&#xff01; 全体起立&#xff01;

有读者问&#xff0c;那DefaultListableBeanFactory这个bean工厂啥时候创建的&#xff0c;我说以后再讲&#xff01;想必大家都想知道&#xff0c;那就了解一下吧&#xff01;看代码&#xff1a;

AnnotationConfigApplicationContext context &#61; new AnnotationConfigApplicationContext();

这是我们创建spring上下文对象&#xff0c;AnnotationConfigApplicationContext 类有一个父类&#xff0c;AnnotationConfigApplicationContext的无参 构造函数执行时会默认调用父类无参构造函数(java基础知识)&#xff0c;AnnotationConfigApplicationContext 的父类如下&#xff1a;

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {

也就是GenericApplicationContext &#xff0c;我们看一下他的构造函数&#xff1a;

public GenericApplicationContext() {

//初始化一个BeanFactory

this.beanFactory &#61; new DefaultListableBeanFactory();

}

我不说话&#xff0c;静静的看着屏幕前的你思考的样子&#xff01; 也就是说&#xff0c;spring启动的时候就创建好了这个bean工厂&#xff01;

咦&#xff1f;不是要将BeanDefinitino的继承关系码&#xff1f;怎么跑偏了&#xff1f;这就是spring的特点&#xff0c;太庞大了&#xff0c;没有孤立的知识点&#xff01;这也是很多读者阅读spring源码时读着读着就蒙圈的原因。

后置处理器的引出

上文我们通过手动将InterService封装成了一个BeanDefinition然后注册(说好听了叫注册&#xff0c;起始就是map.put)到了容器中&#xff0c;我说了现在我们没有这么用的了&#xff0c;都是spring自动帮我们完成扫描注册&#xff0c;在哪完成的扫描注册&#xff1f;回到下面这几行代码&#xff1a;

AnnotationConfigApplicationContext context &#61; new AnnotationConfigApplicationContext();

//注册配置类

context.register(Config.class);

//加载或者刷新当前的配置信息

context.refresh();

context.refresh()方法&#xff0c;完成了spring的启动、扫描、解析、实例化等一系列过程&#xff0c;这个方法完成的功能太多了&#xff0c;我们的扫描注册也是在这里完成的&#xff0c;进入到这个方法&#xff0c;找到这么一行代码&#xff1a;

...以上省略

invokeBeanFactoryPostProcessors(beanFactory);

...以下省略

翻译一下名字&#xff0c;执行bean工厂的后置处理器&#xff0c;这行代码完成了扫描与注册&#xff0c;我不带大家分析里面的代码&#xff0c;你只需要知道他的作用就行&#xff0c;这行代码执行完成后&#xff0c;我们只是把业务类InterService封装成了BeanDefinition而已&#xff0c;业务类InterService并没有实例化&#xff0c;在业务类InterService实例化之前我们能不能从beanDefinition中将InterService偷梁换柱呢&#xff1f;或者说&#xff0c;我们能通过BeanDefinition来构建bean&#xff0c;那我们能不能修改bean呢&#xff1f;那必须的&#xff01;

通过后置处理器完成&#xff0c;什么是后置处理器&#xff1f;可以把它理解成回调&#xff0c;我扫描注册成功后回调后置处理器&#xff01;BeanDefinition讲完后紧接着就讲后置处理器。我们添加一个后置处理器&#xff1a;

/**

* 扫描注册成功完成后&#xff0c;spring自动调用后置处理器MyBeanFactoryPostProcessor的postProcessBeanFactory方法

*/

&#64;Component

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

&#64;Override

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)

throws BeansException {

//通过bean工厂拿到业务类InterService的beanDefinition

GenericBeanDefinition beanDefinition &#61;

(GenericBeanDefinition) beanFactory.getBeanDefinition("interService");

System.out.println("扫描注册成功完成后&#xff0c;spring自动调用次方法");

System.out.println(beanDefinition.getDescription());

}

}

spring扫描注册完成后&#xff0c;会自动调用MyBeanFactoryPostProcessor的postProcessBeanFactory方法&#xff0c;这个方法给你传递了一个ConfigurableListableBeanFactory类型的bean工厂&#xff0c;ConfigurableListableBeanFactory是一个接口&#xff0c;上文spring启动实例化的DefaultListableBeanFactory工厂是它的实现类。天啊&#xff0c;竟然把bean工厂给你了&#xff0c;相当于敌人把军火库暴露在你面前&#xff0c;你岂不是想干嘛就干嘛&#xff01;上述代码我们通过bean工厂拿到了业务类InterService的beanDefinition&#xff0c;我都拿到你的beanDefinition了&#xff0c;那么我不但可以get到你的信息&#xff0c;我也可以set你的信息从而改变你的行为来影响你后续的实例化。我们来编写另一个业务类&#xff1a;

public class User {

private int age &#61;31;

private String name&#61;"myname";

}

spring启动时也会把这个业务类扫描&#xff0c;接下来&#xff0c;看好了&#xff0c;我在bean工厂中偷梁换柱&#xff0c;在beanDefinition中将你的InterService业务类替换掉&#xff1a;

/**

* 扫描注册成功完成后&#xff0c;spring自动调用后置处理器MyBeanFactoryPostProcessor的postProcessBeanFactory方法

*/

&#64;Component

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

&#64;Override

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)

throws BeansException {

GenericBeanDefinition beanDefinition &#61;

(GenericBeanDefinition) beanFactory.getBeanDefinition("interService");

System.out.println(beanDefinition.getBeanClassName());

System.out.println("开始偷梁换柱");

beanDefinition.setBeanClass(User.class);

}

}

测试一下&#xff1a;

public class Test {

public static void main(String[] args) {

AnnotationConfigApplicationContext context &#61; new AnnotationConfigApplicationContext();

//注册配置类

context.register(Config.class);

context.refresh();

System.out.println("更改后的业务类&#xff1a;"&#43;context.getBeanDefinition("interService").getBeanClassName());

}

}

打印结果&#xff1a;

//我们尝试获取InterService实例

context.getBean(InterService.class);

}

}

打印结果报错&#xff0c;因为InterService不存在spring当中了&#xff1a;

getBeanDefinition方法返回的BeanDefinition类型&#xff0c;为什么强转成GenericBeanDefinition&#xff0c;起始BeanDefinition接口中并没有setBeanClass这个方法&#xff0c;GenericBeanDefinition是他的实现&#xff0c;提供更丰富的功能。不同的BeanDefinition实现具有不同的作用。

下一篇我们详细讲一下不同BeanDefinition的作用&#xff0c;BeanDefinition学精通后你基本迈入了spring源码大门。



推荐阅读
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 标题: ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 解决java.lang.IllegalStateException: ApplicationEventMulticaster not initialized错误的方法和原因
    本文介绍了解决java.lang.IllegalStateException: ApplicationEventMulticaster not initialized错误的方法和原因。其中包括修改包名、解决service name重复、处理jar包冲突和添加maven依赖等解决方案。同时推荐了一个人工智能学习网站,该网站内容通俗易懂,风趣幽默,值得一看。 ... [详细]
  • 本文讨论了在Spring 3.1中,数据源未能自动连接到@Configuration类的错误原因,并提供了解决方法。作者发现了错误的原因,并在代码中手动定义了PersistenceAnnotationBeanPostProcessor。作者删除了该定义后,问题得到解决。此外,作者还指出了默认的PersistenceAnnotationBeanPostProcessor的注册方式,并提供了自定义该bean定义的方法。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了Java的集合及其实现类,包括数据结构、抽象类和具体实现类的关系,详细介绍了List接口及其实现类ArrayList的基本操作和特点。文章通过提供相关参考文档和链接,帮助读者更好地理解和使用Java的集合类。 ... [详细]
  • springmvc学习笔记(十):控制器业务方法中通过注解实现封装Javabean接收表单提交的数据
    本文介绍了在springmvc学习笔记系列的第十篇中,控制器的业务方法中如何通过注解实现封装Javabean来接收表单提交的数据。同时还讨论了当有多个注册表单且字段完全相同时,如何将其交给同一个控制器处理。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 集合的遍历方式及其局限性
    本文介绍了Java中集合的遍历方式,重点介绍了for-each语句的用法和优势。同时指出了for-each语句无法引用数组或集合的索引的局限性。通过示例代码展示了for-each语句的使用方法,并提供了改写为for语句版本的方法。 ... [详细]
  • 在springmvc框架中,前台ajax调用方法,对图片批量下载,如何弹出提示保存位置选框?Controller方法 ... [详细]
author-avatar
火立者
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有