一、前言
在前面, 我们通过文章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(简单实现)
还是一样的, 我们创建一个接口, 里面两个方法, 一个是执行前的, 一个执行后的
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
以上就是我本期的全部内容了, 具体代码可查看代码库