人人都能看懂的Spring底层原理,看完绝对不会懵逼

为什么要使用Spring?

其实回答这个问题,可以反过来思考,能不能不使用Spring,不使用Spring会怎么样?

如果只是写些小Demo代码,或者开发一个小工具,那真的没必要使用Spring。

但如果是公司级的项目或系统,里面的代码量是非常庞大的,工程里面会有很多的类很多的模块系统的运行会创建很多对象,而对象与对象之间又有错综复杂的依赖关系。在没有Spring的管理下,这些对象的创建,依赖关系的维护,都需要通过硬编码进行管理,那么久而久之代码就会变得非常的难以维护,类与类之间,模块与模块之间的耦合度会非常的高,一个小改动,就有可能影响很多地方。

其实不使用Spring也不是不可以,很多开源框架也没有依赖Spring进行开发,但是它们也在进行版本的迭代和扩展,那是因为它们使用了各种设计模式优化了它们代码的设计,使其变得更好维护和扩展,或者可以说是不得不使用设计模式去优化,因为不这么做的话,它们的项目也会慢慢的变得无法再继续维护和扩展

而我们基本上都是做业务开发的,都是以实现功能为主,功能可用基本上就不会再想着去优化,所以如果不使用Spring,那么10个项目可能就是10座祖传屎山。
在这里插入图片描述
其实问题主要就是以下两点:

  1. 所有需要用到的对象,都需要手动创建,这些都是硬编码
  2. 对象与对象间的依赖关系,需要人为管理,对象的属性需要手动赋值,一个对象里面依赖到另外10个对象,那连带的要创建这10个对象

使用Spring,就是为了降低类与类之间,模块与模块之间的耦合度,使得代码更易于维护。

Spring的核心功能,就是管理对象的生命周期,特别是对象的创建,以及对象依赖关系的管理,也就是所谓的IOC(控制反转)和DI(依赖注入)。而其他的功能(例如AOP、事务)都相当于是扩展功能。
在这里插入图片描述

Spring的核心组件

先看看Spring有哪些比较核心的组件,没有列全,只列了一些主要的,混个眼熟。

在这里插入图片描述
其中DefaultListableBeanFactory是Bean工厂的最终实现类,最终使用到的Bean工厂就是它,而它同时又实现了BeanDefinitionRegistry(bean定义注册表),所以它也是一个bean定义注册表。

Spring是如何实现IOC和DI的?

定义了BeanDefinition

首先,Spring把我们交给他管理的对象,称为Bean,然后又定义了BeanDefinition(Bean定义)去描述这些Bean

那为什么要弄一个BeanDefinition呢?

  1. BeanDefinition相当于是对Class类元数据信息的一种扩展

  2. BeanDefinition就像是造房子前先要有的设计图纸,然后根据设计图纸来造房子
    在这里插入图片描述
    因为Spring不是简单的帮我们创建一个对象就完事,他需要知道如何创建这个对象如何给它的属性赋值注入哪些值有哪些生命周期回调函数等等,单凭一个Class对象,不足以描述一个Bean的所有属性。
    因此,Spring把Bean的作用域,是否懒加载,属性依赖信息,自动装配模型等描述信息,封装到了BeanDefinition中。
    有了BeanDefinition之后,Spring就可以根据BeanDefinition去实例化和初始化Bean
    在这里插入图片描述

扫描加载BeanDefinition

先有BeanDefinition,再有bean。

接下来Spring就要扫描我们的配置文件或注解,加载BeanDefinition,注册到Spring容器里面,正确来说应该是放到一个Map中,这个Map的名字叫beanDefinitionMap,key就是beanName,value就是BeanDefinition。

而这个工作是通过BeanDefinitionReader完成的。
如果是XML的配置方式,则通过XmlBeanDefinitionReader完成BeanDefinition的加载和注册,
如果是注解的配置方式,则通过AnnotatedBeanDefinitionReader完成BeanDefinition的加载和注册。

BeanDefinition最后会被注册到DefaultListableBeanFactory(也就是我们常说的的Bean工厂)的beanDefinitionMap中,DefaultListableBeanFactory实现了BeanDefinitionRegistry接口,所以拥有了BeanDefinition注册表的功能。
在这里插入图片描述

根据BeanDefinition进行Bean的实例化和初始化

