非懒加载的单例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方法。