六、通勤路上搞定 Spring 面试(1)

漫谈面试系列

前言

估计市面上没几个 Java 开发人员会不知道 Spring Framework 这个开源框架以及相应的全家桶框架,会用 Spring 的大有人在,但是你真的能解释清楚 Spring Framework 的详细内容吗?可以说面试很少会问你怎么用,但是绝对会问你某个功能怎么实现。因此,接下来让我们通过相应的面试题来走进 Spring Framework 的底层。

下面我们通过 3 道常问的面试题进行相关知识点的学习:

  1. 如果要你实现 Spring IOC,你会注意哪些问题?
  2. BeanFactory 和 FactoryBean 有什么区别,Bean 是怎么加载出来的?
  3. AOP是什么,怎么实现,Spring 事务和它有什么关系?

为了防止阅读疲劳,这三个问题会分别写在三个博客上面,读者按需阅读即可,但如果对 Spring 相关内容不熟悉,建议按照博客顺序进行阅读,因为这三个问题是自下而上的进行依赖。以下文章为第一个问题的解读。

1、如果要你实现Spring IOC,你会注意哪些问题

一般面试官问这个问题,基本上就是考验你对 Spring IOC 的设计思路理解如何,当然我们也不可能将 Spring IOC 的所有接口和实现类过一遍,我们只需要了解 IOC 容器最根基的内容即可。

首先,我们从宏观的角度进行分类,对整个 Spring IOC 有一个设计方向上的了解。我将 Spring IOC 的接口/实现类分为两大类,分别是:

  1. Factory 结尾的接口/类:最基本的容器,提供了 bean 相关的创建和存取,代表有 BeanFacotry 、DefaultListableBeanFactory
  2. Context 结尾的接口/类:通过组合的形式,将 xxxFactory 类作为成员变量,除了可以提供 bean 相关的创建和存取功能,同时也带有额外的扩展功能,例如国际化、资源加载、事件发布等。代表有 ApplicationContext 、ClassPathXmlApplicationContext

从上面可以得知,如果我们需要实现最基本的 IOC 容器,那么只需要参考 Factory 的类型即可。
目前 Spring5 中 最顶级的 Factory 接口—— BeanFactory ,只有一个可用实现类,那就是 DefaultListableBeanFactory,这个类便是一个可以独立使用的 IOC 容器,也就是说,我们可以通过了解这个类,来了解如何实现一个基本的 Spring IOC 容器。下面,我们通过查看 DefaultListableBeanFactory 的相关类图,来看看一个完善的 IOC 容器需要具备哪些功能:
DefaultListableBeanFactory类图

同样的,我将 DefaultListableBeanFactory 的类图也区分为两个类型:

1.第一个则是左边框住的规范工厂接口类型,主要用来定义一个 bean 的工厂需要哪些功能
2.第二个则是剩下的部分接口、抽象类和具体实现类,主要是实现注册、创建、获取 bean 的功能来辅助工厂

接下来我们一个个梳理这些接口、抽象类和实现类的主要功能,一层一层的剥开 Spring IOC 的面纱。

  1. 工厂规范接口类型:
name作用
BeanFactory定义了通过 name 获取单个 Bean 、判断是否为容器里面的 Bean 以及获取别名的规范,name 和 bean 是一一对应的,但 name 和别名是一对多的关系
ListableBeanFactory定义了通过 Class 来获取多个 Bean 的规范,由于面向对象的多态性,一个父类可以有多个子类,因此如果通过一个 Class 来获取 Bean ,有可能会获取到多个 Bean
HierarchicalBeanFactory定义了容器父子关系的规范,也就是允许容器可以有父子关系,例如 springMVC 和 spring 整合会出现父子容器的场景。默认实现下,父容器无法获取子容器的 bean ,但是子容器可以获取父容器的 bean
ConfigurableBeanFactory定义了对工厂配置的规范,例如设置类加载器、父级工厂、bean的默认依赖关系以及注册别名、作用域等相关配置类型方法
AutowireCapableBeanFactory定义了创建/初始化 bean 、注册/调用初始化前后的后置器、销毁对象的后置器等规范,同时他还提供了对非容器的 bean 进行主动注入的功能规范,不过这个功能一般用于集成其他第三方框架才用
ConfigurableListableBeanFactory这个类主要是提供了注入的 bean 的规则配置,例如忽略某个类型/接口的注入,某个类自动注入属性的时候注入指定对象等。该接口是针对工厂进行配置的集大成者,主要是提供给 xxxContext 类型的类使用进行工厂的配置

  1. 工厂辅助接口/实现类 和 具体工厂抽象类/实现类:
name作用
AliasRegistry提供了关于别名的增删改查规范
SimpleAliasRegistry提供了基于 map 进行关于别名的增删改查的基本实现
SingletonBeanRegistry提供了单例 bean 的注册规范
DefaultSingletonBeanRegistry提供了单例 bean 的默认注册实现
FactoryBeanRegistrySupport提供了 FactoryBean 的注册实现,这里 FactoryBean 生产出来的 bean 有单例也有原型
AbstractBeanFactory主要提供了获取单个 bean 的具体实现
AbstractAutowireCapableBeanFactory主要提供了创建 bean 的具体实现
BeanDefinitionRegistry提供了注册 BeanDefinition 的规范
DefaultListableBeanFactory提供了注册 BeanDefinition 的实现,提供了获取多个 bean 的实现,提供了配置工厂的实现

