vlambda博客
学习文章列表

《完爆面试官》系列之Spring源码篇(上)


前言

HR小姐姐带领着我来到一间洽谈室中,眼前只见一张长长的会议桌,环顾四周,我立即找了个面向窗户的位子,这样有利于面试官可以清楚地看见索大的刷脸,然后缓缓地坐下来。HR和我简单的寒暄几句后,递给我一杯水,让我稍等一会,面试官马上就来了。

索大暗暗思道:这个小姐姐人还挺好,有礼貌,声音也很温柔,面试官应该也不赖吧!

面试开始

没过多久,一位身着格子衫+牛仔裤搭配,体型略显瘦高,戴着一个黑框框眼镜的男士,推开洽谈室的门。

索大礼貌性地起身问候:帅气的面试官,您好呀。

《完爆面试官》系列之Spring源码篇(上)

面试官仔细地翻阅索大的简历,来来回回翻了几次,心里暗道:

这家伙,整整写了五页纸,不知道是真有点东西还是凑字数的,待我来考考他吧

小伙子,看你简历写的挺多的,而且很多项目都是使用Spring框架,那你说下你对Spring的理解吧?

《完爆面试官》系列之Spring源码篇(上)

幸好索大之前有对Spring框架进行一个全面的复习,爽道:

Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标是简化Java企业级应用开发,并通过POJO为基础的编程模型促进良好的编程习惯。而他主要是通过:控制反转(IOC)、依赖注入(DI)以及面向切面(AOP)这三种方式来达成的。

spring之所以强大,是因为Spring有众多模块为我们提供了开发企业应用的一切

《完爆面试官》系列之Spring源码篇(上)
Spring的七个核心模块
  • Spring Core:核心类库提供spring框架的基本功能IOC服务。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。

  • Spring Context:Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,如JNDI、EJB、电子邮件、国际化、校验和调度功能。提供框架式Bean访问方式,其他程序可以通过Context访问Spring的Bean资源。

  • Spring AOP:Spring在它的AOP模块中提供了对面向切面编程的丰富支持。例如方法拦截器(method-interceptors)和切点(pointcuts),可以有效的防止代码上功能的耦合,这个模块是在Spring应用中实现切面编程的基础。

  • Spring DAO:DAO模块主要目的是将持久层相关问题与一般的的业务规则和工作流隔离开来。Spring 中的DAO提供一致的方式访问数据库,不管采用何种持久化技术,Spring都提供一致的编程模型。Spring的DAO模块对JDBC进行了再封装,隐藏了Connection、Statement、ResultSet等JDBC API,使DAO模块直接继承JdbcDaoSupport类。

  • Spring ORM:对现有的ORM框架的支持;

  • Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传;

  • Spring MVC:MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。Spring的MVC框架提供清晰的角色划分:控制器、验证器、命令对象、表单对象和模型对象、分发器、处理器映射和视图解析器。Spring支持多种视图技术。

你说下使用Spring框架的好处是什么?

索大回答这类问题,一般喜欢从他的特点来分析,比如:

(1)spring属于低侵入式设计,代码的污染极低;

(2)spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;

(3)Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。

(4)spring对于主流的应用框架提供了集成支持。

你说下什么是Spring IOC 容器?

IOC就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部资源。

索大这样回答其实还不能打动面试官,还要进行深入分析。在Spring容器中,

谁控制谁?:当然是IoC 容器控制了对象

控制了什么?:主要控制了外部资源获取(不只是对象包括比如文件等)

什么方面反转了?:获得依赖对象的方式反转了

通过一张图来解释从IOC容器获取对象的过程:

《完爆面试官》系列之Spring源码篇(上)

我们来总结下实现原理Spring的IOC容器是通过反射机制+工厂模式实现的,在实例化一个类时,它通过反射调用类中set方法将事先保存在HashMap中的类属性注入到类中。

*回答到这里,如果不顺便把**依赖注入(DI)*说下,似乎不能体现索大的逼格啊

《完爆面试官》系列之Spring源码篇(上)

依赖注入(Dependency Injection):就是说组件bean之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件bean之中。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

不过我们需要注意两个细节:

  1. 依赖注入发生的时间 (1).用户第一次通过getBean方法向IoC容索要Bean时,IoC容器触发依赖注入。(2).当用户在Bean定义资源中为元素配置了lazy-init属性,即让容器在解析注册Bean定义时进行预实例化,触发依赖注入。
  2. 依赖注入实现在以下两个方法中:(1).createBeanInstance:生成Bean所包含的java对象实例。(2).populateBean :对Bean属性的依赖注入进行处理。

