博客记录-day008-Stack、HashMap+MVC架构

147 阅读20分钟

一、沉默王二-集合框架

1、Stack

其实 Java 已经帮我们实现了一个栈,就是 java.util.Stack,它继承自 Vector,是线程安全的,有点 StringBuffer 的感觉。 我们来看一下 push 方法的源码:

public E push(E item) { 
     addElement(item); 
  
     return item; 
 }

push 方法虽然没有 synchronized 关键字,但调用了 Vector 类的 addElement 方法,该方法上添加了 synchronized 关键字。

public synchronized void addElement(E obj) { 
     modCount++; 
     ensureCapacityHelper(elementCount + 1); 
     elementData[elementCount++] = obj; 
 }

再来看一下 pop 方法的源码:

public synchronized E pop() { 
     E obj; 
     int len = size(); 
  
     obj = peek(); 
     removeElementAt(len - 1); 
  
     return obj; 
 }

该方法添加了 synchronized 关键字,并且先调用 peek 方法获取到栈顶元素:

public synchronized E peek() {
    int     len = size();

    if (len == 0)
        throw new EmptyStackException();
    return elementAt(len - 1);
}

接着调用 Vector 类的 removeElementAt 方法移除栈顶元素。

虽然 Stack 类并不常用,但栈这个数据结构却很重要。在 Java 中,推荐使用 ArrayDeque 来代替 Stack,因为 ArrayDeque) 是非线程安全的,性能更好。

2、HashMap

HashMap 是 Java 中常用的数据结构之一,用于存储键值对。在 HashMap 中,每个键都映射到一个唯一的值,可以通过键来快速访问对应的值,算法时间复杂度可以达到 O(1)。

1)增加元素

将一个键值对(元素)添加到 HashMap 中,可以使用 put() 方法。例如,将名字和年龄作为键值对添加到 HashMap 中:

HashMap<String, Integer> map = new HashMap<>();
map.put("沉默", 20);
map.put("王二", 25);

2)删除元素

从 HashMap 中删除一个键值对,可以使用 remove() 方法。例如,删除名字为 "沉默" 的键值对:

map.remove("沉默");

3)修改元素

修改 HashMap 中的一个键值对,可以使用 put() 方法。例如,将名字为 "沉默" 的年龄修改为 30:

map.put("沉默", 30);

为什么和添加元素的方法一样呢?这个我们后面会讲,先简单说一下,是因为 HashMap 的键是唯一的,所以再次 put 的时候会覆盖掉之前的键值对。

4)查找元素

从 HashMap 中查找一个键对应的值,可以使用 get() 方法。例如,查找名字为 "沉默" 的年龄:

int age = map.get("沉默");

5)遍历元素

// 创建 HashMap 对象,键类型为 String,值类型为 String
Map<String, String> map = new HashMap<>();

// 使用 put() 方法向 HashMap 中添加数据
map.put("chenmo", "沉默");
map.put("wanger", "王二");
map.put("chenqingyang", "陈清扬");

// 遍历 HashMap,输出所有键值对
for (Map.Entry<String, String> entry : map.entrySet()) {
    String key = entry.getKey();
    String value = entry.getValue();
    System.out.println("Key: " + key + ", Value: " + value);
}
// 遍历 HashMap 
for (String key : hashMap.keySet()) { 
    String value = hashMap.get(key); 
    System.out.println(key + " 对应的值为:" + value); 
}

1)hash方法原理

来看一下 hash 方法的源码(JDK 8 中的 HashMap):

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

我们来 new 一个 HashMap,并通过 put 方法添加一个元素。

HashMap<String, String> map = new HashMap<>();
map.put("chenmo", "沉默");

来看一下 put 方法的源码。

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

这段代码是将 key 的 hashCode 值进行处理,得到最终的哈希值

HashMap 的底层是通过数组的形式实现的,初始大小是 16。 怎么确定位置(索引)呢?