最后,我总结一下一个 IOC 容器所应该具备的基本功能:

  1. 对外提供根据某个标识获取 bean 的功能,例如通过唯一的名字获取,或者通过类型进行获取。
  2. 定义用来描述 bean 的配置的Java类,方便工厂进行统一解析生产 bean
  3. 对内实现具体的创建 bean 的功能,创建的时候要考虑是单例还是原型,是否存在工厂模式类型的 bean 等,创建的过程是否需要增强,例如添加后置器等。

这里我解释一下第二点,在 Java 中,一切对象可以通过 Class 这个类进行描述,而对于 IOC 容器,同样也有描述所有 bean 的类,例如 Spring IOC 中的 BeanDefinition , 其主要是用来描述所需要生产的 bean 的构造函数、字段属性、依赖类等信息,可以理解为 bean 的生产说明书,工厂根据这些说明书进行 bean 的一一生产,即我们不需要管 bean 信息的来源,我们只负责具体的生产工作。

举个例子:
我们将 DefaultListableBeanFactory 比作为 3D打印机,3D打印机本身没有产出设计图纸的功能,但是他可以接受某种格式的图纸切片文件,将这些文件放入打印机并且启动后,便可以进行打印工作,但是打印机一直处于被动的状态,他只会被动的接受打印指令信息以及被动的接受打印命令,并没有主动去打印的功能,其实 BeanFactory 本质上也是被动的,他只会被动的接受 BeanDefinition 对象,被动的接受获取 bean 的命令来创建、返回相应的 bean 给用户

BeanFactory3D 打印机
本身无法产出 Bean 的描述对象本身无法产出打印需要的指令
可以接受符合规范的 Bean 的描述对象可以接受 G-code 格式的文件作为打印指令
需要其他工具解析 bean 的信息并转为 BeanDefinition需要第三方工具对设计图纸进行解析切片,将图纸文件转换为 3D打印机可以用的指令 G-code
需要人为的将 BeanDefinition 导入进工厂进行需要人为的将打印指令导入到打印机
需要人为的去调用工厂的获取 bean 方法需要人为的去启动打印机进行打印获取模型
生产后的 bean 会被保存起来,方便下次获取,如果是原型,那就在生产一个打印出来的模型想用的时候可以随时获取使用,如果需要多个,那就接着打印生产

最后说一点,DefaultListableBeanFactory 这个 IOC 容器的 bean 都是懒加载的,我们平时使用 Spring ,在项目启动的时候,xxxContext 类会去调用这个 IOC 容器的相关方法去创建和管理 bean。
如果我们不借助 xxxContext 类,想要这个 IOC 容器可以创建 bean ,就得通过额外的工具类,这个工具类需要两个功能,第一个是解析 bean 的信息,第二个是调用工厂的方法将这些 bean 注册进去工厂,也就是说工厂的注册行为是一个被动行为,例如下面所示的代码案例,而这方面的内容,不是本文讨论的核心内容,有兴趣的读者可以自行去了解。

public static void main(String[] args) {
        DefaultListableBeanFactory  beanFactory = new DefaultListableBeanFactory();
        //需要通过一些解析工具来读取相应的 bean 信息
        //然后将这些 bean 信息转换为 BeanDefinition 对象,
        //最后将这些 BeanDefinition 对象注册到 DefaultListableBeanFactory 的一个 map 里面
        BeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        //下面这个方法会先解析 xml 文件,将里面的 bean 信息转换为 BeanDefinition 对象,
        //然后调用构造函数传入的工厂对象的注册方法进行注册。
        //执行完下面这个代码后,我们调用 beanFactory.getBean() 方法才能获取相应的内容。
        //如果没有执行以下代码,我们调用 beanFactory.getBean() 方法就会抛出没有相应 bean 的异常。
        //因为这个工厂里面没有对应的 bean 的生产说明书
        reader.loadBeanDefinitions("classpath:spring-bean.xml");
    }

回到第一个问题,我们实现 IOC 容器的时候需要注意什么?

  1. 通过定义描述 bean 的配置规范来实现工厂的统一生产,而这个规范需要考虑到多方面的因素,例如反射时候需要用到的类名、构造函数参数列表,注入属性时的类型(根据 name 还是根据 type),这个 bean 的类型(单例、原型、抽象、factoryBean)等,这些规范为 IOC 容器创建 bean 提供了具体的生产说明。
  2. 工厂需要提供相应的配置项,例如哪些 bean 不允许被生产管理(使用工厂的对象自己根据需求进行主动注册),类加载器需要用什么等
  3. 工厂创建单例 bean 的时候需要考虑上锁的情况,防止多个地方调用 getBean 方法且这个单例 bean 没有被生产的时候生产了多个

最后

该章节主要是向读者介绍了 Spring IOC 的宏观分类,以及最基础的 IOC 容器需要配备那些功能,希望读者在阅读完该章节时对 Spring IOC 有更深入的了解,接下来回到问题:

 如果要你实现 Spring IOC,你会注意哪些问题?

如果你们可以很流畅的回答这个问题,那么恭喜你,该章节的内容已经全部掌握,如果不行,希望可以回到对应问题讲解的地方,或者对某个不了解的点进行额外的知识搜索,尽量用自己组织的语言回答这些问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值