Spring 源码第六弹!容器的始祖 DefaultListableBeanFactory(1)

1.DefaultListableBeanFactory


要说 XmlBeanFactory 就不得不先说它的父类 DefaultListableBeanFactory,因为 XmlBeanFactory 中的大部分功能实际上在 DefaultListableBeanFactory 中就已经提供好了,XmlBeanFactory 只是对 IO 流的读取做了一些定制而已。

DefaultListableBeanFactory 是一个完整的、功能成熟的 IoC 容器,如果你的需求很简单,甚至可以直接使用 DefaultListableBeanFactory,如果你的需求比较复杂,那么通过扩展 DefaultListableBeanFactory 的功能也可以达到,可以说 DefaultListableBeanFactory 是整个 Spring IoC 容器的始祖。

我们先来看一下 DefaultListableBeanFactory 的继承关系:

从这张类的关系图中可以看出,DefaultListableBeanFactory 实际上也是一个集大成者。在 Spring 中,针对 Bean 的不同操作都有不同的接口进行规范,每个接口都有自己对应的实现,最终在 DefaultListableBeanFactory 中将所有的实现汇聚到一起。从这张类的继承关系图中我们大概就能感受到 Spring 中关于类的设计是多么厉害,代码耦合度非常低。

这些类,在本系列后面的介绍中,大部分都会涉及到,现在我先大概介绍一下每个类的作用,大家先混个脸熟:

  1. BeanFactory:这个接口看名字就知道是一个 Bean 的工厂,BeanFactory 接口定义了各种获取 Bean 的方法、判断 Bean 是否存在、判断 Bean 是否单例等针对 Bean 的基础方法。

  2. ListableBeanFactory:这个接口继承自 BeanFactory,在 BeanFactory 的基础上,扩展了 Bean 的查询方法,例如根据类型获取 BeanNames、根据注解获取 BeanNames、根据 Bean 获取注解等。

  3. AutowireCapableBeanFactory:该接口继承自 BeanFactory,在 BeanFactory 的基础上,提供了 Bean 的创建、配置、注入、销毁等操作。有时候我们需要自己手动注入 Bean 的时候,可以考虑通过实现该接口来完成。AutowireCapableBeanFactory 在 Spring Security 中有一个重要的应用就是 ObjectPostProcessor,这个松哥将在 Spring Security 系列中和大家详细介绍。

  4. HierarchicalBeanFactory:该接口继承自 BeanFactory,并在 BeanFactory 基础上添加了获取 parent beanfactory 的方法。

  5. SingletonBeanRegistry:这个接口定义了对单例 Bean 的定义以及获取方法。

  6. ConfigurableBeanFactory:这个接口主要定了针对 BeanFactory 的各种配置以及销毁的方法。

  7. ConfigurableListableBeanFactory:这是 BeanFactory 的配置清单,这里定义了忽略的类型、接口,通过 Bean 的名称获取 BeanDefinition 、冻结 BeanDefinition 等。

  8. AliasRegistry:这个接口定义了对 alias 的注册、移除、判断以及查询操作。

  9. SimpleAliasRegistry:这个类实现了 AliasRegistry 接口并实现了它里边的方法,SimpleAliasRegistry 使用 ConcurrentHashMap 做载体,实现了对 alias 的注册、移除判断以及查询操作。

  10. DefaultSingletonBeanRegistry:这个类基于 Java 中的集合,对 SingletonBeanRegistry 接口进行了实现。

  11. FactoryBeanRegistrySupport:该类继承自 DefaultSingletonBeanRegistry,并在 DefaultSingletonBeanRegistry 的基础上,增加了获取 FactoryBean 类型、移除 FactoryBean 缓存的方法等等操作。

  12. AbstractBeanFactory:实现了 ConfigurableBeanFactory 接口并继承自 FactoryBeanRegistrySupport,在 AbstractBeanFactory 中对 ConfigurableBeanFactory 中定义的方法进行了实现。

  13. AbstractAutowireCapableBeanFactory:该类继承自 AbstractBeanFactory 并对 AutowireCapableBeanFactory 接口中定义的方法进行了落地实现。

  14. BeanDefinitionRegistry:这个接口继承自 AliasRegistry 接口,并增加了一系列针对 BeanDefinition 的注册、移除、查询、判断等方法。

  15. 最后的 DefaultListableBeanFactory 自然就具备了上面所有的功能。