我先告诉大家结论,通过这个与运算 (n - 1) & hash,其中变量 n 为数组的长度,变量 hash 就是通过 hash() 方法计算后的结果。那“chenmo”这个 key 计算后的位置(索引)是多少呢? 答案是 8,也就是说 map.put("chenmo", "沉默") 会把 key 为 “chenmo”,value 为“沉默”的键值对放到下标为 8 的位置上(也就是索引为 8 的桶上)。

hash 方法是用来做哈希值优化的,把哈希值右移 16 位,也就正好是自己长度的一半,之后与原哈希值做异或运算,这样就混合了原哈希值中的高位和低位,增大了随机性。

说白了,hash 方法就是为了增加随机性,让数据元素更加均衡的分布,减少碰撞

hash 方法的主要作用是将 key 的 hashCode 值进行处理,得到最终的哈希值。由于 key 的 hashCode 值是不确定的,可能会出现哈希冲突,因此需要将哈希值通过一定的算法映射到 HashMap 的实际存储位置上。

hash 方法的原理是,先获取 key 对象的 hashCode 值,然后将其高位与低位进行异或操作,得到一个新的哈希值。为什么要进行异或操作呢?因为对于 hashCode 的高位和低位,它们的分布是比较均匀的,如果只是简单地将它们加起来或者进行位运算,容易出现哈希冲突,而异或操作可以避免这个问题。

然后将新的哈希值取模(mod),得到一个实际的存储位置。这个取模操作的目的是将哈希值映射到桶(Bucket)的索引上,桶是 HashMap 中的一个数组,每个桶中会存储着一个链表(或者红黑树),装载哈希值相同的键值对(没有相同哈希值的话就只存储一个键值对)。

总的来说,HashMap 的 hash 方法就是将 key 对象的 hashCode 值进行处理,得到最终的哈希值,并通过一定的算法映射到实际的存储位置上。这个过程决定了 HashMap 内部键值对的查找效率。

2)HashMap 的扩容机制

当存储的哈希值相同时,需要使用拉链法存储。

HashMap 的底层用的也是数组。向 HashMap 里不停地添加元素,当数组无法装载更多元素时,就需要对数组进行扩容,以便装入更多的元素;除此之外,容量的提升也会相应地提高查询效率,因为“桶(坑)”更多了嘛,原来需要通过链表存储的(查询的时候需要遍历),扩容后可能就有自己专属的“坑位”了(直接就能查出来)。

1.resize方法

HashMap 的扩容是通过 resize 方法来实现的,JDK 8 中融入了红黑树(链表长度超过 8 的时候,会将链表转化为红黑树来提高查询效率),对于新手来说,可能比较难理解。

为了减轻大家的学习压力,就还使用 JDK 7 的源码,搞清楚了 JDK 7 的,再看 JDK 8 的就会轻松很多。

来看 Java7 的 resize 方法源码,我加了注释:

// newCapacity为新的容量
void resize(int newCapacity) {
    // 小数组,临时过度下
    Entry[] oldTable = table;
    // 扩容前的容量
    int oldCapacity = oldTable.length;
    // MAXIMUM_CAPACITY 为最大容量,2 的 30 次方 = 1<<30
    if (oldCapacity == MAXIMUM_CAPACITY) {
        // 容量调整为 Integer 的最大值 0x7fffffff(十六进制)=2 的 31 次方-1
        threshold = Integer.MAX_VALUE;
        return;
    }

    // 初始化一个新的数组(大容量)
    Entry[] newTable = new Entry[newCapacity];
    // 把小数组的元素转移到大数组中
    transfer(newTable, initHashSeedAsNeeded(newCapacity));
    // 引用新的大数组
    table = newTable;
    // 重新计算阈值
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

该方法接收一个新的容量 newCapacity,然后将 HashMap 的容量扩大到 newCapacity。

计算 newCapacity。

// 将旧容量左移一位,计算新的容量(即旧容量的两倍)  
int newCapacity = oldCapacity << 1;  

// 检查新容量和旧容量是否都大于或等于默认初始容量  
if (newCapacity >= DEFAULT_INITIAL_CAPACITY && oldCapacity >= DEFAULT_INITIAL_CAPACITY) {  
    // 如果新容量大于最大容量,设置新容量为最大容量  
    if (newCapacity > MAXIMUM_CAPACITY)  
        newCapacity = MAXIMUM_CAPACITY;  
} else {  
    // 如果新容量小于默认初始容量,设置新容量为默认初始容量16  
    if (newCapacity < DEFAULT_INITIAL_CAPACITY)  
        newCapacity = DEFAULT_INITIAL_CAPACITY;  
}
2.Java8扩容

1、获取原来的数组 table、数组长度 oldCap 和阈值 oldThr。

2、如果原来的数组 table 不为空,则根据扩容规则计算新数组长度 newCap 和新阈值 newThr,然后将原数组中的元素复制到新数组中。

3、如果原来的数组 table 为空但阈值 oldThr 不为零,则说明是通过带参数构造方法创建的 HashMap,此时将阈值作为新数组长度 newCap。

4、如果原来的数组 table 和阈值 oldThr 都为零,则说明是通过无参数构造方法创建的 HashMap,此时将默认初始容量 DEFAULT_INITIAL_CAPACITY(16)和默认负载因子 DEFAULT_LOAD_FACTOR(0.75)计算出新数组长度 newCap 和新阈值 newThr。

5、计算新阈值 threshold,并将其赋值给成员变量 threshold。

6、创建新数组 newTab,并将其赋值给成员变量 table。

7、如果旧数组 oldTab 不为空,则遍历旧数组的每个元素,将其复制到新数组中。

8、返回新数组 newTab。

JDK 8 扩容索引是这样:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

image.png

将键的hashCode()返回的 32 位哈希值与这个哈希值无符号右移 16 位的结果进行异或。在 JDK 8 的新 hash 算法下,数组扩容后的索引位置,要么就是原来的索引位置,要么就是“原索引+原来的容量”,遵循一定的规律。

当我们往 HashMap 中不断添加元素时,HashMap 会自动进行扩容操作(条件是元素数量达到负载因子(load factor)乘以数组长度时),以保证其存储的元素数量不会超出其容量限制。

在进行扩容操作时,HashMap 会先将数组的长度扩大一倍,然后将原来的元素重新散列到新的数组中。

由于元素的位置是通过 key 的 hash 和数组长度进行与运算得到的,因此在数组长度扩大后,元素的位置也会发生一些改变。一部分索引不变,另一部分索引为“原索引+旧容量”。

3)加载因子

上一个问题提到了加载因子(或者叫负载因子),那么这个问题我们来讨论为什么加载因子是 0.75 而不是 0.6、0.8。

我们知道,HashMap 是用数组+链表/红黑树实现的,我们要想往 HashMap 中添加数据(元素/键值对)或者取数据,就需要确定数据在数组中的下标(索引)

先把数据的键进行一次 hash:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

再做一次取模运算确定下标:

i = (n - 1) & hash

那这样的过程容易产生两个问题:

  • 数组的容量过小,经过哈希计算后的下标,容易出现冲突;
  • 数组的容量过大,导致空间利用率不高。

一开始,HashMap 的容量是 16, 加载因子是 0.75:

也就是说,当 16*0.75=12 时,会触发扩容机制。

加载因子是用来表示 HashMap 中数据的填满程度:

加载因子 = 填入哈希表中的数据个数 / 哈希表的长度

Java 8 之前,HashMap 使用链表来解决冲突,即当两个或者多个键映射到同一个桶时,它们被放在同一个桶的链表上。当链表上的节点(Node)过多时,链表会变得很长,查找的效率(LinkedList 的查找效率为 O(n))就会受到影响。

Java 8 中,当链表的节点数超过一个阈值(8)时,链表将转为红黑树(节点为 TreeNode),红黑树是一种高效的平衡树结构,能够在 O(log n) 的时间内完成插入、删除和查找等操作。这种结构在节点数很多时,可以提高 HashMap 的性能和可伸缩性。

