模拟手写实现Spring

非懒加载的单例bean在13行 在spring容器启动的时候就创建好了

懒加载的单例bean 会在getBean的时候才创建

prototype原型bean(多例), 每次在getBean的时候都返回创建新的对象

搭建好基础框架 文件目录

这里AppConfig不属于spring 是自己的配置文件

扫描逻辑

AppConfig配置

很明显 这里用到了构造函数

所以需要成员变量 Class configClass;

定义扫描路径

AppConfig上肯定需要注解用于定义扫描路径

@Target(ElementType.TYPE)——接口、类、枚举、注解

@Target(ElementType.FIELD)——字段、枚举的常量

@Target(ElementType.METHOD)——方法

@Target(ElementType.PARAMETER)——方法参数

@Target(ElementType.CONSTRUCTOR) ——构造函数

@Target(ElementType.LOCAL_VARIABLE)——局部变量

@Target(ElementType.ANNOTATION_TYPE)——注解

@Target(ElementType.PACKAGE)——包

一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解

加入容器注解

@Component

普通对象==》依赖注入(属性赋值)==》初始化前(@PostConstruct,……)=》初始化(InitializingBean)=》初始化后(AOP)=》代理对象=====》bean

所以现在首要的第一件事就是要去创建单例bean

那么我们就要扫描出哪些类上加了@Component注解

扫描出所有加Component注解的类

拿到AppConfig上面要扫描的路径

可以正常获取到path

找到路径下所有的.class文件

只有路径,但是要还不够,肯定要取路径下的.class文件

要怎么找到这个文件夹呢?

Boogstrap类加载器=======》管理目录jre/lib

Ext类加载器============》管理目录jre/ext/lib

AppClassLoader类加载器==》管理目录??

是自己通过-classpath指定的,AppClassLoader就会去你指定的下面找这些类

我们可以通过当前类拿到AppClassLoader(因为当前类就是通过AppClassLoader解析的)

但我们最终目的是找到文件,先要把path的"."替换目录的符号"/"

通过这个path相对路径我们就能拿到service文件夹

com/zkc/service

这里我们就能通过URL拿到整个目录file(file可以表示文件也可以表示目录),这里拿到的就是目录service

递归调用

我们先判断这个file是不是一个目录,如果是目录的话

我们就可以通过listFIles()遍历到所有的文件file,这里可以用到递归来判断是文件夹还是文件

如果是文件就往里头进

这时候先把类加载进来才方便操作

但是现在有个问题 因为类加载器加载是相对路径而不是绝对路径

需要转换成com.zkc.service.userService这种格式

首先从@ComponentScan需要扫描路径注解获取到的com.zkc.service

截取出com

我们通过com的子串第一次出现的索引位置和.class索引再替换掉"/"为"."就能截取出下图所需路径

看哪个类上加了@Component 注解

就要创建bean?不对,只有单例才在这创建

添加@Scope注解

当前目录结构

我们需要通过scope注解来判断是单例还是原型

但是我们想一下,如果在一开始调用getBean就要获取对象

不管是单例还是原型我们不可能重新再解析一遍,因为在一开始加载

ApplicationContext就已经全部都解析过了,没必要返工

引入BeanDefinition

回到刚才===》在类上加了@Component注解说明这个类是一个bean

那么就要给他创建个BeanDefinition定义,把定义信息封装起来

其实内部还有个beanDefinitionMap,用于存放beanDefinition

存入beanDefinitionMap

扫描的过程中间,只要每扫描到一个bean,就放入这个beanDefinitionMap中

首先先要获取beanName,这里有个细节,如果没写@Component("xxxxxxx"),就需要使用默认的beanName

这里的默认beanName需要把首字母换成小写

存入map

此时扫描的方法就已经实现完成抽象成一个方法。

==================================================

创建单例bean

遍历beanDefinitionMap来创建单例bean,封装个createBean()方法

原型bean其实就是调用createBean()方法,每次调用都会返回新的对象

引入单例池

getBean如果是单例,就可以直接从单例池中取出

createBean()

这里有参构造只支持Integer和String,还有无参

写完整比较复杂(下面createParameter方法里的应该是Integer.class不是int.class 这个地方错了)

当前OrderService是单例,UserService是原型bean

验证成功

就算没加@Scope注解,默认beanDefinition也是赋值为单例

依赖注入@Autowired

声明注解

这就ok了?nonono 肯定是null的

结果调试

依赖注入肯定是在创建bean的过程中

先遍历当前class的所有的属性,看属性上是否加了@Autowired注解

然后反射设置值,那要赋什么值呢?