有了BeanDefinition,就可以根据BeanDefinition进行Bean的实例化和初始化了。

实例化

首先是根据BeanDefinition进行Bean的实例化,实例化是通过反射完成的,会通过反射获取Constructor构造器对象,然后调用newInstance方法进行实例化。

但是一个类中可能有多个构造函数,具体要用哪个呢?
这就要看BeanDefinition中关于构造器参数的描述信息constructorArgumentValues,获取最匹配的构造器。这个步骤叫做推断构造
如果没有指定构造器参数,则使用的是默认的构造器。
在这里插入图片描述

属性赋值

Bean实例化之后,就要对Bean进行属性赋值,也就是依赖注入

Spring会根据BeanDefinition中记录的autowireMode自动注入模型,使用对应的策略进行依赖注入。

不同的自动注入模型,对应使用不同的策略为该Bean的属性找到匹配的值或对象:

  • byName:BeanFactory#getBean(属性名)
  • byType:根据类型从Spring容器中获取beanName,再通过BeanFactory#getBean(beanName)获取bean,这里如果获取到多个相同类型的bean,还要推断出一个最匹配的
  • constructor:构造器注入,不在这里处理,在实例化的时候已经处理了
  • no:不使用自动注入,使用我们手动指定的值(也就是配置文件指定要注入的属性值),手动指定的值保存在BeanDefinition的propertyValues中

为该Bean的属性找到匹配的值后,并不是马上进行属性赋值,而是收集到一个PropertyValues中,后面再根据PropertyValues为该Bean的所有属性进行赋值。PropertyValues相当于是一个对bean的不同属性要赋哪些值的描述对象。
BeanDefinition中的propertyValues也是一个PropertyValues对象,保存的是我们手动指定的属性值,这里会合并为一个PropertyValues对象。

属性赋值当然也是通过反射进行,通过setXXX方法的方法对象Method的 Method#invoke(…) 方法进行属性赋值。
在这里插入图片描述

保存到单例缓冲池

初始化完成后的Bean,就会放入到单例缓存池中。

这个单例缓存池的名字叫singletonObjects,也是一个Map,key是beanName,value是对应的Bean,当我们需要某个Bean时,就从该缓存池中返回。
在这里插入图片描述

一个Bean从创建到销毁都会经历哪些步骤?

上面这个流程是比较粗糙的,一个Bean在Spring中是有一个完整的生命周期的,也就是在实例化、初始化、初始化完成、再到销毁,有哪些步骤,要干哪些事情。

在这里插入图片描述

以上就是bean的生命周期的详细过程。

xxxAware接口是用于获取Spring自身的bean的,比如实现了BeanFactoryAware接口,Spring会回调setBeanFactory方法,我们就可以获取到BeanFactory,通过实现BeanNameAware接口获取beanName也是同理。

BeanPostProcessor是Spring提供的扩展点,该接口有两个方法postProcessBeforeInitializationpostProcessAfterInitialization(也就是before方法和after方法),会在bean初始化的时候被回调。用于在执行beab的初始化方法的前后,对该bean进行一些特殊处理,比如可以动态代理返回一个代理对象,替换掉原来的bean。
我们可以定义自己的BeanPostProcessor实现,然后注册到Spring容器中。
在这里插入图片描述

bean的初始化方法和销毁回调方法,我们应该比较熟悉。我们可以通过接口的方式xml注解,配置初始化方法和销毁回调方法。

生命周期阶段接口xml注解
初始化InitializingBean#afterPropertiesSetinit-method@PostConstruct
销毁DisposableBean#destroydestroy-method@PreDestroy

Spring怎么完成实例化的?

首先,推断构造 并且 通过构造器反射实例化 bean对象的这个过程,是在AbstractAutowireCapableBeanFactory的createBeanInstance方法里面进行的,AbstractAutowireCapableBeanFactory是DefaultListableBeanFactory的父类。
在这里插入图片描述

推断构造