4)线程不安全

线程不安全三方面原因:

  • 多线程下扩容会死循环
  • 多线程下 put 会导致元素丢失
  • put 和 get 并发时会导致 get 到 null
1.多线程下扩容会死循环

众所周知,HashMap 是通过拉链法来解决哈希冲突的,也就是当哈希冲突时,会将相同哈希值的键值对通过链表的形式存放起来。

JDK 7 时,采用的是头部插入的方式来存放链表的,也就是下一个冲突的键值对会放在上一个键值对的前面(讲扩容的时候讲过了)。扩容的时候就有可能导致出现环形链表,造成死循环。

2.多线程下 put 会导致元素丢失

正常情况下,当发生哈希冲突时,HashMap 是这样的:

但多线程同时执行 put 操作时,如果计算出来的索引位置是相同的,那会造成前一个 key 被后一个 key 覆盖,从而导致元素的丢失。

3.put 和 get 并发时会导致 get 到 null

线程 1 执行 put 时,因为元素个数超出阈值而导致出现扩容,线程 2 此时执行 get,就有可能出现这个问题。

因为线程 1 执行完 table = newTab 之后,线程 2 中的 table 此时也发生了变化,此时去 get 的时候当然会 get 到 null 了,因为元素还没有转移

二、小博哥-编程基础

1、MVC架构

MVC 是一种非常常见且常用的分层架构,主要包括;M - mode 对象层,封装到 domain 里。V - view 展示层,但因为目前都是前后端分离的项目,几乎不会在后端项目里写 JSP 文件了。C - Controller 控制层,对外提供接口实现类。DAO 算是单独拿出来用户处理数据库操作的层。

  • 如图,在 MVC 的分层架构下。我们编程3步的所需各类对象、方法、接口,都分配到 MVC 的各个层次中去。
  • 因为这样分层以后,就可以很清晰明了的知道各个层都在做什么内容,也更加方便后续的维护和迭代。

接下来我们再看下一套 MVC 架构中各个模块在调用时的串联关系;

  • 以用户发起 HTTP 请求开始,Controller 在接收到请求后,调用由 Spring 注入到类里的 Service 方法,进入 Service 方法后有些逻辑会走数据库,有些逻辑是直接内部自己处理后就直接返回给 Controller 了。最后由 Controller 封装结果返回给 HTTP 响应。
  • 同时我们也可以看到各个对象在这些请求间的一个作用,如;请求对象、库表对象、返回对象。

三、小博哥-spring

1、bean的定义、注册、获取

这一次我们把 Bean 的创建交给容器,而不是我们在调用时候传递一个实例化好的 Bean 对象,另外还需要考虑单例对象,在对象的二次获取时是可以从内存中获取对象的。此外不仅要实现功能还需要完善基础容器框架的类结构体,否则将来就很难扩容进去其他的功能了。

鉴于本章节的案例目标,我们需要将 Spring Bean 容器完善起来,首先非常重要的一点是在 Bean 注册的时候只注册一个类信息,而不会直接把实例化信息注册到 Spring 容器中。那么就需要修改 BeanDefinition 中的属性 Object 为 Class,接下来在需要做的就是在获取 Bean 对象时需要处理 Bean 对象的实例化操作以及判断当前单例对象在容器中是否已经缓存起来了。整体设计如图

  • 首先我们需要定义 BeanFactory 这样一个 Bean 工厂,提供 Bean 的获取方法 getBean(String name),之后这个 Bean 工厂接口由抽象类 AbstractBeanFactory 实现。这样使用模板模式 (opens new window)的设计方式,可以统一收口通用核心方法的调用逻辑和标准定义,也就很好的控制了后续的实现者不用关心调用逻辑,按照统一方式执行。那么类的继承者只需要关心具体方法的逻辑实现即可。
  • 那么在继承抽象类 AbstractBeanFactory 后的 AbstractAutowireCapableBeanFactory 就可以实现相应的抽象方法了,因为 AbstractAutowireCapableBeanFactory 本身也是一个抽象类,所以它只会实现属于自己的抽象方法,其他抽象方法由继承 AbstractAutowireCapableBeanFactory 的类实现。这里就体现了类实现过程中的各司其职,你只需要关心属于你的内容,不是你的内容,不要参与。
  • 另外这里还有块非常重要的知识点,就是关于单例 SingletonBeanRegistry 的接口定义实现,而 DefaultSingletonBeanRegistry 对接口实现后,会被抽象类 AbstractBeanFactory 继承。现在 AbstractBeanFactory 就是一个非常完整且强大的抽象类了,也能非常好的体现出它对模板模式的抽象定义。