上面的内容可能看的大家眼花缭乱,松哥这里通过几个简单实际的例子,来带大家使用一下 DefaultListableBeanFactory 的功能,可能大家的理解就比较清晰了。

DefaultListableBeanFactory 作为一个集大成者,提供了非常多的功能,我们一个一个来看。

2.代码改造


首先文章中一开始的三行代码我们可以对其略加改造,因为我们已经说了 XmlBeanFactory 中的大部分功能实际上在 DefaultListableBeanFactory 中就已经提供好了,XmlBeanFactory 只是对 IO 流的读取做了一些定制而已,文件的读取主要是通过 XmlBeanDefinitionReader 来完成的(本系列前面文章已经讲过),我们可以对文章一开始的三行代码进行改造,以便更好的体现“XmlBeanFactory 中的大部分功能实际上在 DefaultListableBeanFactory 中就已经提供好了”:

ClassPathResource res=new ClassPathResource(“beans.xml”);

DefaultListableBeanFactory factory=new DefaultListableBeanFactory();

XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(factory);

reader.loadBeanDefinitions(res);

User user = factory.getBean(User.class);

System.out.println("user = " + user);

使用前四行代码代替 XmlBeanFactory,这样 XmlBeanFactory 的功能是不是就很明确了?就是前四行代码的功能。

3.动态注册 Bean


动态注册 Bean,这是 DefaultListableBeanFactory 的功能之一,不过准确来说应该是动态注册 BeanDefinition 。

我们先来看一个简单的例子:

DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();

GenericBeanDefinition userBeanDefinition = new GenericBeanDefinition();

MutablePropertyValues pvs = new MutablePropertyValues();

pvs.add(“username”, “javaboy”);

pvs.add(“address”, “www.javaboy.org”);

userBeanDefinition.setPropertyValues(pvs);

userBeanDefinition.setBeanClass(User.class);

defaultListableBeanFactory.registerBeanDefinition(“user”, userBeanDefinition);

User user = defaultListableBeanFactory.getBean(User.class);

System.out.println("user = " + user);

首先我们自己手动构建一个 DefaultListableBeanFactory 对象。当然也可以使用前面的 XmlBeanFactory。

然后再手动构建一个 GenericBeanDefinition。在前面的文章中,松哥和大家讲过,现在默认使用的 BeanDefinition 就是 GenericBeanDefinition,所以这里我们自己也手动构建一个 GenericBeanDefinition。有了 GenericBeanDefinition 之后,我们设置相关的类和属性。

接下来再将 userBeanDefinition 注册到 defaultListableBeanFactory。注册完成之后,我们就可以从 defaultListableBeanFactory 中获取相应的 Bean 了。

这里说一句题外话,希望大家在阅读本系列每一篇文章的时候,能够将本系列前后文章联系起来一起理解,这样会有很多意料之外的收获。例如上面的,我们既可以声明一个 DefaultListableBeanFactory,也可以声明一个 XmlBeanFactory,那你大概就能据此推断出 XmlBeanFactory 的主要目的可能就是对资源文件进行读取和注册。

那么到底是怎么注册的呢?我们来看一下 defaultListableBeanFactory.registerBeanDefinition 方法的定义:

@Override

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)

throws BeanDefinitionStoreException {

Assert.hasText(beanName, “Bean name must not be empty”);

Assert.notNull(beanDefinition, “BeanDefinition must not be null”);

if (beanDefinition instanceof AbstractBeanDefinition) {

try {

((AbstractBeanDefinition) beanDefinition).validate();

}

catch (BeanDefinitionValidationException ex) {

throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,

“Validation of bean definition failed”, ex);

}

}

BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);

if (existingDefinition != null) {

if (!isAllowBeanDefinitionOverriding()) {

throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);

}

else if (existingDefinition.getRole() < beanDefinition.getRole()) {

// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE

if (logger.isInfoEnabled()) {

logger.info(“Overriding user-defined bean definition for bean '” + beanName +

“’ with a framework-generated bean definition: replacing [” +

existingDefinition + “] with [” + beanDefinition + “]”);

}

}

else if (!beanDefinition.equals(existingDefinition)) {

if (logger.isDebugEnabled()) {

logger.debug(“Overriding bean definition for bean '” + beanName +

“’ with a different definition: replacing [” + existingDefinition +

“] with [” + beanDefinition + “]”);

}

}

else {

if (logger.isTraceEnabled()) {

logger.trace(“Overriding bean definition for bean '” + beanName +

“’ with an equivalent definition: replacing [” + existingDefinition +

“] with [” + beanDefinition + “]”);

}

}

