简单分析Spring核心IOC和DI底层源码

一、 首先要了解Spring的核心编程思想,什么是AOP,OOP,BOP,IOC,DI?

1.AOP:AspectOrientedProgramming(面向切面编程),所谓的面向切面即是将一个有规律的整体的代码,在开发的时候将其分开,运行的时候在将其合并,这样就可以在每一个模块间做其他的事情,面向切面也就是面向规则。

2.OOP:ObjectOrientedProgramming(面向对象编程),万物皆对象。

3.BOP:BeanOrientedProgramming(面向Bean编程),这里的Bean表示普通java类。

4.IOC:InversionofControl(控制反转),在之前未使用到spring时一般在需要对象时是自己new一个对象,而在Spring将new对象的控制权交给Spring,并且由Spring保存到已创建的容器中。

5.DI:DependencyInjection(依赖注入),指的是对象被动的接收依赖类,而不是主动的去找相关依赖类,也就是说对象不会去容器里面找相关依赖的类,而是在容器实例化的时候主动的将类注入给对象

二、了解spring容器体系--BeanFactory(Bean工厂)

BeanFactory是springIOC容器最基本的容器接口,其子类开展详情请看源码,在该接口中定义了对Bean操作的相关规则,例如各种getBean方法来得到bean实例,isSingleton方法判断bean是否为单例,getType方法得到bean实例类型,getAliases方法得到bean实例的别名等等方法。由于spring对于工厂产生bean实例的过程分的很细,需要在查看源码时要非常细心,要不然会一点就出不去了。

三、了解IOC过程--定位,载入,注册

1.定位:首先我们在使用到Spring框架时配置文件application-context.xml在通过classpathResource得到资源路径传给加载器时就是一个在定位配置文件的过程。其中细节就是,IOC容器创建是通过XMLBeanFactory接口、FileXMLapplicationContex接口实现,其中调用父类构造方法设置Bean资源加载器,然后设置Bean定义资源文件的文件路径。在参照父类的setApplicalictionContext方法可以发现,在解析资源文件的时候,会使用,;/t/n来划分多资源文件将该资源文件路径存放到数组中。

2.载入:载入过程的开始是通过abstractApplicationContext接口的refresh函数开始的,该函数的作用是:在创建IOC容器前,如果该容器已有容器存在,则需要将已有的容器销毁和关闭使得整个过程都是使用的新建立起来的IOC容器也可以保证单一性,该函数类似于重启,在建立好新的容器之后开始对Bean定义进行载入。其子类FileSystemXMLApplicationContext通过调用父类的refresh方法进行载入,其源码大概是在配置BeanFactory容器属性,例如事件处理器,类加载器,后置处理器,初始化信息源,初始化各类Bean及其容器生命周期事件。在子类调用refreshBeanFactory方法时会先判断BeanFactory是否存在,存在就销毁,然后创建IOC容器,调用Load..方法装配bean,其中该方法实现装配的方式为:先创建读取器去读取资源文件,由于资源文件使用到xml,使用SAX解析XML。然后在读取器去读资源路径时,会先判断该资源是否存在,如果存在就判断该路径是类路径还是URL方式,解析完路径得到相关xml文件进行解析。在开始解析xml文件的时候,首先做编码处理,然后使用InputStream流得到xml解析源,然后将xml文件解析为document对象,用documentLoader实现,整个从定位到Bean定义资源文件通过加载器装换为document对象完成之后开始分析,这些被管理的Bean对象是怎么样加载注册到容器中的。