工程结构:

small-spring-step-02
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.springframework.beans
    │           ├── factory
    │           │   ├── config
    │           │   │   ├── BeanDefinition.java
    │           │   │   └── SingletonBeanRegistry.java
    │           │   ├── support
    │           │   │   ├── AbstractAutowireCapableBeanFactory.java
    │           │   │   ├── AbstractBeanFactory.java
    │           │   │   ├── BeanDefinitionRegistry.java
    │           │   │   ├── DefaultListableBeanFactory.java
    │           │   │   └── DefaultSingletonBeanRegistry.java
    │           │   └── BeanFactory.java
    │           └── BeansException.java
    └── test
        └── java
            └── cn.bugstack.springframework.test
                ├── bean
                │   └── UserService.java
                └── ApiTest.java

image.png

这个图展示了一个关于Spring框架中Bean工厂的类图结构。以下是各个部分的描述:

  1. SingletonBeanRegistry接口: 这个接口定义了获取单例Bean的方法getSingleton(String beanName)。它的目的是提供一种机制来管理和访问单例对象。

  2. AbstractBeanFactory类:实现了BeanFactory接口,提供了获取Bean和创建Bean的基本实现。它包含方法getBean(String name)getBeanDefinition(String beanName)createBean(String beanName, BeanDefinition beanDefinition)

  3. AbstractAutowireCapableBeanFactory类:继承自AbstractBeanFactory,增加了自动装配的能力,提供了createBean(String beanName, BeanDefinition beanDefinition)方法。

  4. DefaultListableBeanFactory类:是AbstractAutowireCapableBeanFactory的具体实现,维护了一个Bean定义的映射beanDefinitionMap,并提供了注册和获取Bean定义的方法。

  5. SingletonBeanRegistry接口:定义了获取单例Bean的方法getSingleton(String beanName)

  6. DefaultSingletonBeanRegistry类:实现了SingletonBeanRegistry接口,维护了一个单例对象的映射singletonObjects,并提供了添加单例对象的方法addSingleton(String beanName, Object singletonObject)

  7. BeanDefinitionRegistry接口:定义了注册Bean定义的方法registerBeanDefinition(String beanName, BeanDefinition beanDefinition)

  8. BeanDefinition类:包含了Bean的定义信息,如beanClass

虽然这一章节关于 Spring Bean 容器的功能实现与 Spring 源码中还有不少的差距,但以目前实现结果的类关系图来看,其实已经具备了一定的设计复杂性,这些复杂的类关系设计在各个接口定义和实现以及在抽象类继承中都有所体现,例如:

  • BeanFactory 的定义由 AbstractBeanFactory 抽象类实现接口的 getBean 方法
  • 而 AbstractBeanFactory 又继承了实现了 SingletonBeanRegistry 的DefaultSingletonBeanRegistry 类。这样 AbstractBeanFactory 抽象类就具备了单例 Bean 的注册功能。
  • AbstractBeanFactory 中又定义了两个抽象方法:getBeanDefinition(String beanName)、createBean(String beanName, BeanDefinition beanDefinition) ,而这两个抽象方法分别由 DefaultListableBeanFactory、AbstractAutowireCapableBeanFactory 实现。
  • 最终 DefaultListableBeanFactory 还会继承抽象类 AbstractAutowireCapableBeanFactory 也就可以调用抽象类中的 createBean 方法了。

