手写模拟spring

1.模拟一个spring容器,首先我们需要新建一个Java工程,不要创建web,也不要maven工程,就一个简单的Java工程,我们需要将这个工程改造成spring,先定义一个Context容器类。

还要创建两个注解,一个是在spring中常见的注解Componet,用于加在类上,使得类被创建到容器中,不过此时的注解是没有实现逻辑的。

我们让使用这个注解的类的用户端(程序员)传递一个值,这个值就是我们创建对象的时候在spring容器中有一个bean的名称。

另一个是ComponetScan注解,用于扫描包的范围。

这个注解也是需要使用的时候传递一个值,这个值就是扫描要的路径。

再在配置类中使用这个注解,并设置需要扫描的路径。

然后还创建了一个创建容器所需要的配置类,此时基本的spring的架构算是搭建起来了。

2.完成spring容器类中的getBean方法,在真正的spring容器类中有一个getBean方法用于从容器中传递一个bean的名称来获取bean。所以我们也模仿一下这个方法:

此时我们调用这个方法时候就需要传递一个bean的名称:

3.在真正的创建spring容器的构造器中,实现复杂的逻辑,主要的是对spring容器的启动和bean的加载,那么是怎么实现的?

首先在spring容器构造器中,我们首先会拿到传递进来的配置类,这个配置类上我们判断一下它的类上是否有包扫描注解(ComponentScan注解),若是没有则启动spring容器失败,若是有那么继续进行逻辑。然后,拿到这个配置类上的注解,再将注解中的属性给拿到,拿到之后是一个包的相对路径的以点(.)分割的路径。

这个路径是不行的,每一个用户端创建的包名都不同,如:项目名不同。所以我们需要通过spring容器的classLoader拿到再硬盘的绝对路径,然后再拼接上这个包扫描的路径。

但是这个扫描的相对路径需要转换一下,然后再拼接。

当然通过clssloader就会拿到这个包扫描下的所有全类名,那么现在只拿到了绝对路径的包名,要怎么拿到这个包下的全部类呢?

将这个包名创建称为一个文件类型,然后判断这个文件是否是一个目录,若是一个目录则将该路径下的每一个文件进行遍历,若是以.class结尾的文件则我们再处理后面逻辑。

然后拿到这些以class结尾的类,就能排除掉其他文件只拿到java文件,然后通过反射利用这些类的全类名,这里全类名需要转化一下。反射拿到这些类对象后,再判断,若是上面有Component注解的类,则是需要创建bean对象的类,所以我们进行判断,后续再处理创建逻辑。(loadClass需要处理一下检查异常)

4.具体的去创建bean对象,首先第一步是判断要创建的bean对象是否是单例还是多列对象,所以我们需要一个Scope注解:

那么我们创建对象的时候就可以加入这个注解,来代表是创建的bean是单例还是多列:

且我们需要一个描述创建bean信息的类BeanDefinition

这个类描述了要创建bean的类型,bean的实例,真正的BeanDefinition中还有判断是否懒加载等信息。

继续完成bean的创建,首先创建BeanDefinitionbean信息定义类,然后设置bean是什么类型,这个已经通过类加载器拿到类类型了直接设置即可,然后判断若是这个类上有Scpoe注解再判断注解上的值是单例还是多列(这里再判断逻辑没写,默认是多列),若是没有Scope注解则认为是单例,并给BeanDefinition对象设置为单例。

完成对象的信息对象创建后,需要一个Map集合来装,设置一个全局变量contrntHashMap集合:

key为bean的名称,值为对象信息对象。    

bean的名字是使用Componet注解的时候设置的值,所以我们在Map集合加入信息的时候要先拿到这个注解上的bean名称:

当然这里应该还要加一个判断,若是没有拿到这个注解的value值就要默认使用类名首字母小写作为bean名称。

然后将bean和BeanDefinition对象给加入到map集合中。

5.在上面第4步中,看似完成来了bean的创建,但实际上仅仅只是拿到了需要创建bean的信息并放入到了全局共享的map集合中,而在真正的spring中bean都是懒加载的,只有需要它的时候才去创建bean。所以前面步骤都是在给getBean,也就是真正的创建bean的逻辑做准备。