而推断构造的过程,是由一个bean后置处理器完成的,它就是AutowiredAnnotationBeanPostProcessor,在createBeanInstance方法里面,Spring会获取所有的bean后置处理器,判断是否是SmartInstantiationAwareBeanPostProcessor类型的bean后置处理,如果是,则回调它的determineCandidateConstructors(beanClass, beanName)方法,这里就会回调AutowiredAnnotationBeanPostProcessor的determineCandidateConstructors(beanClass, beanName)方法
在这里插入图片描述
AutowiredAnnotationBeanPostProcessor的determineCandidateConstructors(beanClass, beanName)方法又是怎么处理的呢?

首先会通过beanClass.getDeclaredConstructors()获取所有的构造器,beanClass就是Class对象,然后遍历所有构造器对象,看构造器是否带@Autowired注解,注解required属性是否为true,是否无参构造等等,然后保存遍历结果

遍历完成后对遍历结果做处理
在这里插入图片描述
返回的其实不是一个构造器,而是构造器数组
在这里插入图片描述

其实推断构造这里还有一个名为primaryConstructor(主构造器)的东西,这个东西只有时Kotlin类才有值,非Kotlin类则为null,所以我们直接忽略。

实例化

因为推断构造返回的是一个构造器数组,所以还要从数组中筛选出最合适的构造器

  • 返回的构造器数组,如果是空,Spring会使用无参构造器进行实例化,如果没有无参构造器,就会报错
  • 构造器数组不是空,那么就要从数组中挑一个最合适的

最后就是通过InstantiationStrategy进行反射实例化,InstantiationStrategy顾名思义就是实例化策略,默认实现类是
SimpleInstantiationStrategy,它的instantiate方法,又调用了BeanUtils.instantiateClass(ctor, args),里面会通过ctor.newInstance(…)反射实例化,ctor就是最合适的构造器,args就是构造器参数。
在这里插入图片描述
那么,这个最合适的构造器,怎么挑选呢?

以下逻辑稍微有点复杂,如果对这一块不感兴趣的小伙伴,可以忽略

这里先记住两个概念:argsToUse将要使用的参数,constructorToUse将要使用的构造器

  1. 首先,如果我们主动传参(手动getBean并传入了参数),则argsToUse使用我们传入的参数
  2. 如果没有我们没有传参,则看BeanDefinition中是否缓存了曾经处理过的构造器和构造参数,如果有则用缓存的
  3. 缓存也没有,则要在构造器数组中挑选
  4. 如果构造器数组只有一个无参构造,并且我们没有主动传参,使用无参构造,并缓存到BeanDefinition
  5. 否则,先确定最少需要的参数个数minNrOfArgs,我们主动参数了,则minNrOfArgs就是我们传的参数个数,如果没有,但是我们在配置中指定了构造参数,则minNrOfArgs就是我们指定的参数的个数,再否则,就是0
  6. 对构造器进行排序,public优先,构造器参数多的优先
  7. 遍历构造器数组
  8. 如果已经挑选出了构造器和构造参数(constructorToUse != null && argsToUse != null),并且当前构造器所需参数更少,那么就break,也就是后面的构造器都不会比已经挑选出的更合适
  9. 如果当前构造器参数少于 minNrOfArgs,忽略
  10. 如果我们没有主动传参,则Spring要获取传给构造器的参数,如果我们在配置中指定了构造参数,则取我们在配置中指定的,如果没有,则要通过getBean去取(自动注入)
  11. 计算取得参数与当前构造器所需参数的差值
  12. 保留差值更小的
  13. 遍历结束
  14. 如果非主动传参,则把推断结果缓存到BeanDefinition中
  15. constructorToUse和argsToUse交给InstantiationStrategy,反射实例化
    在这里插入图片描述

Spring怎么完成依赖注入?

以下是Spring对依赖注入的具体处理逻辑。

byName

byName的依赖注入比较简单,就是以属性名作为beanName,调用BeanFactory#getBean(属性名),从Spring容器中获取对应的bean,然后通过反射进行注入。
在这里插入图片描述

byType

byType稍微复杂一点。
byType使用到了DefaultListableBeanFactory的resolveDependency方法,这个方法顾名思义就是解决依赖关系

byType的初始处理

  1. 首先会根据属性名propertyName,生成一个属性描述符PropertyDescriptor,这个属性描述符是对当前bean的某个属性的描述。
  2. 然后根据属性描述符,生成MethodParameter对象,顾名思义,就是方法参数对象,里面包装了属性对应的setXXX方法,和set方法的参数信息。
  3. 然后根据MethodParameter对象,生成一个DependencyDescriptor依赖描述符。

