如今已是2024年,掌握 Spring 早已是 Java 从业人员的基本要求。
Spring 帮我们屏蔽掉了许多繁琐的工作,使我们可以把重心放在业务逻辑上。但也因此,要深刻体会到 Spring 带来的便捷性,反倒需要与没有使用 Spring 时作对比。为此,我特地翻了下上大学时用 JaveWeb 开发的项目(无框架),希望能让大家更好理解。
01
Spring IoC 容器概述
Spring 的架构图如上所示,IoC 与 AOP 都是 Spring 的核心模块,其中 IoC 更是 Spring 的基石。
IoC:Inverse of Control,控制反转。从(对象 -> 对象)变成了(对象 -> 容器 -> 对象),即控制对象的方式被反转了。
DI:Dependency Injection,依赖注入。即由容器注入这个依赖关系。
IoC 是思想,而 DI 是具体实现手段。这里放上代码更加直观。
上面的代码中,LoginServlet 直接去 new UserDao,与 UserDao 强耦合,如果 UserDao 的构造函数发生变化,此处的代码也需要调整;而下面的代码,UserServiceImpl 通过注解获取 UserDao,UserDao 的管理交给了容器,从而实现了解耦。
当然,这只是个简单的例子,我们可以通过工厂模式实现类似的功能。换句话说,Spring IoC 可以理解为一个非常强大的工厂。
02
BeanFactory 与 ApplicationContext
1.整体设计
BeanFactory:定义了 IoC 容器最基本的形态,提供了 IoC 容器应该遵守的服务契约。
ApplicationContext:IoC 容器的高级形态,除了应该有的基本 IoC 功能外还支持应用事件,不同信息源等高级特性。
BeanDefinition:就是 POJO 类在 IoC 容器的抽象,这些 Java 类成为了容器内部的数据结构,让容器可以轻松的使用和管理它们。
上图为 IoC 容器的核心接口设计图。
以 BeanFactory 作为核心,第一条设计路径为左侧绿色的 HierarchicalBeanFactory 到 ConfigurableBeanFactory。在这条路径中,定义了基本方法如 getBean(),双亲 IoC 容器的管理功能 get/setParentBeanFactory(),以及配置 Bean 后置处理器的 addBeanPostProcessor() 等。
以 ApplicationContext 作为核心,第二条设计路径为右侧橙色的从 BeanFactory 最终落到 WebApplicationContext、ConfigurableApplicationContext 的链路。ApplicationContext 继承了 MessageSource、ResourceLoader、ApplicationEventPublisher 接口,它不但有 BeanFactory IoC 容器的基本功能,还拥有了支持不同信息源、访问资源、支持应用事件等高级特性。
通过 BeanFactory 接口的 getBean 方法,可以通过名字来取得 IoC 容器中的 Bean。基于此用户可以执行以下操作:
-
containsBean() 判断是否有存在指定名字的 Bean
-
isSingleton() 判断是否单例
-
isPrototype() 判断是否原型
-
isTypeMatch() 判断类型是否匹配
-
getType() 获取类型
-
getAliases() 获取别名
2.以 XmlBeanFactory 为例
XmlBeanFactory 顾名思义是以读取 XML 文件方式定义的 BeanDefinition 的 IoC 容器。
public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException{
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
}
查看源码,我们可以清楚地看到,在 XmlBeanFactory 中初始化了一个 XmlBeanDefinitionReader 对象,在 XmlBeanFactory 的构造函数中,那些 XML 形式的资源是由 Reader 来加载的。
如果以编程式使用,那么就是这样:
ClassPathResource res = new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);
我们可以更为直观地描述步骤:
-
创建 IoC 配置文件的抽象资源。
-
创建一个 BeanFactory。
-
创建一个 BeanDefinition 的读取器。
-
让读取器去读取资源,并完成 Bean 的载入和注册。
至此就可以使用 Bean 了。
03
Spring IoC 容器的初始化
在前面编程式使用的例子中我们可以看到,初始化包括了 BeanDefinition 的 Resource 定位、载入和注册三个过程。
由于类名长,抽象接口多,通过文字或图片学习还是比较晦涩,建议在IDEA里跟着方法过一遍。
1.Resource 定位过程
下面以 FileSystemXmlApplicationContext 为例,流程大致如下:
-
FileSystemXmlApplicationContext 构造函数调用 this.refresh()(注意这个 refresh 可以视作整个容器的初始化方法)
-
AbstractApplicationContext.refresh() 调用 this.obtainFreshBeanFactory()
-
AbstractApplicationContext.obtainFreshBeanFactory() 调用 this.refreshBeanFactory(),也就是 AbstractRefreshableApplicationContext.freshBeanFactory()
-
AbstractXmlApplicationContext.loadBeanDefinition()
-
AbstractBeanDefinitionReader.loadBeanDefinition() 注意这里有多个重载方法
-
GenericApplicationContext.getResource()
-
DefaultResourceLoader.getResource()
-
FileSystemXmlApplicationContext.getResourceByPath() 根据路径生成一个 FileSystemResource 并返回
如果是其他的 ApplicationContext,会对应生成其他种类的 Resource,例如 ClassPathResource、ServletContextResource。
2.BeanDefinition 载入
把用户定义好的 Bean 表示成 IoC 容器内部的数据结构,即 BeanDefinition。
下面以 DefaultListableBeanFactory 为例,流程大致如下:
-
AbstractApplicationContext.refresh()
-
AbstractRefreshableApplicationContext.createBeanFactory()
-
AbstractRefreshableApplicationContext.loanBeanDefinition()
-
BeanDefinitionParserDelegate.parseBeanDefinitionElement()
3.IoC 容器注册 BeanDefinition
将 BeanDefinition 注入到一个 HashMap 中去。
在 DefaultListableBeanFactory 中我们可以看到一个叫 beanDefinitionMap 的成员变量,类型为 ConcurrentHashMap 。流程大致如下:
-
XmlBeanDefinitionReader.loanBeanDefinition()
-
DefaultBeanDefinitionDocumentReader.processBeanDefinition()
-
DefaultListableBeanFactory.registerBeanDefinition() 更新 beanDefinitionMap
04
Spring IoC 容器的依赖注入
依赖注入在用户第一次向 IoC 容器索要 Bean 的时候触发,当然,也可以在 BeanDefinition 信息中通过 lazy-init 属性来让容器完成对 Bean 的预实例化。
下面以 DefaultListableBeanFactory 为例,流程大致如下:
-
DefaultListableBeanFactory 调用AbstractBeanFactory.doGetBean()
-
AbstractAutowireCapableBeanFactory.createBean()
-
SimpleInstantiationStrategy.instantiate()
-
AbstractAutowireCapableBeanFactory.populateBean()
-
AbstractAutowireCapableBeanFactory.applyPropertyValues()
-
BeanDefinitionResolver.resolveReference()
05
其他特性
1.容器初始化和关闭过程
下面以 FileSystemXmlBeanFactory 为例,流程大致如下:
-
FileSystemXmlBeanFactory 调用 AbstractApplicationContext.prepareBeanFactory()
-
AbstractApplicationContext.addPropertyEditorRegistrar()
-
AbstractApplicationContext.addBeanPostProcessor()
-
AbstractApplicationContext.doClose()
-
FileSystemXmlBeanFactory.closeBeanFactory()
关于 IoC 容器中 Bean 的生命周期:
-
Bean 实例的创建
-
为 Bean 实例设置属性
-
调用 Bean 的初始化方法
-
应用可以通过 IoC 容器使用 Bean
-
当容器关闭时,调用 Bean 的销毁方法
2.自动装配的实现
下前面提到的例子多是以 XML 配置 Bean 的形式,实际工作中使用注解的占多数。配置好 autowiring 属性,IoC 容器会使用反射自动查找属性的类型或者名字。
关于这块的具体实现,可参考 AbstractAutowireCapableBeanFactory.populateBean()。
原文链接:读书笔记-《Spring技术内幕》(一)IoC容器的实现
原创不易,点个关注不迷路哟,谢谢!