在我们的Context这个容器类上除了上面的构造方法还有一个getBean方法,这个getBean方法需要传递一个bean的名称,然后去beanDefinitionHashMap中去这个bean,若是找到了则返回一个beanDefinition对象(bean信息定义类),若是找不到则抛出异常,此时再判断是多列还是单例,而再真正的spring中单例的对象是只创建一次的,所以为什么不把单例对象的创建也放入构造器中?也就是说第4步完,拿到需要创建bean的对象并出创建map之后再将单例对象放入单例池,什么是是单例池?如何完成创建?

6.完成单例对象的创建。在创建完成map集合后,将map集合中的需要创建bean的beanDefinition对象给拿到,判断Scpoe属性是否为单例,若是单例则将该对象给创建出来

然后再ConText容器中再设置一个全局属性,这个全局属性是一个Map集合它用于存储所有的单例对象,也就是单例池:

再将刚才找到的单例对象给创建的单例对象放入单例池中。如此,在getBean方法的单例对象获取时候,只需要去单例池中获取bean对象就行。

那么我们完成getBean方法中的内容:

这个时候如果是单例,则只需要将bean的名称在单例池中获取就可以拿到单例对象,这里做了安全处理,若是没有在单例池中拿到单例对象,则创建出来,并放入单例池中。

若是多列,那只需要每一次调用getBean方法的时候都去调用creatBean方法(创建Bean方法)即可。

7.总结这个Context容器类中都做了什么事,有那些方法:

一个是beanDefintionHashMap全局属性bean的信息表述类的集合。

二个是sgingetonHashMap全局属性是所有单例bean的集合。

三个是构造器方法,用于对类的扫描查找,将需要创建bean对象的类的信息给创建出来并放入map中。

四个是creatBean,用于创建bean,(还未完成)。

五个是getBean用于懒加载bean的对象,客户端(程序员)获取bean。

8.完成bean对象的创建。在creatBean方法中,我们已经得到需要创建的bean的名称,和对应要创建bean的信息,那么这个bean的信息中beanDefinition类中有Type属性就是bean的类型,通过这个bean的类型再通过反射,拿到无参的构造方法就能完成bean的创建,将bean给返回。

去创建一个Context容器,然后在一个类上(这个类是空的类)设置componet注解上的单例和多列,会发现现在如同正常spring相同能通过控制注解的方式来在spring容器中创建单例或者多列bean。

9.对于createBean方法来说,不可能这么简单创建一个bean对象即可,真正的spring中这个bean对上有一些属性,且加了Autowired注解就能实现属性bean的注入,那么是如何实现的呢?

首先,线程创建一个Autoired注解;

设置FIELD值使得这个注解只能加载字段上。

来一个需要创建bean的类,并使用这个注解:

此时我们的createBean中,我们在把bean对象使用无参构造创建出来之后,bean返回之前,我们拿到这个对象的所有属性,并判断属性上是否有Autoired这个注解,若是有这个注解,再过反射爆破(设置Accesible为true)给属性注入值,但是注入什么值呢?注入的就是这个用这个属性的名称去spring容器中获取,且因为这个徐娅注解的orderservce对象是的单例的,所以会去单例池中获取,但是这个容器中肯定是没有这个值的,但我们getBean时候获取单例时进行了判断若是单例池中没有这个对象,则创建这个对象放入单例池中,并返回这个对象。

再getBean方法时候又会调用当前的creatBean方法,若是这个orderservice对象,还有属性需要注入,那么就会再次注入。此时就会存在一个问题,若是再次注入的的bean又需要当前bean创建出来(换句话说,orderservice中有个属性是Userservice,而Userservice中有给orderservice),此时就出现了循环引用的问题。

10.回调接口,Aware接口。

这里只写BeanNameAare接口的实现。

真正的spring在客户端(程序员)自己的bean中如果实现了BeanBameAware接口,那么就会重写SetBeanName方法,这个Bean是交给spring管理的,所以这个方法也是由spring给调用的,调用的时候就会将这个bean的名称给通过这个方法给传递过来,客户端(程序员)就可以拿到最终在spring中它的bean到底是一个什么名字。

spring如何实现的呢?

在sring包中定义这个BeanNameAware接口,并设置一个setBeanName方法。

然后,在bean的创建过程中,在依赖注入完成之后,对Aware接口中的方法进行调用。

首先,先判断这个要创建的bean的类是否实现了Aware接口,然后判断是那个Aware接口去调用它的对应接口的方法,将需要的值给传过去。这里只实现了BeanNameAwre接口,所以调用setbeanName方法并将bean的名称传递过去。

