Spring之手写模拟Spring Bean的创建

简介: Spring之手写模拟Spring Bean的创建

一、前言

在前面, 我们通过文章Spring底层核心原理解析知道了在Spring中 bean的创建大致经历了以下步骤

  • 通过推断构造方法来实例化一个对象
  • 对该对象进行依赖注入(属性赋值)
  • 依赖注入后,Spring会判断该对象是否实现了BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了,就表示当前对象必须实现该接口中所定义的setBeanName()、setBeanClassLoader()、setBeanFactory()方法,那Spring就会调用这些方法并传入相应的参数(Aware回调
  • 初始化前( @PostConstruct)
  • 初始化(继承InitializingBean类)
  • 初始化后(AOP)

本篇文章会手写模拟实现spring创建bean的过程(简单实现):

  • 推断构造方法(只有无参)
  • 依赖注入
  • 初始化
  • 初始化后(AOP)

Bean的种类如下:

  • 单例Bean: 默认的bean
  • 懒加载Bean: @Lazy, context.getBean()的时候才会去加载
  • 原型Bean: @Scope("prototype")   每次getBean的时候都会返回一个新的bean对象

bean的创建

在main方法中, 主要的是以下两行代码

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        NingxuanService ningxuanService = (NingxuanService)context.getBean("ningxuanService");
复制代码
  • 所有的单例bean会在执行第一行代码, new AnnotationConfigApplicationContext()的时候加载出来
  • 所有懒加载的bean会在执行第二行代码的时候加载出来

环境搭建

pom, 仅留java 1.8

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.ningxuan</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <java.version>1.8</java.version>
    </properties>
</project>
复制代码

项目结构如下

  • spring目录: 手写spring相关代码
  • ningxuan目录: 测试目录
  • service: 业务层
  • Test: main方法

二、完整代码

具体实现可定位到标题三、具体实现

项目结构

网络异常,图片无法展示
|

具体代码请进入gitee仓库查看

三、具体实现

1、bean加载第一步: 获取所有的bean并加载到bean存储池

main方法中 创建非懒加载的单例bean

NingxuanApplicationContext context = new NingxuanApplicationContext(AppConfig.class);
复制代码

主要思路为:

  • 获取扫描路径
  • (ComponentScan) appConfig.getAnnotation(ComponentScan.class)
  • 取出扫描路径下的类
  • File file = new File(resource.getFile());
  • 测试结果在下方
  • 遍历路径下的类, 判断是否有@Component注解
  • clazz.isAnnotationPresent(Component.class)
  • 获取beanName
  • String beanName = clazz.getAnnotation(Component.class).value();
  • 生成 beanDefinition
  • BeanDefinition beanDefinition = new BeanDefinition();
  • 判断是否为单例Bean修改 beanDefinition的 Scope属性
  • if (clazz.isAnnotationPresent(Scope.class))
  • 将 beanName和 beanDefinition加入 beanDefinitionMap Bean存储池中存储
  • beanDefinitionMap.put(beanName, beanDefinition);

测试扫描路径结果:

网络异常,图片无法展示
|

单例Bean执行结果, 多次执行 Bean的地址不变

网络异常,图片无法展示
|

原型Bean执行结果, 每次getBean之后地址都发生改变

网络异常,图片无法展示
|

2、创建加载单例bean

bean存储池的存储结构如下:

private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
复制代码

存储池的 key为 beanName, value为 BeanDefinition, BeanDefinition的数据结构如下

网络异常,图片无法展示
|

所以, 我们想要获取所有的单例bean只要遍历我们的 bean存储池就可以了, 具体过程如下:

  • 遍历bean存储池
  • for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet())
  • 判断是否为 单例
  • definition.getScope().equals("singleton")
  • 通过单例BeanDefinition生成 bean对象
  • Object bean = createBean(beanName, definition);
  • 将bean对象放入单例bean存储池中
  • singletonObjects.put(beanName, bean);

bean对象生成主要看下图就可以了

网络异常,图片无法展示
|

bean的默认名称

我们声明类为bean的时候通常会使用 @Component注解

网络异常,图片无法展示
|

这个时候Spring会自动为我们的bean起名为首字母小写的名称, 如上图为我们生成的bean名称为: 'orderService', 具体实现在 scan方法中, 如下图所示:

网络异常,图片无法展示
|

3、getBean(String beanName)方法

getBean(String beanName)方法放到这里边讲解, 因为下面依赖注入会用到

getBean方法的具体实现是:

  • 通过beanName去我们的 bean存储池(beanDefinitionMap)中查找
  • 若不存在则抛出异常
  • 若存在则判断是否为单例bean
  • 若为单例bean则查找单例bean存储池 singletonObjects 返回value
  • 若不为单例bean则通过 createBean(String beanName, BeanDefinition beanDefinition)方法创建bean实例, 如下图所示
    网络异常,图片无法展示
    |

getBean方法如下图所示

网络异常,图片无法展示
|