那你知道Spring的注入方式有哪几种?

常见的注入方式有三种:setter 属性注入、构造方法注入和注解方式注入。

  • Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。

  • 构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。

  • 注解方式注入:使用@Autowired注解来自动装配指定的bean。@Autowired可用于:构造函数、成员变量、Setter方法。

    如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;

    如果查询的结果不止一个,那么@Autowired会根据名称来查找;

    如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。

注意:@Autowired和@Resource之间的区别

  • @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。

  • @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。

你知道spring循环依赖是怎么解决的?

索大先假设场景如下,A->B->A

1、实例化A,并将未注入属性的A暴露出去,即提前曝光给容器Wrap 2、开始为A注入属性,发现需要B,调用getBean(B) 3、实例化B,并注入属性,发现需要A的时候,从单例缓存中查找,没找到时继而从Wrap中查找,从而完成属性的注入 4、递归完毕之后回到A的实例化过程,A将B注入成功,并注入A的其他属性值,自此即完成了循环依赖的注入

《完爆面试官》系列之Spring源码篇(上)
spring循环依赖流程图

首先我们要清楚Spring单例对象的初始化可以分为三步:

  1. 【createBeanInstance】, 实例化, 实际上就是调用对应的构造方法构造对象, 此时只是调用了构造方法,spring xml中指定的property并没有进行populate
  2. 【populateBean】,填充属性, 这步对spring xml中指定的property进行populate
  3. 【initializeBean】,调用spring xml中指定的init方法, 或者AfterPropertiesSet方法

会发生循环依赖的步骤集中在第一步和第二步

然后我们还要对Spring的三级缓存有所了解

对于单例对象来说,在Spring的整个容器的生命周期内, 有且只存在一个对象,很容易想到这个对象应该存在Cache中,Spring大量运用了Cache的手段,在循环依赖问题的解决过程中甚至使用了“三级缓存”。

  1. singletonObjects:指单例对象的cache
  2. earlySingletonObjects:指提前曝光的单例对象的cache
  3. singletonFactories:指单例对象工厂的cache

Spring解决循环依赖的步骤:

首先Spring会尝试从缓存中获取,这个缓存就是指singletonObjects;

  1. Spring首先从singletonObjects(一级缓存)中尝试获取,
  2. 如果获取不到并且对象在创建中,则尝试从earlySingletonObjects(二级缓存)中获取;
  3. 如果还是获取不到并且允许从singletonFactories通过getObject获取,则通过singletonFactory.getObject()(三级缓存)获取
  4. 如果获取到了则移除对应的singletonFactory, 将singletonObject放入到earlySingletonObjects, 其实就是将三级缓存提升到二级缓存中!

解决循环依赖的关键, 单例对象此时已经被创建出来的。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。

最后索大来个小结:

Spring循环依赖的理论依据其实是Java基于值传递,传递引用, 当我们获取到对象的引用时,对象的field或者或属性是可以延后设置的。

Spring通过三级缓存加上“提前曝光”机制,配合Java的对象引用原理,比较完美地解决了某些情况下的循环依赖问题!

面试结束

面试官上下打量着我,毫不吝啬地夸了一句:

小伙子,不赖呀。

这个时候,一定不要激动,可以给个低调奢华的表情

《完爆面试官》系列之Spring源码篇(上)
《完爆面试官》系列之Spring源码篇(上)

总结

Spring 是一个主流的 Java Web 开发框架,该框架是一个轻量级的应用框架,具有很高的凝聚力和吸引力。Spring 框架因其强大的功能以及卓越的性能而受到众多开发人员的喜爱。涉及到的知识远不止于此,我们要脚踏实地地去实践,然后理解并掌握其中的原理。

参考资料:

  1. Spring官网 https://spring.io/
  2. 计文柯 《深入理解Spring技术内幕》
  3. 郝佳     《Spring源码深度解析》

花絮

以上就是本篇文章的全部内容了,谢谢大家的阅览,各位的支持和鼓励是索大前进的动力,下一篇我

们不见不散。

码字不易啊,喜欢的话不妨点赞👍 关注💓分享给朋友👥,这对我真的很重要呀!