Spring的IOC,你真的能解释清楚吗?

3,581 阅读9分钟

一直以来,SpringFramework 作为 Java 企业级开发的老大哥,面试中也常被问到。虽说有些基础性的问题可能不那么特别被面试官和求职者重视,但如果真的问起来,能不能回答的准确、全面、有深度,还是很容易体现出水平的。

在接下来的一个系列中,我会慢慢盘点一些 Spring 中常见但不好回答全面的问题,跟小伙伴们分享。

本文主题:Spring 的 IOC 如何回答的尽可能全面、准确、有深度。

问:什么是IOC?

这是一个看上去特别简单、感觉很无脑的问题,很多求职者在回答这个问题时会不加思考、快速回答出一个很简单但同时也没有什么含量的答案。

草率的回答

  • IOC 是控制反转,Inverse of Control 。

试问一句,亲,你在做名词翻译吗?

就算真的是在做名词翻译,也不应该只是把这个简称的全称解释出来就完事吧,好歹的展开解释点东西也好吧,作为面试官不应该只想听到这么一点点吧。

方向偏了

  • IOC 是控制反转,它把对象间的依赖关系的维护权利交给了 Spring ,程序本身不再维护。

这里面大体上把 IOC 的核心思想解释出来了:对象间的依赖关系的维护权利发生了转移,但是请小伙伴们注意,我们在问 IOC ,这个问题仅仅是问 IOC 本身,与具体的技术无关IOC 不止有 Spring ,只是当下最强大的、使用最广的是 Spring 而已。

所以小伙伴们在回答理论、概念等问题时,不要直接在概念解释中提到具体的技术,技术都是概念和理论的落地实现,不止一种。单把一种拉出来,面试官可能会觉得:你是不是只知道这个?

一种参考回答

该答案仅供参考,可根据自身的知识储备动态调整。

IOC 全名控制反转 Inverse of Control,它是一种编程原则,它的设计和架构可以实现组件间的解耦,核心思想是将控制权转移出去

这里面提到了几个点:

  • 编程原则:它是一种理论,而非具体的某种技术落地
  • 组件间的解耦:所谓耦合,就是上面提到的对象间的依赖关系解耦,就是解除了对象间的依赖关系
    • 提到解耦,有可能面试官会继续问“什么是解耦”,也有可能不会问 “为什么用 Spring ”了
  • 控制权的转移:IOC 为了实现解耦,将原有的对象间的主动依赖改为被动接收型依赖(由直接 new 变为 set )

问:IOC与DI的区别

如果对 IOC 的实现不是特别了解,或者只是用 SpringFramework 用的太习惯了,亦或是刻板的学习 SpringFramework ,那这个答案通常会是这样的:

  • IOC 就是 DI 。

如果回答出这个答案,而面试官碰巧也跟你一样,那恭喜你“瞎猫碰到死耗子”了!因为这个回答真的大错特错啊,IOC 不止有 DI 的!

正确的回答应该是:

  • IOC 是一种思想、编程原则,DI 是 IOC 思想的一种实现方式。
  • IOC 的实现方式有依赖查找( Dependency lookup )和依赖注入( Dependency Injection )。

上面已经介绍过了,IOC 仅仅是一种思想,它的意图是想让对象间的依赖控制发生转换。用过 SpringFramework 的小伙伴都知道,你没有在哪个地方见过直接写 IOC 的代码,都是由一些实现方式来体现 IOC 的。

如果按照上面这样回答,可能会引来下面一个问题:

依赖查找和依赖注入分别都是什么?如何区分它们?

针对这个问题,最好不要一上来就搬出代码解释,最好是先理论后代码。

一般情况下,对比依赖查找和依赖注入,通常可以从以下几个维度对比:

依赖查找 依赖注入
实现方式 使用上下文(容器)主动获取 依赖上下文被动接收
作用目标 通常是方法体内的局部变量,也可以是对象成员 通常是对象成员
API依赖 依赖 IOC 框架的 API(必须操纵容器的 API ) 可以不依赖(暴露 setter 方法即可)
applicationContext.getBean(beanName) public void setXXX() { ... }

问:SpringFramework中实现的IOC有什么?

真的不会有小伙伴只能答出 ApplicationContext 吧,一开始学的时候应该知道还有个 BeanFactory 吧。这是 SpringFramework 中两个核心的 IOC 容器的抽象。BeanFactory 仅仅是提供了一个容器管理的基本能力,ApplicationContext 在此基础上做了更加完善、强大的扩展。具体的对比可以参照下表:

Feature BeanFactory ApplicationContext
Bean instantiation/wiring —— Bean的实例化和属性注入 Yes Yes
Integrated lifecycle management —— 生命周期管理 No Yes
Automatic BeanPostProcessor registration —— Bean后置处理器的支持 No Yes
Automatic BeanFactoryPostProcessor registration —— BeanFactory后置处理器的支持 No Yes
Convenient MessageSource access (for internalization) —— 消息转换服务(国际化) No Yes
Built-in ApplicationEvent publication mechanism —— 事件发布机制(事件驱动) No Yes