3.注册:注册的开始是先将上一步得到的document对象按照Bean规则解析,首先拿到根元素,通过根元素来做相关操作,由于使用到xml文件一般xml文件的根标签为<xml>如果有自定义的标签按照自定义标签的规则进行解析,得到每一个根标签之后,判断如果为import标签则按照导入元素解析并且在解析时会优于解析相关资源文件导入到IOC容器中,如果为alias标签则按照别名的方式解析,如果不是上面两者则按照spring的bean规则解析。之后开始解析相关《bean》标签的元素的各种,例如id,name,alias,然后开始检查相关参数值,设置相关参数值,除了以上三个之外还有meta,replaced-method,lookup-method属性等等,解析完bean标签的属性开始解析子元素标签property,子元素标签主要看property,然后解析得到其属性值是ref还是value,这两者不允许同时存在,如果是ref属性则使用ref封装信息对象,反之使用value封装对象,其中ref表示指向一个依赖对象引用,value表示封装为一个字符串类型的对象。在深入解析子元素ref,在ref里面只能有三种属性,bean,local,parent。这个部分的解析完全可以根据使用Spring配置文件的方式来理解源码,在使用的时候是怎么样配置的,底层对于该类型的配置就会做对应的操作。将以上document对象都解析完毕之后会变成一个spring可以理解的数据结构-BeanDefinition,这个时候只是完成了IOC容器数据结构存储的一些静态信息,但是还没有将该信息注册到IOC容器中。之后就开始注册Bean定义信息到IOC容器,整个IOC容器通过使用HashMap来存储注册解析的BeanDefinition,底层通过定义ConcurrentHashMap(256)来作为容器将数据结构中存储的bean定义的信息注册到容器中。

至此整个Bean定义资源文件中配置的Bean被解析之后,已经被注册到IOC容器中,被容器管理起来,真正的完成了IOC容器初始化的全部工作,现在容器中得到所有的Bean的信息,可以被使用也可以被检索,IOC容器的真正的作用就是对这些Bean定义的信息进行处理和维护,也是IOC容器控制反转的基础。

四、了解DI依赖注入过程。

首先要知道在spring完成IOC容器的定位载入和注册之后是没有进行依赖注入的,所以依赖注入的时机是发生在当用户调用getbean方法时向容器索要Bean触发依赖注入,注意如果用户在定义资源文<bean>标签时如果配置了lazy-init属性,依赖注入会在容器解析注册bean时进行预实例化(这样就可以不用调用getbean创建,直接拿容器实例化好的对象即可,效率会高一点)

1.基于xml文件的依赖注入:

首先在调用getbean向IOC容器获取Bean方法的分析,我们可以看到在Spring中,如果Bean定义的单例模式(Singleton),则容器在创建之前先从缓存中查找,以确保整个容器中只存在一个实例对象。如果Bean定义的是原型模式(Prototype),则容器每次都会创建一个新的实例对象。除此之外,Bean定义还可以扩展为指定其生命周期范围。再往下分析可以发现具体操作依赖注入的方法createBeanInstance:生成Bean所包含的java对象实例、populateBean:对Bean属性的依赖注入进行处理。首先分析第一个方法生成bean,根据工厂方法和自动装配特性调用参数匹配的构造器来进行bean实例化,这里面使用到的初始化策略是采用了JDK反射机制或者CGLIB来进行初始化,调用instantiate方法进行初始化,如果Bean有方法被覆盖了就使用JDK反射机制得到原构造器进行实例化对象,如果没有被覆盖则使用CGLIB来做动态代理将原本的bean作为基类生成实例对象。这里对bean进行实例化涉及到代理模式,JDK的代理只能针对接口,如果该类没有实现任何接口那就只能使用CGLIB进行动态代理。java实例对象创建完毕就开始调用populateBean方法进行Bean属性依赖处理,在进行依赖注入时开始分析是否需要进行属性类型装换需不需要解析属性值,如果不需要直接注入,如果需要则将需要解析的属性值解析,将需要装换的类型进行装换比如我们一般在注入时不一定是本类有可能是子父类这样就需要先解析完在进行注入。真个解析过程参照源码,解析完毕就开始进行依赖注入,依赖注入实现方法processKeyedProperty,其大致思想就是如果是集合类型的属性,就将属性值解析为目标类型集合后赋值给属性,如果是非集合类型的属性,就使用JDK的反射和内省机制通过方法的getter和setter去获取和设置对应的值属性。

2.基于注解的依赖注入:

注解版开发越来越成为主流,也更加的方便。在spring里面有类级别和类内部级别的注解,例如@Component、@Repository、@Controller、@Service和@Autowire、@Value、@Resource,首先对于这些注解bean注册到容器中,可以在初始化容器时也可以手动调用注册方法进行注册然后手动刷新,在初始化注解容器时指定要自动扫描的路径,如果容器创建以后向给定路径动态添加了注解Bean,则需要手动调用容器扫描的方法,然后手动刷新容器,使得容器对所注册的Bean进行处理。在进行注册bean时需要使用注解元数据解析器解析Bean中关于作用域的配置,使用AnnotationConfigUtils的processCommonDefinitionAnnotations方法处理注解Bean定义类中通用的注解,使用AnnotationConfigUtils的applyScopedProxyMode方法创建对于作用域的代理对象,通过BeanDefinitionReaderUtils向容器注册Bean。然后我们分别对以上4个进行分析,第一个判断作用域元信息,也就是判断注册的bean是单例还是原态然后对于通用注解的处理;第二个对配置作用域进行代理,如果配置的注解属性ProxyMode为No则不代理,这部分主要面向AOP使用;第三点注册到容器中,对bean进行判断,校验之后添加到一个hashmap集合管理的bean容器中,如果在传入初始参数时为主键bean的所在包,则容器会扫描给定的包及子包然后注册到容器中。整个过程就是先将注解bean注册到容器中,在注册的时候解析相关bean属性对不同属性做不同的操作,对于需要扫描包的进行扫描后注册,注册完毕开始类似于xml注入形式对bean进行代理实例化。

五、小结及IOC高级特性

IOC的过程上面已经过了一遍,也就是定位载入和注册,我们已经基本上了解了SpringIOC容器对Bean定义资源的定位、读入和解析过程,同时也清楚了当用户通过getBean方法向IOC容器获取被管理的Bean时,IOC容器对Bean进行的初始化和依赖注入过程,这些是SpringIOC容器的基本功能特性。SpringIOC容器还有一些高级特性,如使用lazy-init属性对Bean预初始化、FactoryBean产生或者修饰Bean对象的生成、IOC容器初始化Bean过程中使用BeanPostProcessor后置处理器对Bean声明周期事件管理和IOC容器的autowiring自动装配功能等。

1.Bean预初始化:都知道整个IOC及DI的过程就是定位载入注册之后调用getbean方法进行依赖注入,但是如果<bean>配置了lazy-init属性则容器会事先预初始化,发生的时机是在刚刚注册完毕之后直接调用getbean触发依赖注入,这样在用户第一次向容器索取bean时就不用在进行依赖注入,直接取现成的Bean提高了第一次获取bean的性能

2.关于BeanFactory和FactoryBean的区别,一个是bean工厂,一个是工厂bean。说白了一个是工厂用来管理bean的,一个是bean对象用来产生其他bean实例的,一般我们会使用转义字符“&”来区分FactoryBean对象本身和FactoryBean产生的对象。例如我们在使用JDK动态代理时候会发现代理出来的对象名为&proxy0。

3.后置处理器(BeanPostProcessor):用于管理bean生命周期事件,它的调用一般发生在容器对bean对象创建和属性依赖注入之后,在进行属性依赖注入调用getbean触发依赖注入时给实例对象添加了一个后置处理器,后置处理器有多种分别解决不同的操作,如:AOP面向切面编程的注册通知适配器、Bean对象的数据校验、Bean继承属性/方法的合并等等,我们以最简单的AOP切面织入来简单了解其主要的功能。

4.解决IOC存在的循环依赖问题:场合:在IOC中目的是为了自动维护多关系之间的关联与依赖关系,例如在实例化A时,A依赖于B,B依赖于C,那么在实例化时先调用getBean("beanA"),之后在调用getBean("beanB")到C之后又调用getbean(“beanA”)形成循环调用。解决方法,底层使用了快照的形式。当进行实例化时先调用singletonObjects缓存,如果没有在调用earlySingletonObjects缓存,之后进行曝光Bean,将bean缓存,在把原来的bean移除。当进行依赖调用时,C调用getbean(“beanA”)会去调用SingletonObjects缓存获取A的实例化,但是发现A未实例化返回null,B调用完之后发现C为Null也设置为null,之后A也设置为null全部依赖实例化完成。

六、AOP理解

我们一般做活动的时候,一般对每一个接口都会做活动的有效性校验(是否开始、是否结束等等)、以及这个接口是不是需要用户登录。

按照正常的逻辑,我们可以这么做。 è¿éåå¾çæè¿°


这有个问题就是,有多少接口,就要多少次代码copy。对于一个“懒人”,这是不可容忍的。好,提出一个公共方法,每个接口都来调用这个接口。这里有点切面的味道了。 

è¿éåå¾çæè¿°
同样有个问题,我虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点)。

这样接口只需要关心具体的业务,而不需要关注其他非该接口关注的逻辑或处理。 
红框处,就是面向切面编程。

逻辑图(偷图):

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值