2) BeanDefinition 定义

cn.bugstack.springframework.beans.factory.config.BeanDefinition

// 定义一个BeanDefinition类  
public class BeanDefinition {  

    // 声明一个私有变量beanClass,用于存储Bean的类信息  
    private Class beanClass;  

    // 构造函数,接受一个Class类型的参数beanClass,并初始化beanClass  
    public BeanDefinition(Class beanClass) {  
        this.beanClass = beanClass;  
    }  
    // ...get/set 方法用于获取和设置beanClass的值  
}
    
  • 在 Bean 定义类中已经把上一章节中的 Object bean 替换为 Class,这样就可以把 Bean 的实例化操作放到容器中处理了。如果你有仔细阅读过上一章并做了相应的测试,那么你会发现 Bean 的实例化操作是放在初始化调用阶段传递给 BeanDefinition 构造函数的

3) 单例注册接口定义和实现

cn.bugstack.springframework.beans.factory.config.SingletonBeanRegistry

// 定义一个接口SingletonBeanRegistry  
public interface SingletonBeanRegistry {  

    // 定义一个方法getSingleton,接受一个字符串参数beanName,返回一个Object对象  
    Object getSingleton(String beanName);  

}

  • 这个类比较简单主要是定义了一个获取单例对象的接口

cn.bugstack.springframework.beans.factory.support.DefaultSingletonBeanRegistry

// 定义一个类DefaultSingletonBeanRegistry,实现了SingletonBeanRegistry接口  
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {  

    // 声明一个私有变量singletonObjects,使用HashMap来存储单例对象  
    private Map<String, Object> singletonObjects = new HashMap<>();  

    // 重写getSingleton方法,根据beanName返回对应的单例对象  
    @Override  
    public Object getSingleton(String beanName) {  
        return singletonObjects.get(beanName); // 从singletonObjects中获取单例对象  
    }  

    // 声明一个受保护的方法addSingleton,用于添加单例对象  
    protected void addSingleton(String beanName, Object singletonObject) {  
        singletonObjects.put(beanName, singletonObject); // 将单例对象放入singletonObjects中  
    }  

}

  • 在 DefaultSingletonBeanRegistry 中主要实现 getSingleton 方法,同时实现了一个受保护的 addSingleton 方法,这个方法可以被继承此类的其他类调用。包括:AbstractBeanFactory 以及继承的 DefaultListableBeanFactory 调用。

4) 抽象类定义模板方法(AbstractBeanFactory)

cn.bugstack.springframework.beans.factory.support.AbstractBeanFactory

// 定义一个抽象类AbstractBeanFactory,继承自DefaultSingletonBeanRegistry并实现BeanFactory接口  
public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory {  

    // 重写getBean方法,根据给定的name获取Bean对象  
    @Override  
    public Object getBean(String name) throws BeansException {  
        // 从singleton中获取单例Bean  
        Object bean = getSingleton(name);  
        // 如果单例Bean不为空,直接返回该Bean  
        if (bean != null) {  
            return bean;  
        }  

        // 获取Bean的定义信息  
        BeanDefinition beanDefinition = getBeanDefinition(name);  
        // 根据Bean定义创建新的Bean实例  
        return createBean(name, beanDefinition);  
    }  

    // 声明一个抽象方法getBeanDefinition,用于获取BeanDefinition,子类需实现  
    protected abstract BeanDefinition getBeanDefinition(String beanName) throws BeansException;  

    // 声明一个抽象方法createBean,用于创建Bean实例,子类需实现  
    protected abstract Object createBean(String beanName, BeanDefinition beanDefinition) throws BeansException;  

}
  • AbstractBeanFactory 首先继承了 DefaultSingletonBeanRegistry,也就具备了使用单例注册类方法。
  • 接下来很重要的一点是关于接口 BeanFactory 的实现,在方法 getBean 的实现过程中可以看到,主要是对单例 Bean 对象的获取以及在获取不到时需要拿到 Bean 的定义做相应 Bean 实例化操作。那么 getBean 并没有自身的去实现这些方法,而是只定义了调用过程以及提供了抽象方法,由实现此抽象类的其他类做相应实现。
  • 后续继承抽象类 AbstractBeanFactory 的类有两个,包括:AbstractAutowireCapableBeanFactory、DefaultListableBeanFactory,这两个类分别做了相应的实现处理,接着往下看。