4、依赖注入(最简单的实现, 通过属性名字去查找)

依赖注入主要流程:

  • 取出类中的属性
  • 判断是否有注解 @Autowired
  • 开启反射
  • 在 getBean中找出 bean对象
  • getBean(String beanName)方法实际上就是在 beanDefinitionMap存储池中查找bean是否存在, 具体后面再讲

网络异常,图片无法展示
|

上边我们贴过getBean(String beanName)方法的截图了, 这里就不水图了.

但是我们的getBean代码并不完善, 当出现如下图的情况, 我们先创建了 JuejinService的bean对象, 当依赖注入orderService这个bean且这个bean为单例bean的时候, 我们的bean存储池中可能还没有对其进行添加, 就会爆出空指针异常

throw new NullPointerException();
复制代码

网络异常,图片无法展示
|

这是我们要对getBean(String beanName)方法进行更改, 手动添加这个bean

网络异常,图片无法展示
|

依赖注入注意

  • 所有被 @Component注解修饰的类都会遍历添加进 bean存储池(beanDefinitionMap)
  • 在遍历 bean存储池(beanDefinitionMap)生成单例bean对象的时候可能会存在依赖也为单例bean, 但是还未被添加进 单例bean存储池(singletonObjects)中的情况
  • 这个时候我们要手动创建添加进去, 防止依赖注入失败

5、初始化

我们新建 InitializingBean 接口, 里面有一个方法为afterPropertiesSet

网络异常,图片无法展示
|

具体流程: 在创建bean的方法 createBean()中判断是否实现了 InitializingBean 接口, 若实现了则执行相应的方法

网络异常,图片无法展示
|

6、AOP(简单实现)

还是一样的, 我们创建一个接口, 里面两个方法, 一个是执行前的, 一个执行后的

网络异常,图片无法展示
|
在我们初始化bean存储池的时候就去判断, 我们的bean类是否实现了 BeanPostProcessor接口 如果实现了, 则加入缓存

private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();
复制代码

同时在我们的 createBean 方法执行的时候去对我们的缓存进行一个遍历, 去执行aop的相关方法

网络异常,图片无法展示
|

我这边也新建了一个类去实现 BeanPostProcessor 接口, 具体的实现代码都是在 NingxuanPostProcessor 中做的, 这边我没有去做

流程大概是:

  • 根据传入的 beanName执行方法, 然后返回回来

网络异常,图片无法展示
|

三、总结

回到我们的最初研究Spring的bean加载中, 现在我们可以对每一行代码进行详细的解答了

第一行代码:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
复制代码

他主要做的几件事:

  • 扫描注解定义包下的类
  • 扫描过程中把所有的bean放入 bean存储池
  • 扫描过程中将实现了 BeanPostProcessor 接口的类放入缓存池中
  • 遍历 bean存储池, 对单例bean创建bean对象, 并添加进 单例bean存储池中
  • 创建bean对象过程中进行
  • 获取实例对象
  • 依赖注入
  • 初始化
  • AOP

以上就是我本期的全部内容了, 具体代码可查看代码库


目录
相关文章
|
25天前
|
缓存 Java Spring
Spring 框架中 Bean 的生命周期
Spring 框架中 Bean 的生命周期
32 1
|
1月前
|
XML Java 开发者
Spring Boot中的bean注入方式和原理
Spring Boot中的bean注入方式和原理
61 0
|
1月前
|
XML 缓存 Java
Spring源码之 Bean 的循环依赖
循环依赖是 Spring 中经典问题之一,那么到底什么是循环依赖?简单说就是对象之间相互引用, 如下图所示: 代码层面上很好理解,在 bean 创建过程中 class A 和 class B 又经历了怎样的过程呢? 可以看出形成了一个闭环,如果想解决这个问题,那么在属性填充时要保证不二次创建 A对象 的步骤,也就是必须保证从容器中能够直接获取到 B。 一、复现循环依赖问题 Spring 中默认允许循环依赖的存在,但在 Spring Boot 2.6.x 版本开始默认禁用了循环依赖 1. 基于xml复现循环依赖 定义实体 Bean java复制代码public class A {
|
1月前
|
存储 NoSQL Java
Spring Boot统计一个Bean中方法的调用次数
Spring Boot统计一个Bean中方法的调用次数
35 1
|
2月前
|
Java 索引 Spring
spring创建bean的过程
spring创建bean的过程
|
1天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
10天前
|
Java 数据库连接 开发者
浅谈Spring的Bean生命周期
浅谈Spring的Bean生命周期
18 1
|
14天前
|
XML Java 数据格式
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
19 0
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
|
24天前
|
XML Java 程序员
作为Java程序员还不知道Spring中Bean创建过程和作用?
作为Java程序员还不知道Spring中Bean创建过程和作用?
15 0
|
29天前
|
XML 缓存 Java
天天用 Spring,bean 实例化原理你懂吗
天天用 Spring,bean 实例化原理你懂吗
17 0