在这里插入图片描述
PropertyDescriptor就是对bean的某个属性的一个描述对象。
在这里插入图片描述
MethodParameter就是对方法和方法参数等信息的封装。
在这里插入图片描述
DependencyDescriptor是对set方法的描述。这个描述符对象,包含了set方法所在类的Class,set方法名称,参数类型,set方法对应的属性名等。
DependencyDescriptor在后续DefaultListableBeanFactory的resolveDependency方法寻找符合依赖的bean时使用。
在这里插入图片描述
以上PropertyDescriptor、MethodParameter、DependencyDescriptor三个对象,其实也不是重点,如果记不住,可以直接忽略。

byType的核心处理

生成的DependencyDescriptor,然后就交给DefaultListableBeanFactory的resolveDependency方法。
然后,DefaultListableBeanFactory#resolveDependency有两步处理:

  1. 回调AutowireCandidateResolver的getSuggestedValue(DependencyDescriptor)方法,根据DependencyDescriptor依赖描述符获取对应的值。
  2. 如果AutowireCandidateResolver的getSuggestedValue方法没有返回有效的值。则通过bean的类型获取所有符合该类型的beanName,然后获取生成一个Map,key是beanName,value是beanName对应的bean,然后推断哪个bean更符合,则使用哪个bean注入。

在这里插入图片描述
这里出现了一个比较陌生的东西-AutowireCandidateResolver(自动装配候选解析器),其实它就是Spring定义的一个接口,里面有个getSuggestedValue方法。我们可以通过自定义一个AutowireCandidateResolver然后实现getSuggestedValue方法,实现自定义的依赖注入,例如为某些特定类型的属性,注入我们自己给定的值或者对象。比如Spring自己就实现了一个QualifierAnnotationAutowireCandidateResolver类型的AutowireCandidateResolver,getSuggestedValue方法处理@value注解修饰的属性的依赖注入,返回@Value注解上给定的值或者@Value指定的配置项对应的值。
在这里插入图片描述
如果AutowireCandidateResolver的getSuggestedValue方法返回了null,那么就要走接下来的步骤:通过bean类型获取所有类型匹配的beanName,进而获取所有类型匹配的bean,然后推断一个最合适的。

那如何推断最符合的bean呢?

在这里插入图片描述
其实就是四步:

  1. 如果被 @Primary 注解修饰的,优先使用,但是发现有多个被@Primary修饰的,则报错
  2. 如果没有@Primary注解修饰的,则看有没有被 @Priority ,如果有,则使用,发现多个被@Priority注解修饰的,则报错
  3. 如果也没有,则寻找beanName和属性名匹配的bean
  4. 如何还没有,如果该属性是必须要注入的,则报错,否则返回null
    在这里插入图片描述

@Autowired

@Autowired注解修饰的属性的处理,比较特别。
Spring自定义了一个BeanPostProcessor实现类AutowiredAnnotationBeanPostProcessor,再进行依赖注入时会进行回调,扫描被被@Autowired注解修饰的属性,然后最终会调用DefaultListableBeanFactory的resolveDependency方法,所以最后的处理逻辑是跟byType一致的。
在这里插入图片描述
@Autowired如果在字段上,则通过Field#set(…)进行反射注入
@Autowired如果在方法上,则通过set方法的Method对象的Method.invoke(…)方法进行反射注入

循环依赖怎么解决?

什么是循环依赖?

就是你中有我,我中有你~
在这里插入图片描述
这种情况如果不做特殊处理,就会造成死循环,也就是初始化A时,发现依赖了B,就去初始化B,而初始化B时,发现依赖了A,又去初始化A…

那Spring如何解决循环依赖呢?答案就是三级环境。

三级缓存解决循环依赖

第一级缓存就是单例缓存池singletonObjects,这个是在bean初始化完成后才放入进去的,而依赖注入是在bean初始化的时候进行的,显然不能解决循环依赖。
所以应该多加一个二级缓存,在bean实例化后,初始化前,提前放入到二级缓存中,这叫提前暴露。然后当进行依赖注入时,发现属性所依赖的bean又依赖到自己,那就直接从缓存中拿
在这里插入图片描述