5)实例化Bean类(AbstractAutowireCapableBeanFactory)

cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory

// 定义一个抽象类AbstractAutowireCapableBeanFactory,继承自AbstractBeanFactory  
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {  

    // 重写createBean方法,根据Bean名称和Bean定义创建Bean实例  
    @Override  
    protected Object createBean(String beanName, BeanDefinition beanDefinition) throws BeansException {  
        // 声明一个Object类型的变量bean,初始化为null  
        Object bean = null;  
        try {  
            // 使用Bean定义中的类信息创建Bean的新实例  
            bean = beanDefinition.getBeanClass().newInstance();  
        } catch (InstantiationException | IllegalAccessException e) {  
            // 如果创建Bean实例失败,抛出BeansException,并传入错误信息和原始异常  
            throw new BeansException("Instantiation of bean failed", e);  
        }  

        // 将创建的Bean添加到单例注册表中,以便后续访问  
        addSingleton(beanName, bean);  
        // 返回创建的Bean实例  
        return bean;  
    }  

}
  • 在 AbstractAutowireCapableBeanFactory 类中实现了 Bean 的实例化操作 newInstance,其实这块会埋下一个坑,有构造函数入参的对象怎么处理?可以提前思考
  • 在处理完 Bean 对象的实例化后,直接调用 addSingleton 方法存放到单例对象的缓存中去。

6) 核心类实现(DefaultListableBeanFactory)

cn.bugstack.springframework.beans.factory.support.DefaultListableBeanFactory

// 定义一个类DefaultListableBeanFactory,继承自AbstractAutowireCapableBeanFactory并实现BeanDefinitionRegistry接口  
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements BeanDefinitionRegistry {  

    // 声明一个私有变量beanDefinitionMap,用于存储Bean的定义信息  
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();  

    // 重写registerBeanDefinition方法,用于注册Bean定义  
    @Override  
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {  
        // 将Bean名称和Bean定义存入beanDefinitionMap中  
        beanDefinitionMap.put(beanName, beanDefinition);  
    }  

    // 重写getBeanDefinition方法,根据Bean名称获取对应的Bean定义  
    @Override  
    public BeanDefinition getBeanDefinition(String beanName) throws BeansException {  
        // 从beanDefinitionMap中获取指定名称的Bean定义  
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);  
        // 如果Bean定义为null,则抛出BeansException,提示未定义此Bean  
        if (beanDefinition == null) throw new BeansException("No bean named '" + beanName + "' is defined");  
        // 返回获取到的Bean定义  
        return beanDefinition;  
    }  

}
    
  • DefaultListableBeanFactory 在 Spring 源码中也是一个非常核心的类,在我们目前的实现中也是逐步贴近于源码,与源码类名保持一致。
  • DefaultListableBeanFactory 继承了 AbstractAutowireCapableBeanFactory 类,也就具备了接口 BeanFactory 和 AbstractBeanFactory 等一连串的功能实现所以有时候你会看到一些类的强转,调用某些方法,也是因为你强转的类实现接口或继承了某些类。
  • 除此之外这个类还实现了接口 BeanDefinitionRegistry 中的 registerBeanDefinition(String beanName, BeanDefinition beanDefinition) 方法,当然你还会看到一个 getBeanDefinition 的实现,这个方法我们文中提到过它是抽象类 AbstractBeanFactory 中定义的抽象方法。现在注册Bean定义与获取Bean定义就可以同时使用了,是不感觉这个套路还蛮深的。接口定义了注册,抽象类定义了获取,都集中在 DefaultListableBeanFactory 中的 beanDefinitionMap 里