前言
欢迎来到本篇文章!通过上一篇什么是 Spring?为什么学它?的学习,我们知道了 Spring 的基本概念,知道什么是 Spring,以及为什么学习 Spring。今天,这篇就来说说 Spring 中的核心概念之一 IoC。
IoC 这个概念对于初学者来说还真不是很好理解,我就是那个理解不了的初学者。那时候,学起来很费解,只是迷迷糊糊知道了一些概念名词,控制反转,依赖注入。
现在,我重新梳理这些知识,尽量写清楚什么是 IoC 以及相关的知识,如有错误,敬请指正!好了废话不多说,进入正题!
什么是 IoC?什么是 Spring IoC 容器?
IoC(Inversion of Control),即控制反转,也被称为依赖注入(Dependency Injection,DI)。
如果有对「依赖」不太明白的朋友,那么可以去看上一篇,在上一篇中,我们通过 Employee 和 Department 的例子解释了何为「依赖」
IoC 是一种定义对象之间依赖关系的过程。
在 Spring 没出现之前,当一个对象需要使用其他对象来完成某些操作,就需要我们自己去创建或查找这些依赖的对象。
现在,有了 Spring,我们的对象交给 Spring 管理,这些对象可以理解为存放在一个容器中的,这个容器就称为 Spring IoC容器。在 IoC 容器中,对象不再自己管理它们的依赖,而是通过构造方法参数、工厂方法的参数或者在对象创建后通过属性设置来定义它们的依赖关系。
Spring 的 IoC 容器负责在创建对象时注入它们依赖的其他对象,也就是自动地把依赖的对象提供给需要它们的对象。这样一来,对象不再需要主动去查找或创建它们的依赖,而是由容器在创建对象时帮助它们完成依赖注入的过程。
控制反转的概念主要是与传统的直接构造(即 new
操作)来控制对象依赖的方式相反。传统方式中,一个对象通常会直接创建或查找它所依赖的其他对象,而在 IoC 中,对象将自身的控制权交给了容器,容器负责管理对象的创建和依赖注入,因此被称为「控制反转」。
初次见面 BeanFactory 和 ApplicationContext
在 Spring Framework 中,org.springframework.beans
和 org.springframework.context
包是 Spring IoC 容器的基础。
下面介绍两个新手村的伙伴给大家认识,BeanFactory
接口和它的子接口 ApplicationContext
接口。
BeanFactory
接口提供了一个高级配置机制,能够管理任何类型的对象。
对于它的子接口 ApplicationContext
来说,它的子接口增加了以下功能:
-
更容易与 Spring AOP 特性集成
-
消息资源处理(用于国际化)
-
事件发布
-
应用程序层特定上下文,例如 WebApplicationContext,用于 Web 应用程序。
简而言之,BeanFactory
提供配置框架和基本功能,而 ApplicationContext
添加了更多企业特定功能。
什么是 Bean?
在 Spring 中,构成应用程序骨干并由 Spring IoC 容器管理的对象称为 Bean。 Bean 是由 Spring IoC 容器实例化、组装和管理的对象。否则,Bean 只是我们应用程序中众多对象中的一个普通的对象而已。
Bean 及其相互依赖关系是反映在容器使用的配置元数据(Configuration Metadata)中的,这个配置元数据可以用 XML、Java 注解或 Java 代码表示。
提示:如果你和我一样比较喜欢深究这些英文单词的中文意思,现在的我给你个建议:
就是觉得没必要深究!
比如说 Bean,中文是什么意思,你去找翻译,发现翻译是「豆」,还有类似 JavaBean,翻译是「Java 豆」,这些都是毫无意义的翻译,所以没必要知道它的中文意思是什么,Bean 就是 Bean,Bean 就是被 Spring IoC 容器管理的对象,这就是 Bean;JavaBean 则是只提供 setter 和 getter 的纯对象,本身没有任何业务逻辑,这就是 JavaBean。
容器是谁?
我们一直谈到「容器」,那么容器到底是什么呢?嘿嘿,容器马上就揭晓了!
实际上,Spring 的 IoC 容器就是由 org.springframework.context.ApplicationContext
接口来代表的。这个容器承担着实例化、配置和组装Bean的责任。
容器通过读取配置元数据来了解如何创建、配置和组装对象,同时也允许我们描述应用程序中各个对象之间的复杂依赖关系。目前从本系列的角度来看,我们会使用传统的 XML 方式来定义配置元数据,这是我们需要学习和了解的,后续才能更好地理解使用 Java 注解或代码作为配置元数据的方式。
Spring 为我们提供了多个 ApplicationContext
接口的实现。在独立的应用程序中,常见的实例化方式是创建 ClassPathXmlApplicationContext
或 FileSystemXmlApplicationContext
的一个实例。
**不过,在我们日常的开发和工作中,我们基本上不需要显式地去实例化一个或多个 Spring 容器。**特别是在Web应用程序的场景下,通常只需在web.xml
文件中简单地编写约8行标准的XML配置即可完成(你可以参考一些方便的方式来初始化Web应用程序的ApplicationContext)。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
所以,容器终于来啦!它就是 Spring 的 IoC 容器,负责管理 Bean 的实例化、配置和组装,而我们可以通过配置元数据来描述应用程序中的对象和它们之间的依赖关系。记住,我们一般不需要手动实例化 Spring 容器,特别是在 Web 应用程序中。
Spring IoC 容器怎样运行的?
从顶层上来看,Spring IoC 是这样运行的,就是将我们应用程序中的各种业务对象与配置元数据结合起来,使得我们在初始化 ApplicationContext 之后,有一个完整配置的、可用的应用程序。
什么是配置元数据?
先说个结论,目前日常工作中,配置元数据基本都是以 Java 注解或者 Java 代码的方式来提供给 Spring 容器的,不过 XML 的方式我们也要学习,对于后续学习是有帮助的。
「配置元数据」是 Configuration Metadata,不是 Configure Metadata,这里的「配置」二字是名词,不是动词,千万不要理解成去配置元数据。
这个配置元数据,实际就是用来描述配置的数据,上面我也说了,我们可以用 Java 注解或者 Java 代码的方式来描述配置,也可以用 XML 的方式来描述数据。
所以现在,相信你已经明白何为配置元数据了,所以学习下以 XML 格式的文件作为配置元数据。
XML 格式的配置元数据
配置元数据以简单直观的 XML 格式提供,这也是本系列前大半部分内容用来传达 Spring IoC 容器关键概念和特性的方式,也正如前面说的,学习 XML 的配置方式,便于我们后续学习 Java 注解或者 Java 代码的配置方式。
下面的示例展示了基于XML的配置元数据的基本结构:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
<!-- 该 bean 的协作者和配置写在此处 -->
</bean>
<bean id="..." class="...">
<!-- 该 bean 的协作者和配置写在此处 -->
</bean>
<!-- 更多的 bean 定义写在此处 -->
</beans>
上面的示例中,主要使用了 <bean>
元素(或者说标签),该元素有两个属性,id
和 class
:
-
id
属性:它是一个字符串,用于标识单个 bean 定义。 -
class
属性:它定义了 bean 的类型,并使用完全限定类名(又称全限定名、全类名。多种叫法,都是同一个东西)。
我们习惯说把对象交给 Spring IoC 容器管理,那你如何个交法呢?
上面的 XML 已经给出了答案,就是定义 Bean,我们每定义一个 Bean,就是将对应的类的对象交给了 Spring IoC 容器了。
这些 Bean 的定义就是构成我们应用程序中的各种实际对象。一般我们在开发的时候,都会分层次的,控制层、业务层、持久层、表现层(视图层)或者其他层次,然后我们就会定义业务层对象、持久层对象、表现层对象等等。
在上一篇中,我举了个例子,员工和部门的,让这两个东西交给了 Spring IoC 管理了,实际上,在日常开发中,是不会这样做的,不会配置细粒度的领域对象(Domain Object)。因为一般这些领域对象都是在业务层和持久层中创建或者加载的。
如何实例化一个 Spring IoC 容器?
上面我也说过,在我们日常的开发和工作中,我们基本上不需要显式地去实例化一个或多个 Spring 容器的。
但是我们现在在学习,就有必要了解如何手动去实例化一个 Spring IoC 容器。
一行代码就能够实例化一个 Spring IoC 容器:
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
在 XML 为配置元数据的情况下,我们可以创建一个 ClassPathXmlApplicationContext
对象,它是 ApplicationContext
的一个实现类,提供给我们的构造函数的参数是一条或多条路径是资源字符串,它让容器从各种外部资源(如本地文件系统、Java CLASSPATH
等)加载配置元数据,这样我们就实例化一个 Spring IoC 容器,context
对象就是这个容器了。
现在我们将持久层的对象和业务层的对象定义到 XML 中,先创建好需要的类和接口:
接着,在 daos.xml
中定义如下两个 bean,交给 Spring IoC 管理:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="roleDao" class="cn.god23bin.demo.dao.impl.RoleDaoImpl">
<!-- 该 bean 的协作者和其他配置 -->
</bean>
<bean id="userDao" class="cn.god23bin.demo.dao.impl.UserDaoImpl">
<!-- 该 bean 的协作者和其他配置 -->
</bean>
<!-- 其他的持久层的 bean 定义在这里 -->
</beans>
services.xml
同理:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 引入另一个 XML 定义的 bean -->
<import resource="daos.xml"/>
<bean id="userService" class="cn.god23bin.demo.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao" />
<property name="roleDao" ref="roleDao" />
</bean>
<!-- 其他的业务层的 bean 定义在这里 -->
</beans>
在 services.xml
中,使用 <import />
可以让 Bean 的定义跨越 XML 文件。一般每个单独的 XML 配置文件代表了我们应用中的一个逻辑层或者模块,就如同这里的 daos.xml
和 services.xml
。
使用 Spring IoC 容器
我们把对象交给了 Spring IoC 容器管理,让它帮我们创建对象以及处理对象之间的依赖关系。
在上面的 XML 中,我们定义了 UserServcie 对象,即把 UserService 这个对象交给了 Spring IoC,那么如何从容器获取它呢?
ApplicationContext
是一个高级工厂的接口,能够维护不同 Bean 及其依赖关系的注册表。我们通过使用方法 T getBean(String name, Class requiredType)
,就可以获取到我们需要的 Bean 对象。
代码如下:
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
UserService userService = context.getBean("userService", UserService.class);
String roleList = userService.getRoleList();
String username1 = userService.getUsernameById(23L);
String username2 = userService.getUsernameById(24L);
System.out.println("roleList = " + roleList + " | username1 = " + username1 + " | username2 = " + username2);
输出:
总结
以上,就是本文的所有内容,主要讲了什么是 Spring IoC 容器,介绍了 BeanFactory 和 ApplicationContext。
实际上这个 ApplicationContext 就代表容器,它会读取我们的配置元数据,这样它就知道该去管理哪些对象了。
也介绍了什么是 Bean,实际上就是被容器管理的对象,都是所谓的 Bean,也习惯称为 Bean 对象。
还介绍了 Spring IoC 容器从顶层上来看是怎样运行的,就是将各种业务对象和配置元数据相结合,组成一个完整配置的、可用的应用程序。
对于配置元数据,这个可以有多种形式,可以是 XML,可以是 Java 注解,可以是 Java 代码。
最后就介绍了如何去实例化并使用 Spring IoC 容器,虽然我们日常开始是不会这样去做的,不会去创建一个容器,然后通过容器的 getBean 去获取 Bean 进行操作,但是我们就是需要了解学习,因为这些就是 Spring 的基础。
最后的最后
希望各位屏幕前的靓仔靓女们
给个三连!你轻轻地点了个赞,那将在我的心里世界增添一颗明亮而耀眼的星!
咱们下期再见!