那看来搞两级缓存就可以了,为什么要搞三级呢?
那是因为Spring还要处理AOP处理,假如一个bean后面要进行AOP处理,返回一个代理对象,那这里提前暴露的又是原对象,就会出现注入的类型和最后放入到单例缓存池的类型不一致
在这里插入图片描述
所以再多搞一级缓存,也就是第三级缓存
第三级缓存存放什么呢?存放的是把bean包装一层后的ObjectFactory对象ObjectFactory#getObject()返回里面的bean。为什么要包装一层呢?因为不知道这个bean需不需要进行AOP,如果不需要,则返回原来的bean,如果需要,那么就要提前进行AOP处理,返回代理对象,这样依赖的bean和最后放入单例池的都是同一个对象(代理对象)。
在这里插入图片描述
然后依赖注入时获取依赖的bean就变成这样的流程:
在这里插入图片描述

三级缓存中返回的对象,放入二级缓存,然后从二级缓存删除。
实例化完成后,从二级缓存删除,放入一级缓存。
在这里插入图片描述
到这里,Spring最核心的原理基本上就差不多了,如果这些都能弄懂的话,可以说是对Spring有一个比较深入的理解。

当然Spring还有一些扩展的东西,比如AOP,事务,还有其他的扩展点。

Spring怎么实现AOP的?

AOP是什么?为什么会有AOP?

AOP的意思就是面向切面编程,所谓切面,可以简单理解为横跨了多个方法,就像一个切面一样,在各方法前(或者后)都要执行的代码。

比如处理请求前打印日志,进行权限校验等,这些代码在每个请求方法前都要执行,但是不可能每个方法都重复写一遍,所以通过AOP,就可以把这些每个地方都要执行的代码,提取到切面中去

AOP的核心组件

先看一下AOP的核心组件
在这里插入图片描述
有了解过AOP的应该对这些都很熟悉。

AOP处理

AOP处理要做的事情,就是在被Pointcut命中的方法前后(具体是前是后,要看Advice类型),织入Advice定义的增强逻辑,织入的方式就是创建一个代理对象在执行方法前(或者后),执行增强逻辑
在这里插入图片描述
经过代理增强之后,目标方法就会被包裹起来,根据不同的Advice(通知)类型,在它的前前后后,会有不同的增强逻辑:
在这里插入图片描述

Spring中的AOP实现

Spring首先实现了一个BeanPostProcessor(bean后置处理器),AnnotationAwareAspectJAutoProxyCreator,它的祖先类AbstractAutoProxyCreator在after方法定义了AOP的入口,在after方法中检查是否要对bean进行AOP处理,如果需要,则进行AOP处理。
在这里插入图片描述
检查一个bean是否需要进行AOP处理,其实就是拿到所有我们定义的Pointcut,看bean里面的方法有没有被Pointcut切中的,有的话,就要进行AOP处理。
Pointcut是通过MethodMatcher方法匹配器的matches方法,与方法进行匹配,匹配结果返回true或者false。

如果需要进行AOP处理,则要进行动态代理,生成代理对象。
代理方式有JDK动态代理和CGLib两种,如果bean实现了接口,则使用JDK动态代理,否则使用CGLib
生成的代理对象里,会保存一个Advisor[] 增强器数组,在方法调用时会触发Advisor里的Advice增强逻辑的调用。
在这里插入图片描述
在进行方法调用时,就会从Advisor中筛选出需要执行的Advisor(一个类中有多个方法,可能被不同的Pointcut切中,就要进行不同的增强逻辑),这个筛选也是通过Pointcut(一个Advisor会有一个Pointcut与之对应) 进行匹配。从筛选出的各个Advisor中取出Advice,与目标方法一起,封装为一个执行器链。然后就是责任链模式的链式调用
在这里插入图片描述

Spring怎么实现事务的?

Spring是通过AOP实现事务的,也就是通过AOP处理,把被@Transactional注解修饰的目标方法包裹在try-catch块里面,在进入try-catch之前,先开启事务,然后在try块中执行目标方法,如果目标方法报错,则在catch块中回滚事务,如果没有报错,则在try-catch块之后提交事务。
在这里插入图片描述

Spring提供了哪些扩展点?

这里就简单罗列一下Spring的扩展点、扩展点的用途、以及例子,具体细节就不展开说明了。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值