field.setAccessible(true)的意思是将字段的可访问性设置为true,即使字段是私有的。默认情况下,私有字段是不可直接访问的,但通过设置可访问性为true,我们可以绕过访问限制,从而可以对私有字段进行操作。

可以通过去单例池拿到对应的对象,但有可能单例池里还没创建好,那就去beanDefinition拿到type来生成。

根据某一个类型,去找到它的bean,根据类型如果找到多个再根据名字找。

单例池可能会为空 这时候就要给单例池提前缓存

这时候就算使用UserService1也没问题,因为优先使用类型去找的

初始化InitializingBean接口

接口定义

此时底层还没有实现 是不会自动调用这个初始化的

是在依赖注入完后进行调用的

初始化前和初始化后

BeanPostProcessor 有默认实现

具体方法在接口中的作用是为实现该接口的类提供默认的方法实现。这样,实现类可以选择是否覆盖这些具体方法,如果没有覆盖,则会使用接口中默认的方法实现。

使用说明,直接继承接口,然后重写你想要使用的方法

类上需要加@Component注解,spring才知道使用了这个类

判断一个类是不是实现了某个接口

这里不能用instance of,因为是用于某一个对象的类型,这里的class是类而不是对象

这里只是做了个扫描,其实是在创建bean的时候才会用,这里就需要缓存起来

创建bean的时候,实例化,依赖注入,初始化,

遍历所有的beanPostProcessorList,因为扫描的时候就已经全部塞入list里了

也就是说每创建一个bean的时候 先实例化得到一个对象 依赖注入 初始化前 初始化 初始化后

但是这边有几个bean对象,就会被调用几次

这边最好对ZkcBeanPostProcessor做个过滤好一点,因为ZkcBeanPostProcessor已经实例化出来了

也就是说其实在spring中如果你是ZkcBeanPostProcessor像这么定义了一个实现了PostProcessor接口的话

会对所有的bean,都调用后置处理器这个方法,会把当前beanName和bean对象传进来。

如果只想针对某个bean,得自己去控制

执行完以后又会返回实例对象,那么这个功能就可以用来实现AOP

实现AOP(JDK动态代理)

JDK动态代理是基于接口来进行动态代理的

也就是说我们在创建UserService这个bean的时候,真正的bean其实是proxyInstance这个代理对象

一开始是普通的bean,执行完实例化,依赖注入,初始化,直到执行完初始化后就变成了代理对象了。

也就是说createBean这个方法最后返回的就是代理对象,所以单例池存放的也是代理对象,getBean也就能拿到代理对象

所以执行test方法时,就会进到invoke方法里来

问1:这行代码哪里有问题?

答:因为getBean拿到的对象是代理的是UserInterface这个接口,所以强转不能转成UserService,而是强转成接口UserInterface,但是如果是cglib就没问题。

注意!!!!

这边贼坑,如果没用到这个instance返回值,也就是不返回的话,就不会使用代理对象!!!

也就不会输出进入 invoke方法

假设我自己定义一个注解@zkc(不是spring里的)

我想实现通过自定义注解的值来赋值给属性

现在肯定是不能赋值的 null

这时候就可以使用postProcessor

成功注入

Aware接口

假如我想知道我的beanName是什么?

可以怎么知道呢?

某一个bean如果想知道自己的名字的话,就去实现beanNameAware接口.

spring就会自己调用这个方法来给beanName赋值

在依赖注入之后,前置处理器之前执行

Aware接口底层其实还是实现了BeanPostProcessor接口

初始化前 每个bean是否实现了Aware接口 是针对所有bean的 并不是某个bean

就回调这些方法

例子:也就是说spring就会回调这个方法,把当前正在创建这个bean的bean工厂传过来给你

小总结

扫描=》根据扫描路径,加载所有的类,看哪个类上面加了@Component注解,并根据@Scope等信息生成BeanDefinition,然后就存放到beanDefinitionMap里,key是beanName,并且还额外判断当前扫描的这个类是不是BeanPostProcessor,如果是则直接实例化存到beanPostProcessorList中,扫描结束。

接下来会遍历beanDefinitionMap,如果是单例,则直接创建bean,并放入单例池。

创建bean的过程:

先根据有参无参构造器实例化,然后依赖注入,执行Aware的各种回调,执行beanPostProcessor前置,初始化以及后置初始化方法。

getBean方法:

首先根据beanName去beanDefinitionMap找到beanDefinition,然后根据type判断是不是单例,如果是单例直接去单例池拿(先根据类型后根据beanName),拿不到再去创建bean,同时放入单例池中,如果是原型直接调用createBean方法。

  • 29
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值