11.初始化。真正的spring中给程序员一个干预初始化的办法,需要被创建bean的类实现一个接口,并不是要求你必须实现这个接口。

我们需要创建出这InitializingBean接口,并设置一个afterPropertiesSet()方法。

在需要干预spring初始化bean对象的类上加上这个接口,并实现这个方法

当然这个方法是看程序员自己写的什么逻辑。

然后在bean对象创建过程中,Aware接口回调完成之后,进行调用这个afterPropertiesSet方法,具体这个方法要做什么内容spring不关心,它只需要调用一下这个方法即可。

与Aware接口回调不同的是,这个方法只需要调用就行,不需要spring传递给客户端(程序员)一些什么信息。

总之,这个初始化,是给程序员提供了一个接口和方法用于干预spring创建bean的时候的一些逻辑。(如:在创建过程中打印一句话,等逻辑)

12.程序员对bean的创建的干预,或者说想自己在spring容器的bean创建过程中做一些操作,这就涉及到bean的前处理器,后处理器。

在spring包中有一个接口,这个接口有前处理器,和后处理器两个方法,分别需要传入创建bean的时候的当前bean的名字,和一个bean的对象。

那么我们需要干预spring容器创建bean,首先需要写一个类,这个类实现BeanPostProcessor接口,然后重写其两个方法并将这个类注入到容器中。

然后再Context容器中写一个全局属性,这个属性是一个ArraList集合,这个集合用于存储所有的beanPostProcessor对象。

这个时候我们还要修改Context容器的构造器,之前说过,在包扫描后找到扫描包下所有的需要被创建的bean(被componet注解修饰的类),找到这些类后会判断单例还是多列,将对象放入对象信息类集合中,在这一步判断之前我们新加入一段逻辑。首先判断某一个bean,是否实现了BeanPosyProcessor接口,若是实现了这个接口就把这个bean给创建出来放入ArrayList集合中,不管有没有都会加入到对象信息集合中。

然后,在createBean方法中,在bean初始化之前和之后都加上新的逻辑。当其中一个bean需要被创建时候,那么这个bean初始化之前就会先去拿到bean处理器集合对象(之前的ArryList集合),循环遍历这个对象中的处理器对象,拿到这个处理器对象后,就调用这个处理器对象的前处理器方法,此时这个bean就会被程序员自己写的前处理器给修改(影响spring对bean的创建),同理初始化后调用后处理器方法,也是对spring对bean创建的影响。当然需要程序员对某一个bean产生影响,需要将那个bean给传递过去并传递bean在spring容器中的名字才能操作,比如上面的前处理器中,若是创建的bean的名字叫userService那么就将这个bean给强转一下或者打印信息或是进行代理对象等。

总结就是,在初始化(Context容器的构造器中)时候会首先判断一个bean是否作为处理器bean,若是则放入处理器bean对象集合中,不管是不是都会放入bean信息描述对象集合中。然后,在getBean时候因为会调用bean的创建,那么每一个bean创建都时候都会先去赵处理器bean对象集合是否有处理器对象,若是有,那么每一个bean都会调用自定义的前处理器和后处理器方法的内容,从而对bean的创建进行一个切面,这样我们更容易对bean的创建进行一些处理,更加的灵活,这就是AOP的雏形。

13.AOP代理对象。在步骤12中我们可以对一个bean的创建之前和之后进行操作,使得某一个bean在创建的时候程序员可以干预spring对bean的创建。那么,我们是否可以干预某个bean对象将bean对象拿到后进行一个代理呢?

首先,我们需要将bean对象处理器接口发前处理器和后处理器的方法进行修改:

前后处理器此时都可以返回一个Object对象类型,那么创建bean对象时候调用这两个方法会将所有的bean对象都传递到这两个方法中,然后我们判断每一个bean拿到我们需要被代理的对象,然后使用jdk(接口代理)进行代理后将代理后的对象给返回。

当然也可以继续完善我们需要代理的具体信息,如在调用所有方法的时之前打印一句话:

那么在返回这个代理对象后,就需要在createBean方法中继续修改逻辑:

我们将前处理器和后处理器返回的bean放入即将返回给用户的bean对象中。

此时spring容器就实现了对象的代理。

以上就完成了对spring的模拟,但是只是模拟了主要的步骤,对于真正的spring来说它考虑的东西非常多,还有许多功能设计模式,思想和细节是模拟不出来的。

项目地址:https://gitee.com/luoweng11/luo-spring.git

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值