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

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


目录
相关文章
|
1月前
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
121 26
|
3月前
|
XML 安全 Java
|
3月前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
3月前
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
|
3月前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
78 6
|
3月前
|
XML Java 数据格式
🌱 深入Spring的心脏:Bean配置的艺术与实践 🌟
本文深入探讨了Spring框架中Bean配置的奥秘,从基本概念到XML配置文件的使用,再到静态工厂方式实例化Bean的详细步骤,通过实际代码示例帮助读者更好地理解和应用Spring的Bean配置。希望对你的Spring开发之旅有所助益。
190 4
|
4月前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
167 4
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
3月前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
55 1
|
5月前
|
XML Java 数据格式
Spring从入门到入土(bean的一些子标签及注解的使用)
本文详细介绍了Spring框架中Bean的创建和使用,包括使用XML配置文件中的标签和注解来创建和管理Bean,以及如何通过构造器、Setter方法和属性注入来配置Bean。
110 9
Spring从入门到入土(bean的一些子标签及注解的使用)
|
6月前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
481 24