前言
估计市面上没几个 Java 开发人员会不知道 Spring Framework 这个开源框架以及相应的全家桶框架,会用 Spring 的大有人在,但是你真的能解释清楚 Spring Framework 的详细内容吗?可以说面试很少会问你怎么用,但是绝对会问你某个功能怎么实现。因此,接下来让我们通过相应的面试题来走进 Spring Framework 的底层。
下面我们通过 3 道常问的面试题进行相关知识点的学习:
- 如果要你实现 Spring IOC,你会注意哪些问题?
- BeanFactory 和 FactoryBean 有什么区别,Bean 是怎么加载出来的?
- AOP是什么,怎么实现,Spring 事务和它有什么关系?
为了防止阅读疲劳,这三个问题会分别写在三个博客上面,读者按需阅读即可,但如果对 Spring 相关内容不熟悉,建议按照博客顺序进行阅读,因为这三个问题是自下而上的进行依赖。以下文章为第一个问题的解读。
1、如果要你实现Spring IOC,你会注意哪些问题
一般面试官问这个问题,基本上就是考验你对 Spring IOC 的设计思路理解如何,当然我们也不可能将 Spring IOC 的所有接口和实现类过一遍,我们只需要了解 IOC 容器最根基的内容即可。
首先,我们从宏观的角度进行分类,对整个 Spring IOC 有一个设计方向上的了解。我将 Spring IOC 的接口/实现类分为两大类,分别是:
- Factory 结尾的接口/类:最基本的容器,提供了 bean 相关的创建和存取,代表有 BeanFacotry 、DefaultListableBeanFactory
- Context 结尾的接口/类:通过组合的形式,将 xxxFactory 类作为成员变量,除了可以提供 bean 相关的创建和存取功能,同时也带有额外的扩展功能,例如国际化、资源加载、事件发布等。代表有 ApplicationContext 、ClassPathXmlApplicationContext
从上面可以得知,如果我们需要实现最基本的 IOC 容器,那么只需要参考 Factory 的类型即可。
目前 Spring5 中 最顶级的 Factory 接口—— BeanFactory ,只有一个可用实现类,那就是 DefaultListableBeanFactory,这个类便是一个可以独立使用的 IOC 容器,也就是说,我们可以通过了解这个类,来了解如何实现一个基本的 Spring IOC 容器。下面,我们通过查看 DefaultListableBeanFactory 的相关类图,来看看一个完善的 IOC 容器需要具备哪些功能:
同样的,我将 DefaultListableBeanFactory 的类图也区分为两个类型:
1.第一个则是左边框住的规范工厂接口类型,主要用来定义一个 bean 的工厂需要哪些功能
2.第二个则是剩下的部分接口、抽象类和具体实现类,主要是实现注册、创建、获取 bean 的功能来辅助工厂
接下来我们一个个梳理这些接口、抽象类和实现类的主要功能,一层一层的剥开 Spring IOC 的面纱。
- 工厂规范接口类型:
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 类型的类使用进行工厂的配置 |
- 工厂辅助接口/实现类 和 具体工厂抽象类/实现类:
name | 作用 |
---|---|
AliasRegistry | 提供了关于别名的增删改查规范 |
SimpleAliasRegistry | 提供了基于 map 进行关于别名的增删改查的基本实现 |
SingletonBeanRegistry | 提供了单例 bean 的注册规范 |
DefaultSingletonBeanRegistry | 提供了单例 bean 的默认注册实现 |
FactoryBeanRegistrySupport | 提供了 FactoryBean 的注册实现,这里 FactoryBean 生产出来的 bean 有单例也有原型 |
AbstractBeanFactory | 主要提供了获取单个 bean 的具体实现 |
AbstractAutowireCapableBeanFactory | 主要提供了创建 bean 的具体实现 |
BeanDefinitionRegistry | 提供了注册 BeanDefinition 的规范 |
DefaultListableBeanFactory | 提供了注册 BeanDefinition 的实现,提供了获取多个 bean 的实现,提供了配置工厂的实现 |
最后,我总结一下一个 IOC 容器所应该具备的基本功能:
- 对外提供根据某个标识获取 bean 的功能,例如通过唯一的名字获取,或者通过类型进行获取。
- 定义用来描述 bean 的配置的Java类,方便工厂进行统一解析生产 bean
- 对内实现具体的创建 bean 的功能,创建的时候要考虑是单例还是原型,是否存在工厂模式类型的 bean 等,创建的过程是否需要增强,例如添加后置器等。
这里我解释一下第二点,在 Java 中,一切对象可以通过 Class 这个类进行描述,而对于 IOC 容器,同样也有描述所有 bean 的类,例如 Spring IOC 中的 BeanDefinition , 其主要是用来描述所需要生产的 bean 的构造函数、字段属性、依赖类等信息,可以理解为 bean 的生产说明书,工厂根据这些说明书进行 bean 的一一生产,即我们不需要管 bean 信息的来源,我们只负责具体的生产工作。
举个例子:
我们将 DefaultListableBeanFactory 比作为 3D打印机,3D打印机本身没有产出设计图纸的功能,但是他可以接受某种格式的图纸切片文件,将这些文件放入打印机并且启动后,便可以进行打印工作,但是打印机一直处于被动的状态,他只会被动的接受打印指令信息以及被动的接受打印命令,并没有主动去打印的功能,其实 BeanFactory 本质上也是被动的,他只会被动的接受 BeanDefinition 对象,被动的接受获取 bean 的命令来创建、返回相应的 bean 给用户
BeanFactory | 3D 打印机 |
---|---|
本身无法产出 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 容器的时候需要注意什么?
- 通过定义描述 bean 的配置规范来实现工厂的统一生产,而这个规范需要考虑到多方面的因素,例如反射时候需要用到的类名、构造函数参数列表,注入属性时的类型(根据 name 还是根据 type),这个 bean 的类型(单例、原型、抽象、factoryBean)等,这些规范为 IOC 容器创建 bean 提供了具体的生产说明。
- 工厂需要提供相应的配置项,例如哪些 bean 不允许被生产管理(使用工厂的对象自己根据需求进行主动注册),类加载器需要用什么等
- 工厂创建单例 bean 的时候需要考虑上锁的情况,防止多个地方调用 getBean 方法且这个单例 bean 没有被生产的时候生产了多个
最后
该章节主要是向读者介绍了 Spring IOC 的宏观分类,以及最基础的 IOC 容器需要配备那些功能,希望读者在阅读完该章节时对 Spring IOC 有更深入的了解,接下来回到问题:
如果要你实现 Spring IOC,你会注意哪些问题?
如果你们可以很流畅的回答这个问题,那么恭喜你,该章节的内容已经全部掌握,如果不行,希望可以回到对应问题讲解的地方,或者对某个不了解的点进行额外的知识搜索,尽量用自己组织的语言回答这些问题。