this.beanDefinitionMap.put(beanName, beanDefinition);

}

else {

if (hasBeanCreationStarted()) {

// Cannot modify startup-time collection elements anymore (for stable iteration)

synchronized (this.beanDefinitionMap) {

this.beanDefinitionMap.put(beanName, beanDefinition);

List updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);

updatedDefinitions.addAll(this.beanDefinitionNames);

updatedDefinitions.add(beanName);

this.beanDefinitionNames = updatedDefinitions;

removeManualSingletonName(beanName);

}

}

else {

// Still in startup registration phase

this.beanDefinitionMap.put(beanName, beanDefinition);

this.beanDefinitionNames.add(beanName);

removeManualSingletonName(beanName);

}

this.frozenBeanDefinitionNames = null;

}

if (existingDefinition != null || containsSingleton(beanName)) {

resetBeanDefinition(beanName);

}

else if (isConfigurationFrozen()) {

clearByTypeCache();

}

}

registerBeanDefinition 方法是在 BeanDefinitionRegistry 接口中声明的,DefaultListableBeanFactory 类实现了 BeanDefinitionRegistry 接口,并实现了该方法,我们来看分析下该方法:

  1. 首先对传入的 beanDefinition 对象进行校验,这也是注册前的最后一次校验,不过这个时候 BeanDefinition 对象已经到手了,所以这个校验并非 XML 文件校验,这里主要是对 methodOverrides 的校验。

  2. 接下来会根据 beanName 从 beanDefinitionMap 中获取 BeanDefinition,看看当前 Bean 是否已经定义过了。beanDefinitionMap 是一个 Map 集合,这个集合中 key 是 beanName,value 是 BeanDefinition 对象。

  3. 如果 BeanDefinition 已经存在了,那么接下来会判断是否允许 BeanDefinition 覆盖,如果不允许,就直接抛出异常(不知道小伙伴们有没有印象,在松哥前面的 OAuth2 系列教程中,经常需要配置允许 BeanDefinition 的覆盖,就是因为这个原因,公众号【江南一点雨】后台回复 OAuth2 获取该教程),如果允许 BeanDefinition 的覆盖,那就向 beanDefinitionMap 中再次存一次值,覆盖之前的值。

  4. 如果 BeanDefinition 不存在,那就直接注册。直接注册分两种情况:项目已经运行了和项目还没运行。

  5. 如果项目已经运行,由于 beanDefinitionMap 是一个全局变量,可能存在并发问题,所以要加锁处理。否则就直接注册,所谓的注册就是把对象存入 beanDefinitionMap 中,同时将 beanName 都存入 beanDefinitionNames 集合中。

这便是 registerBeanDefinition 方法的工作流程。

有小伙伴会说,这个方法从头到尾都是 BeanDefinition,跟 Bean 有什么关系呢?

咋一看确实好像和 Bean 没有直接关系。

其实这涉及到另外一个问题,就是 Bean 的懒加载。这个时候先把 BeanDefinition 定义好,等到真正调用 Bean 的时候,才会去初始化 Bean。我们可以在 User 类的构造方法中打印日志看下,如下:

public class User {

private String username;

private String address;

public User() {

System.out.println(“--------user init--------”);

}

@Override

public String toString() {

return “User{” +

“username='” + username + ‘’’ +

“, address='” + address + ‘’’ +

‘}’;

}

public String getUsername() {

return username;

}

public void setUsername(String username) {

this.username = username;

}

public String getAddress() {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

Kafka实战笔记

关于这份笔记,为了不影响大家的阅读体验,我只能在文章中展示部分的章节内容和核心截图

image.png

  • Kafka入门
  • 为什么选择Kafka
  • Karka的安装、管理和配置

image.png

  • Kafka的集群
  • 第一个Kafka程序
  • image.png

afka的生产者

image.png

  • Kafka的消费者
  • 深入理解Kafka
  • 可靠的数据传递

image.png

image.png

  • Spring和Kalka的整合
  • Sprinboot和Kafka的整合
  • Kafka实战之削峰填谷
  • 数据管道和流式处理(了解即可)

image.png

  • Kafka实战之削峰填谷

image.png

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

**

  • 数据管道和流式处理(了解即可)

[外链图片转存中…(img-yowuVTTc-1712184695890)]

  • Kafka实战之削峰填谷

[外链图片转存中…(img-vHy8MxJM-1712184695890)]

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值