深入了解Spring之IoC
GitHub:https://github.com/JDawnF/learning_note
目录
4、Spring中的IoC 容器(ApplicationContext 面向开发应用)
4.HierarchicalBeanFactory 父子级联
6.AutowireCapableBeanFactory 自动装配
7.SingletonBeanRegistry 运行期间注册单例 Bean
1、IoC 容器
Spring 框架的核心是 Spring IoC 容器。容器创建 Bean 对象,将它们装配在一起,配置它们并管理它们的完整生命周期。
-
Spring 容器使用依赖注入来管理组成应用程序的 Bean 对象。
-
容器通过读取提供的配置元数据Bean Definition, 来接收对象进行实例化,配置和组装的指令。
-
该配置元数据 Bean Definition 可以通过 XML,Java 注解或 Java Config 代码提供。
2、依赖注入
在依赖注入中,不必主动、手动创建对象,但必须描述如何创建它们。
-
不是直接在代码中将组件和服务连接在一起,而是描述配置文件中哪些组件需要哪些服务。
-
然后,再由 IoC 容器将它们装配在一起。
另外,依赖注入的英文缩写是 Dependency Injection ,简称 DI 。
3、实现依赖注入的方式
通常,依赖注入可以通过三种方式完成,即:
-
接口注入
-
构造函数(即构造器)注入
/*带参数,方便利用构造器进行注入*/ public CatDaoImpl(String message){ this. message = message; } //XML <bean id="CatDaoImpl" class="com.CatDaoImpl"> <constructor-arg value=" message "></constructor-arg> </bean>
-
setter 注入
public class Id { private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } } <bean id="id" class="com.id "> <property name="id" value="123"></property> </bean>
目前,在 Spring Framework 中,仅使用构造函数和 setter 注入这两种方式。
参照: 《Spring两种依赖注入方式的比较》:
构造函数注入 | setter 注入 |
---|---|
没有部分注入 | 有部分注入 |
不会覆盖 setter 属性 | 会覆盖 setter 属性 |
任意修改都会创建一个新实例 | 任意修改不会创建一个新实例 |
适用于设置很多属性 | 适用于设置少量属性 |
-
实际场景下,setter 注入使用的更多。
4、Spring中的IoC 容器(ApplicationContext 面向开发应用)
Spring 提供了两种IoC 容器,分别是 BeanFactory、ApplicationContext 。
BeanFactory
BeanFactory 在
spring-beans
项目提供。
BeanFactory ,就像一个包含 Bean 集合的工厂类。它会在客户端要求时实例化 Bean 对象。
可以理解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个功能。我们可以称之为 “低级容器”。
ApplicationContext
ApplicationContext 在
spring-context
项目提供。
ApplicationContext可以称之为 “高级容器”,因为他 接口扩展了 BeanFactory 接口,它在 BeanFactory 基础上提供了一些额外的功能。内置如下功能:
-
MessageSource :管理 message ,实现国际化等功能,为应用提供 i18n 国际化消息访问的功能;
-
ApplicationEventPublisher :事件发布,让容器拥有发布应用上下文事件的功能,包括容器启动事件、关闭事件等。
-
ResourcePatternResolver :多资源加载,所有ApplicationContext实现类都实现了类似于 PathMatchingResourcePatternResolver 的功能,可以通过带前缀的 Ant 风格的资源文件路径装载 Spring 的配置文件。
-
EnvironmentCapable :系统 Environment(profile + Properties)相关。
-
Lifecycle :管理生命周期。该接口提供了 start()和 stop()两个方法,主要用于控制异步处理过程。在具体使用时,该接口同时被 ApplicationContext 实现及具体 Bean 实现, ApplicationContext 会将 start/stop 的信息传递给容器中所有实现了该接 口的 Bean,以达到管理和控制 JMX、任务调度等目的。
-
Closable :关闭,释放资源
-
InitializingBean:自定义初始化。
-
BeanNameAware:设置 beanName 的 Aware 接口。
-
ConfigurableApplicationContext 扩展于 ApplicationContext,它新增加了两个主要 的方法: refresh()和 close(),让 ApplicationContext 具有启动、刷新和关闭应用上下 文的能力。在应用上下文关闭的情况下调用 refresh()即可启动应用上下文,在已经启动 的状态下,调用 refresh()则清除缓存并重新装载配置信息,而调用 close()则可关闭应用 上下文。
另外,ApplicationContext 会自动初始化非懒加载的 Bean 对象们。
可以细看 《【死磕 Spring】—— ApplicationContext 相关接口架构分析》
总结下 BeanFactory 与 ApplicationContext 两者的差异:
BeanFactory | ApplicationContext |
---|---|
它使用懒加载 | 它使用即时加载 |
它使用语法显式提供资源对象 | 它自己创建和管理资源对象 |
不支持国际化 | 支持国际化 |
不支持基于依赖的注解 | 支持基于依赖的注解 |
另外,BeanFactory 也被称为低级容器,而 ApplicationContext 被称为高级容器。
为了更直观的展示 “低级容器” 和 “高级容器” 的关系,下面通过常用的 ClassPathXmlApplicationContext 类,来展示整个容器的层级 UML 关系。
ListableBeanFactory
该接口定义了访问容器中 Bean 基本信息的若干方法,如查看 Bean 的个数、获取某一类型 Bean 的配置名、查看容器中是否包括某一 Bean 等方法;
HierarchicalBeanFactory 父子级联
父子级联 IoC 容器的接口,子容器可以通过接口方法访问父容器; 通过 HierarchicalBeanFactory 接口, Spring 的 IoC 容器可以建立父子层级关联的容器体系,子容器可以访问父容器中的 Bean,但父容器不能访问子容器的 Bean。Spring 使用父子容器实 现了很多功能,比如在 Spring MVC 中,展现层 Bean 位于一个子容器中,而业务层和持久 层的 Bean 位于父容器中。这样,展现层 Bean 就可以引用业务层和持久层的 Bean,而业务 层和持久层的 Bean 则看不到展现层的 Bean。
看下面的隶属 ApplicationContext 粉红色的 “高级容器”,依赖着 “低级容器”,这里说的是依赖,不是继承,因为ApplicationContext继承了ListableBeanFactory,而ListableBeanFactory又继承了BeanFactory。他依赖着 “低级容器” 的 getBean 功能。而高级容器有更多的功能:支持不同的信息源头,可以访问文件资源,支持应用事件(Observer 模式)。
通常用户看到的就是 “高级容器”。 但 BeanFactory 也非常够用啦!
左边灰色区域的是 “低级容器”, 只负载加载 Bean,获取 Bean。容器其他的高级功能是没有的。例如上图画的 refresh 刷新 Bean 工厂所有配置、生命周期事件回调等。
WebApplicationContext 是专门为 Web 应用准备的,它允许从相对于 Web 根目录的路径中装载配置文件完成初始化工作。从 WebApplicationContext 中可以获得ServletContext 的引用,整个 Web 应用上下文对象将作为属性放置到 ServletContext中,以便 Web 应用环境可以访问 Spring 应用上下文。
5、常用的 ApplicationContext 容器
以下是三种较常见的 ApplicationContext 实现方式:
-
1、ClassPathXmlApplicationContext :从 ClassPath(类路径) 的 XML 配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得。示例代码如下:
ApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);
-
2、FileSystemXmlApplicationContext :由文件系统中的XML配置文件读取上下文。示例代码如下:
ApplicationContext context = new FileSystemXmlApplicationContext(“bean.xml”);
-
3、XmlWebApplicationContext :由 Web 应用的XML文件读取上下文。例如我们在 Spring MVC 使用的情况。
当然,目前我们更多的是使用 Spring Boot 为主,所以使用的是第四种 ApplicationContext 容器,ConfigServletWebServerApplicationContext 。
6、Spring IoC 的实现机制
1.BeanDefinitionRegistry 注册表
Spring 配置文件中每一个节点元素在 Spring 容器里都通过一个 BeanDefinition 对象表示, 它描述了 Bean 的配置信息。而 BeanDefinitionRegistry 接口提供了向容器手工注册 BeanDefinition 对象的方法。
2.BeanFactory 顶层接口
位于类结构树的顶端 ,它最主要的方法就是 getBean(String beanName),该方法从容器中 返回特定名称的 Bean,BeanFactory 的功能通过其他的接口得到不断扩展;
3.ListableBeanFactory
该接口定义了访问容器中 Bean 基本信息的若干方法,如查看 Bean 的个数、获取某一类型 Bean 的配置名、查看容器中是否包括某一 Bean 等方法;
4.HierarchicalBeanFactory 父子级联
父子级联 IoC 容器的接口,子容器可以通过接口方法访问父容器; 通过 HierarchicalBeanFactory 接口, Spring 的 IoC 容器可以建立父子层级关联的容器体系,子 容器可以访问父容器中的 Bean,但父容器不能访问子容器的 Bean。Spring 使用父子容器实 现了很多功能,比如在 Spring MVC 中,展现层 Bean 位于一个子容器中,而业务层和持久层的 Bean 位于父容器中。这样,展现层 Bean 就可以引用业务层和持久层的 Bean,而业务 层和持久层的 Bean 则看不到展现层的 Bean。
5.ConfigurableBeanFactory
是一个重要的接口,增强了 IoC 容器的可定制性,它定义了设置类装载器、属性编辑器、容 器初始化后置处理器等方法;
6.AutowireCapableBeanFactory 自动装配
定义了将容器中的 Bean 按某种规则(如按名字匹配、按类型匹配等)进行自动装配的方法;
7.SingletonBeanRegistry 运行期间注册单例 Bean
定义了允许在运行期间向容器注册单实例 Bean 的方法;对于单实例( singleton)的 Bean 来说,BeanFactory 会缓存 Bean 实例,所以第二次使用 getBean() 获取 Bean 时将直接从 IoC 容器的缓存中获取 Bean 实例。Spring 在 DefaultSingletonBeanRegistry 类中提供了一 个用于缓存单实例 Bean 的缓存器,它是一个用 HashMap 实现的缓存器,单实例的 Bean 以 beanName 为键保存在这个 HashMap 中。
8.依赖日志框框
在初始化 BeanFactory 时,必须为其提供一种日志框架,比如使用 Log4J, 即在类路径下提供 Log4J 配置文件,这样启动 Spring 容器才不会报错。
简单来说,Spring 中的 IoC 的实现原理,就是工厂模式加反射机制。代码如下:
interface Fruit {
public abstract void eat();
}
class Apple implements Fruit {
public void eat(){
System.out.println("Apple");
}
}
class Orange implements Fruit {
public void eat(){
System.out.println("Orange");
}
}
class Factory {
public static Fruit getInstance(String className) {
Fruit f = null;
try {
f = (Fruit) Class.forName(className).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return f;
}
}
class Client {
public static void main(String[] args) {
Fruit f = Factory.getInstance("io.github.dunwu.spring.Apple");
if(f != null){
f.eat();
}
}
}
-
Fruit 接口,有 Apple 和 Orange 两个实现类。
-
Factory 工厂,通过反射机制,创建
className
对应的 Fruit 对象。 -
Client 通过 Factory 工厂,获得对应的 Fruit 对象。
-
实际情况下,Spring IoC 比这个复杂很多很多,例如单例 Bean 对象,Bean 的属性注入,相互依赖的 Bean 的处理,以及等等。
IoC 启动过程其实就是ClassPathXmlApplicationContext 这个类,在启动时,都做了啥。
下图是 ClassPathXmlApplicationContext 的构造过程,实际就是 Spring IoC 的初始化过程。
-
用户构造 ClassPathXmlApplicationContext(简称 CPAC)
-
CPAC 首先访问了 “抽象高级容器” 中由final修饰的refresh 方法,这个方法是模板方法。所以要回调子类(低级容器)的 refreshBeanFactory 方法,这个方法的作用是使用低级容器加载所有 BeanDefinition 和 Properties 到容器中。
-
低级容器加载成功后,高级容器开始处理一些回调,例如 Bean 后置处理器。回调 setBeanFactory 方法。或者注册监听器等,发布事件,实例化单例 Bean 等等功能,这些功能,随着 Spring 的不断升级,功能越来越多。
简单说就是:
-
低级容器 加载配置文件(从 XML,数据库,Applet),并解析成 BeanDefinition 到低级容器中。
-
加载成功后,高级容器启动高级功能,例如接口回调,监听器,自动实例化单例,发布事件等等功能。
当我们创建好容器,就会使用 getBean 方法,获取 Bean,而 getBean 的流程如下:
getBean 的操作都是在低级容器里操作的。这里的递归是因为:
假设 : 当 Bean_A 依赖着 Bean_B,而这个 Bean_A 在加载的时候,其配置的 ref = “Bean_B” 在解析的时候只是一个占位符,被放入了 Bean_A 的属性集合中,当调用 getBean 时,需要真正 Bean_B 注入到 Bean_A 内部时,就需要从容器中获取这个 Bean_B,因此产生了递归。
为什么不是在加载的时候,就直接注入呢?
因为加载的顺序不同,很可能 Bean_A 依赖的 Bean_B 还没有加载好,也就无法从容器中获取,你不能要求用户把 Bean 的加载顺序排列好,这是不人道的。
所以,Spring 将其分为了 2 个步骤:
-
加载所有的 Bean 配置成 BeanDefinition 到容器中,如果 Bean 有依赖关系,则使用占位符暂时代替。
-
然后,在调用 getBean 的时候,进行真正的依赖注入,即如果碰到了属性是 ref 的(占位符),那么就从容器里获取这个 Bean,然后注入到实例中 —— 称之为依赖注入。
可以看到,依赖注入实际上,只需要 “低级容器” 就可以实现。这就是 IoC。
所以 ApplicationContext refresh 方法里面的操作不只是 IoC,是高级容器的所有功能(包括 IoC),IoC 的功能在低级容器里就可以实现。
IoC 在 Spring 里,只需要低级容器就可以实现,2 个步骤:
a. 加载配置文件,解析成 BeanDefinition 放在 Map 里。
b. 调用 getBean的时候,从BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法 —— 完成依赖注入。
上面就是 Spring 低级容器(BeanFactory)的 IoC。
至于高级容器 ApplicationContext,他包含了低级容器的功能,当他执行 refresh 模板方法的时候,将刷新整个容器的 Bean。同时其作为高级容器,包含了太多的功能。一句话,他不仅仅是 IoC。他支持不同信息源头,支持 BeanFactory 工具类,支持层级容器,支持访问文件资源,支持事件发布通知,支持接口回调等等。
可以预见,随着 Spring 的不断发展,高级容器的功能会越来越多。
参照查看 《面试问烂的 Spring IoC 过程》 这篇文章。
7、Spring 框架中的事件
Spring 事件为bean 与 bean之间传递消息,一个bean处理完了希望其余一个接着处理,这时我们就需要其余的一个bean监听当前bean所发送的事件。
Spring 的 ApplicationContext 提供了支持事件和代码中监听器的功能。
我们可以创建 Bean 用来监听在 ApplicationContext 中发布的事件。如果一个 Bean 实现了 ApplicationListener 接口,当一个ApplicationEvent 被发布以后,Bean 会自动被通知。示例代码如下:
public class AllApplicationEventListener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
// process event
}
}
Spring 提供了以下五种标准的事件:
-
上下文更新事件(ContextRefreshedEvent):该事件会在ApplicationContext 被初始化或者更新时发布。也可以在调用ConfigurableApplicationContext 接口中的
refresh()
方法时被触发。 -
上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext 的
start()
方法开始/重新开始容器时触发该事件。 -
上下文停止事件(ContextStoppedEvent):当容器调用 ConfigurableApplicationContext 的
stop()
方法停止容器时触发该事件。 -
上下文关闭事件(ContextClosedEvent):当ApplicationContext 被关闭时触发该事件。容器被关闭时,其管理的所有单例 Bean 都被销毁。
-
请求处理事件(RequestHandledEvent):在 We b应用中,当一个HTTP 请求(request)结束触发该事件。
除了上面介绍的事件以外,还可以通过扩展 ApplicationEvent 类来开发自定义的事件。
① 示例自定义的事件的类,代码如下:
public class CustomApplicationEvent extends ApplicationEvent{
public CustomApplicationEvent(Object source, final String msg) {
super(source);
}
}
② 为了监听这个事件,还需要创建一个监听器。示例代码如下:
public class CustomEventListener implements ApplicationListener<CustomApplicationEvent> {
@Override
public void onApplicationEvent(CustomApplicationEvent applicationEvent) {
// handle event
}
}
③ 之后通过 ApplicationContext 接口的 publishEvent(Object event)
方法,来发布自定义事件。示例代码如下:
// 创建 CustomApplicationEvent 事件
CustomApplicationEvent customEvent = new CustomApplicationEvent(applicationContext, "Test message");
// 发布事件
applicationContext.publishEvent(customEvent);
Spring事件使用步骤如下:
1.先自定义事件:你的事件需要继承 ApplicationEvent
2.定义事件监听器: 需要实现 ApplicationListener
3.使用容器对事件进行发布
还有个例子可以参照:https://www.cnblogs.com/huzi007/p/6215024.html
参照“芋道源码”