下面提供一个比较完整的示例答案,小伙伴们可以根据自己的知识储备和理解,调整这里面的一些描述细节:

BeanFactory 接口提供了一个抽象的配置和对象的管理机制ApplicationContextBeanFactory 的子接口,它简化了与 AOP 的整合、消息机制、事件机制,以及对 Web 环境的扩展WebApplicationContext 等),BeanFactory 是没有这些扩展的。

ApplicationContext 主要扩展了以下功能:(括号内的部分是解释扩展功能的一些简单描述或者原理底层实现,能回答出来更好)

  • AOP的支持AnnotationAwareAspectJAutoProxyCreator 作用于 Bean 的初始化之后 )
  • 配置元信息BeanDefinitionEnvironment 、注解等 )
  • 资源管理Resource 抽象 )
  • 事件驱动机制ApplicationEventApplicationListener
  • 消息与国际化LocaleResolver
  • Environment 抽象(SpringFramework 3.1以后)

问:依赖注入的注入方式?有什么区别?

注意这个问题也是与 SpringFramework 无关的,注入的方式本身就应该是依赖注入的实现,至于框架的代码,那是人家对于这个方式的落地。

可从以下几个维度对比:

注入方式 被注入成员是否可变 是否依赖IOC框架的API 使用场景
构造器注入 不可变 否(xml、编程式注入不依赖) 不可变的固定注入
参数注入 不可变 是(只能通过标注注解来侵入式注入) 通常用于不可变的固定注入
setter注入 可变 否(xml、编程式注入不依赖) 可选属性的注入

基本上问这个问题的话,还可能会继续问另一个问题:

你觉得哪种方式好?为什么?

“莽夫”应聘者一看终于来开放式问题了,赶紧开始自由发挥了:

我觉得参数注入好,因为我写习惯了,给参数打注解多舒服啊!

你是这么说了,面试官咋想:就这?就这???进而对你的印象可能就会有所减分了。

这种问题,除了要表述主观看法之外,更多的是要根据一些既有的论述来辅助你的观点,最好的论述那一定是官方文档了。

SpringFramework 的官方文档在不同的版本推荐的注入方式是不同的:

  • SpringFramework 4.0.2 及之前是推荐 setter 注入,理由是一个 Bean 有多个依赖时,构造器的参数列表会很长;而且如果 Bean 中依赖的属性不都是必需的话,注入会变得更麻烦
  • 4.0.3 及以后官方推荐构造器注入,理由是构造器注入的依赖是不可变的、完全初始化好的,且可以保证不为 null
  • 当然 4.0.3 及以后的官方文档中也说了,如果真的出现构造器参数列表过长的情况,可能是这个 Bean 承担的责任太多,应该考虑组件的责任拆解

问:组件注入的注解有什么?有什么区别?

相信大多数小伙伴都能答出 @Autowired@Resource 吧,如果答出这两个,那证明你应该用过,也会用。但你能回答出 @Inject ,证明你对这些注入的注解确实有了解。作为应聘者,在回答问题时一定是回答的尽可能全面为好,下面对这几种注解作一个对比:

注解 注入方式 是否支持@Primary 来源 Bean不存在时处理
@Autowired 根据类型注入 SpringFramework原生注解 可指定 required=false 来避免注入失败
@Resource 根据名称注入 JSR250规范 容器中不存在指定Bean会抛出异常
@Inject 根据类型注入 JSR330规范 ( 需要导jar包 ) 容器中不存在指定Bean会抛出异常

跟上面差不多,如果问到了这个问题,那就有可能继续被问到下面一个问题:

存在多个相同类型Bean时如何解决注入问题?

可能大多数小伙伴都能答出以下几种解决方案:

  • @Resource :根据名称指定注入的 Bean
  • @Qualifier :配合 @Autowired 注解使用,如果被标注的成员 / 方法在根据类型注入时发现有多个相同类型的 Bean ,则会根据该注解声明的 name 寻找特定的 bean
  • @Primary :配合 @Bean 注解使用,如果有多个相同类型的 Bean 同时注册到 IOC 容器中,使用 @Autowired@Inject 注解时会注入标注 @Primary 注解的 bean

其实你还可以提另外一种方案:把注入的字段名与 bean 的名称保持一致,这样也可以解决注入时报不唯一 Bean 的问题。

以上几个问题是关于 SpringFramework 与 IOC 部分的一些常见问题,倒是问题都不太陌生,但是小伙伴们想回答的全面、有深度,还是需要下下功夫的。希望小伙伴们能有所收获,在面试中流利回答,斩获 offer !


问题整理不易,不点个赞支持一下作者吗?(可怜巴巴)