01|原始IoC:如何通过BeanFactory实现原始版本的IoC容器? -- 知识铺
01|原始IoC:如何通过BeanFactory实现原始版本的IoC容器?
你好,我是郭屹,从今天开始我们来学习手写MiniSpring。 这一章,我们将从一个最简单的程序开始,一步步堆积演化,最后实现Spring这一庞大框架的核心部分。这节课,我们就来构造第一个程序,也是最简单的一个程序,将最原始的IoC概念融入我们的框架之中, 我们就用这个原始的IoC容器来管理一个Bean。 不过要说的是,它虽然原始,却也是一个可以运行的IoC容器。
IoC容器
如果你使用过Spring或者了解Spring框架,肯定会对IoC容器有所耳闻。它的意思是使用Bean容器管理一个个的Bean,最简单的Bean就是一个Java的业务对象。在Java中,创建一个对象最简单的方法就是使用 new 关键字。IoC容器,也就是 BeanFactory,存在的意义就是将创建对象与使用对象的业务代码解耦,让业务开发人员无需关注底层对象(Bean)的构建和生命周期管理,专注于业务开发。 那我们可以先想一想,怎样实现Bean的管理呢?我建议你不要直接去参考Spring的实现,那是大树长成之后的模样,复杂而庞大,令人生畏。 作为一颗种子,它其实可以非常原始、非常简单。实际上我们只需要几个简单的部件:我们用一个部件来对应Bean内存的映像,一个定义在外面的Bean在内存中总是需要有一个映像的;一个XML reader 负责从外部XML文件获取Bean的配置,也就是说这些Bean是怎么声明的,我们可以写在一个外部文件里,然后我们用XML reader从外部文件中读取进来;我们还需要一个反射部件,负责加载Bean Class并且创建这个实例;创建实例之后,我们用一个Map来保存Bean的实例;最后我们提供一个getBean() 方法供外部使用。我们这个IoC容器就做好了。 注意:请根据上面内容,做到下面操作 1.根据内容重新编写内容,把新写的内容放到字段content,内容要有条理性,有结构性。内容使用markdown格式输出。content 中的 换成
实现一个原始版本的IoC容器
目标
实现一个简单的IoC容器,管理Bean的两个属性:id和class。id用于给Bean一个别名,class表示要注入的类。Bean通过XML配置文件注入到框架中。
Bean属性
-
id:Bean的别名,用于简化记忆成本。
-
class:要注入的类的名称。
XML配置示例
下面是一个XML配置文件的示例,展示了如何定义Bean。
|
|
步骤
-
解析XML配置文件:读取XML文件,解析出其中的bean标签,获取id和class属性。
-
创建Bean实例:根据解析出的class属性,创建对应的类实例。
-
注册Bean:将创建的Bean实例注册到一个Map中,以id为键,Bean实例为值。
-
使用Bean:通过id获取对应的Bean实例,进行后续操作。
注意事项
-
确保XML配置文件格式正确,id和class属性不为空。
-
类实例化时,需要处理可能的异常,例如类找不到或实例化失败。
-
考虑到后续可能的扩展,代码应保持一定的灵活性和可维护性。
|
|
在准备阶段,我们需要创建一个新的Java项目,并导入dom4j-1.6.1.jar包。dom4j是一个库,它封装了多种操作XML文件的方法,这将简化我们处理XML文件中的属性的工作,并且为我们后续处理基于XML注入的Bean提供了便利。 尽管我们的目标是学习Spring框架,我们会尽量减少对第三方库的依赖,并通过手动编写代码来实现功能,以更深入地理解底层原理。我们鼓励你亲自动手实践,因为编程是一项技能,提高它的唯一途径就是通过实践。通过不断学习、思考和实践,你的编程技能将得到显著提升。
构建BeanDefinition
创建Java项目后,我们将在项目中创建一个名为com.minis的包,所有相关的程序代码都将放在这个包下。在这个包中,我们将创建第一个类,用于定义Bean,命名为BeanDefinition。在这个类中,我们将定义两个基本的属性:id和className。以下是BeanDefinition类的代码示例。
|
|
在面向对象的编程中,为了更好地封装数据和行为,我们通常会创建包含属性、构造方法以及getter和setter方法的类。对于一个简单的Java Bean来说,这意味着提供一个全参数的构造方法来初始化对象状态,并且为每个属性提供访问器(getter)和修改器(setter)方法,以允许外部代码读取或更改这些属性的值。
实现ClassPathXmlApplicationContext类
当涉及到通过Spring框架进行依赖注入时,XML配置文件是一个常见的选择,它定义了应用程序上下文中Bean的配置信息。要解析这样的XML配置并根据其内容初始化Bean实例,我们需要实现ClassPathXmlApplicationContext
类。这个类的名字已经暗示了它的主要功能:从类路径下的指定XML文件加载配置信息,并据此构建应用上下文。
以下是实现ClassPathXmlApplicationContext
类的基本步骤:
-
定位资源:首先确定XML配置文件的位置。因为我们的类名中包含了
ClassPath
,所以我们将从类路径(如src/main/resources目录)查找XML文件。 -
加载文档:使用DOM或其他方式将XML文件的内容加载到内存中,以便后续处理。
-
解析XML:遍历XML文档结构,识别出代表不同Bean定义的元素及其属性。
-
注册Bean:基于解析出来的信息,在应用上下文中注册相应的Bean。这可能包括设置Bean的作用域、生命周期回调等。
-
实例化Bean:根据需要即时或者延迟地创建Bean实例,并处理它们之间的依赖关系。
-
管理Bean的生命周期:确保正确地调用初始化和销毁方法。 通过以上步骤,我们可以构建一个基础版本的
ClassPathXmlApplicationContext
,它能够支持从XML配置文件中读取Bean的信息,并按照Spring的方式管理和初始化这些Bean。
|
|
aaaaaa在解析和实例化Bean的过程中,ClassPathXmlApplicationContext
扮演了核心角色。它通过定义唯一的构造函数来实现这一过程,主要执行两件事:读取XML配置文件(readXml
)以及根据这些信息实例化Bean(instanceBeans
)。
readXml 方法详解
-
目标:将XML中的文本信息转换为内存中可以使用的数据结构,便于后续的Bean管理。
-
步骤:
- 使用dom4j包提供的
SAXReader
对象创建一个读取器。 - 根据给定的XML文件路径加载文档,并获取根元素。
- 遍历根元素下的所有子节点,从中提取出每个Bean的关键属性(如id、class等)。
- 利用上述属性信息构建
BeanDefinition
对象,代表单个Bean的定义,并将其添加到BeanDefinitions
列表中保存。
- 使用dom4j包提供的
instanceBeans 方法详解
-
目标:基于已有的Bean定义信息,使用反射技术创建具体的Bean实例。
-
步骤:
- 从
BeanDefinitions
中取出每一个BeanDefinition
对象。 - 利用
Class.forName()
方法依据类名字符串得到对应的Class
对象。 - 将生成的具体类实例存入名为
singletons
的Map中,形成ID与实际对象之间的映射关系。
- 从
实现概述
当前阶段,ClassPathXmlApplicationContext
不仅完成了对Bean信息的读取与实例化工作,还承担起了类似BeanFactory
的角色,负责管理容器内的Bean们。通过维护beanDefinitions
和singletons
这两个关键集合,它能够有效地支持基本的依赖注入等功能。
功能验证
为了确保以上设计确实达到了预期效果,在com.minis
目录下新增了一个test
包,用于存放相应的测试代码。通过编写并运行测试案例,我们可以检查ClassPathXmlApplicationContext
是否正确地实现了其功能。
|
|
这里,我们定义了一个sayHello接口,该接口的实现是在控制台打印出“a service 1 say hello”这句话。
|
|
我们将XML文件命名为beans.xml,注入AServiceImpl类,起个别名,为aservice。
|
|
除了测试代码,我们还需要启动类,定义main函数。
|
|
构建最原始的IoC容器
在本节中,我们通过构建ClassPathXmlApplicationContext
来实现一个基础的IoC容器。这个容器通过读取名为beans.xml
的XML文件,获取Bean的定义,并利用getBean
方法来获取并注入AService
接口的实现类AServiceImpl
。通过这种方式,我们可以在控制台输出a service 1 say hello
,展示了IoC容器对Bean的基本管理能力。
BeanDefinition的引入
在实现IoC容器的过程中,我们引入了BeanDefinition
的概念,它定义了Bean的属性和依赖关系。这使得容器能够理解如何创建和配置Bean。
ClassPathXmlApplicationContext的职责
尽管ClassPathXmlApplicationContext
实现了基本的IoC功能,但它承担了过多的职责,违反了单一职责原则。因此,我们需要对其进行优化和扩展。
优化扩展:解耦ClassPathXmlApplicationContext
为了使ClassPathXmlApplicationContext
更加模块化和易于扩展,我们计划将其分解为两个主要部分:
-
核心容器:负责Bean的创建和管理。
-
配置信息访问:负责从外部配置源(如XML文件)读取配置信息。 这种分解不仅有助于保持代码的清晰和模块化,而且也便于未来扩展到其他配置源,如Web或数据库。
项目代码结构的重构
为了使项目结构更加清晰,我们参考Spring的目录结构,对项目代码进行重构。这将有助于我们更好地组织代码,并为未来的扩展打下基础。
|
|
定义BeansException
在正式开始解耦工作之前,我们先定义属于我们自己的异常处理类:BeansException。我们来看看异常处理类该如何定义。
|
|
可以看到,现在的异常处理类比较简单,它是直接调用父类(Exception)处理并抛出异常。有了这个基础的BeansException之后,后续我们可以根据实际情况对这个类进行拓展。
定义 BeanFactory
首先要拆出一个基础的容器来,刚才我们反复提到了 BeanFactory 这个词,现在我们正式引入BeanFactory这个接口,先让这个接口拥有两个特性:一是获取一个Bean(getBean),二是注册一个BeanDefinition(registerBeanDefinition)。你可以看一下它们的定义。
|
|
定义Resource
刚刚我们将BeanFactory的概念进行了抽象定义。接下来我们要定义Resource这个概念,我们把外部的配置信息都当成Resource(资源)来进行抽象,你可以看下相关接口。
|
|
aaaaaaa在当前的项目中,我们主要依赖于XML文件作为配置数据的来源。为了提高系统的灵活性和可扩展性,我们引入了Resource
接口,这使得将来能够从多种来源获取配置信息,比如数据库或Web网络。
现在,我们的目标是进一步解耦代码,以便将XML文件的读取与解析逻辑从现有的ClassPathXmlApplicationContext
类中分离出来。为此,我们将定义一个新的类——ClassPathXmlResource
,它将负责处理所有与类路径下的XML资源相关的操作。
定义 ClassPathXmlResource
-
创建
ClassPathXmlResource
类:该类将实现Resource
接口,并专注于提供访问位于类路径下的XML文件的功能。 -
封装XML读取逻辑:在
ClassPathXmlResource
内部,我们需要封装用于打开并读取XML文件的方法。 -
支持解析:虽然
ClassPathXmlResource
本身不直接进行XML解析(这是BeanFactory
的责任),但它应该能以一种对BeanFactory
友好的方式提供原始XML内容。 -
测试覆盖:确保为
ClassPathXmlResource
编写足够的单元测试来验证其行为。 通过这样的设计,我们可以更容易地管理和维护与XML配置文件相关的功能,同时也为未来可能添加的新类型资源配置打下了基础。此外,这样做也符合面向对象的设计原则,即每个类都应该有单一职责。
|
|
在处理XML文件时,dom4j库提供了极大的便利。它能够帮助我们将XML文件中的标签和属性转换成Java对象,从而简化了代码的编写。尽管我们也可以手动编写代码来解析XML文件,但为了减少重复劳动,我们选择使用这个第三方库。
dom4j的作用
dom4j是一个外部jar包,它使得读取和解析XML文件变得简单。通过这个库,我们可以轻松地将XML中的标签和参数映射到Java对象中。
XmlBeanDefinitionReader的作用
解析XML文件后,我们需要将这些解析后的数据转换成BeanDefinition对象,以便后续使用。XmlBeanDefinitionReader正是完成这一任务的工具。它将解析好的XML数据转换成我们需要的BeanDefinition,从而使得XML文件中定义的配置能够被应用程序所理解和使用。
|
|
可以看到,在XmlBeanDefinitionReader中,有一个loadBeanDefinitions方法会把解析的XML内容转换成BeanDefinition,并加载到BeanFactory中。
BeanFactory功能扩展
首先,定义一个简单的BeanFactory实现类SimpleBeanFactory。
|
|
在Spring框架中,SimpleBeanFactory扮演着核心的角色,负责Bean的实例化和加载。通过将ClassPathXmlApplicationContext中与BeanDefinition相关的部分提取出来,ClassPathXmlApplicationContext变得更加简洁,其功能被重新分配给BeanFactory、Resource和Reader。以下是对这一变化的详细解析:
SimpleBeanFactory的作用
-
Bean实例化:SimpleBeanFactory负责将BeanDefinition转换为具体的Bean实例。
-
加载到内存:它还负责将这些BeanDefinition加载到内存中,以便后续使用。
ClassPathXmlApplicationContext的变化
-
功能简化:提取BeanDefinition相关功能后,ClassPathXmlApplicationContext成为一个“空壳子”,其核心功能被分解。
-
集成者角色:尽管功能被分解,ClassPathXmlApplicationContext仍然扮演着集成者的角色,负责将不同的组件和功能整合在一起。
功能分配
-
BeanFactory:负责Bean的创建和管理。
-
Resource:处理资源的加载和访问。
-
Reader:负责读取和解析配置文件。
总结
通过这种设计,Spring框架能够更加灵活地处理Bean的生命周期和配置,同时也提高了代码的可维护性和可扩展性。
|
|
在本次课程中,我们探讨了ClassPathXmlApplicationContext
在实例化过程中的三个主要步骤,这为我们理解如何将XML配置转换为可管理的Bean对象提供了基础。下面是对这些步骤的结构化总结:
ClassPathXmlApplicationContext
实例化过程
- 解析XML文件:
- 首先,
ClassPathXmlApplicationContext
会加载并解析指定路径下的XML配置文件。 - 解析过程中识别出所有的bean定义以及它们之间的关系。
- 构建BeanDefinition:
- 根据解析到的信息,创建每个bean对应的
BeanDefinition
对象。 - 这些
BeanDefinition
对象包含了关于如何创建特定bean的所有信息,比如类名、作用域等属性。
- 实例化与注入Bean:
- 接下来,使用之前构建好的
BeanDefinition
来创建实际的bean实例。 - 创建完成后,这些bean将被添加到
BeanFactory
容器之中,同时根据需要完成依赖注入。
小结通过上述几个关键步骤,我们可以实现从XML配置文件到具体Java对象(即beans)的转化,并最终将这些对象置于IoC容器内进行统一管理。尽管整个流程看似简单,但它体现了非常重要的编程原则之一:单一职责原则。这意味着每个类或组件都应该专注于执行单一功能。遵循这一原则有助于提高代码的可维护性和扩展性。
aaaaaaa以上就是本节课关于MiniSpring框架核心部分——Bean和IoC初步搭建的知识点介绍。
IoC控制反转概念解析
在本节课中,我们学习了如何通过框架容器自动管理业务类对象,而无需手动创建。这种管理方式体现了IoC(控制反转)的核心思想。IoC的核心在于将对象的创建和依赖关系的管理从业务代码中分离出来,交由框架容器负责。以下是IoC概念的详细解析和代码体现:
1. 框架容器管理对象
-
自动对象创建:框架容器自动创建业务类对象,无需手动
new
。 -
Resource和BeanFactory:
-
Resource:定义Bean的数据来源。
-
BeanFactory:负责Bean的容器化管理。
2. 功能解耦与容器结构
-
功能解耦:通过将对象创建和依赖管理分离,容器结构更清晰。
-
易于阅读和扩展:清晰的结构使得阅读和后续扩展更为方便。
3. IoC的“反转”含义
-
控制权转移:IoC“反转”了对象创建和依赖管理的控制权,从业务代码转移到框架容器。
-
代码体现:在代码中,这种“反转”体现在不再需要在业务逻辑中显式创建对象,而是通过框架容器来获取所需的对象。
4. IoC的扩展性和适用性
-
基本功能的实现:虽然是一个简化的模型,但已经具备了IoC的基本功能。
-
持续扩展:随着更多功能的添加,这个模型可以发展成为一个完整的框架。
5. 源代码参考
完整源代码可以在以下GitHub链接查看:YaleGuo/minis。
课后思考题
- IoC的“反转”:思考IoC的字面含义“控制反转”,它究竟反转了什么?又是如何在代码中体现的?欢迎在留言区讨论并分享这节课。 我们下节课再见!
- 原文作者:知识铺
- 原文链接:https://index.zshipu.com/geek002/post/202410/01%E5%8E%9F%E5%A7%8BIoC%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87BeanFactory%E5%AE%9E%E7%8E%B0%E5%8E%9F%E5%A7%8B%E7%89%88%E6%9C%AC%E7%9A%84IoC%E5%AE%B9%E5%99%A8--%E7%9F%A5%E8%AF%86%E9%93%BA/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com