学习目的与要求
本章主要介绍Spring IoC的基本概念、Spring IoC容器以及依赖注入的类型等内容。

本章主要内容

  • Spring IoC 的基本概念;
  • Spring IoC 容器;
  • 依赖注入的类型。

IoC(控制反转)是Spring 框架的基础,也是Spring框架的核心理念,本章将介绍IoC的基本概念、容器以及依赖注入的类型等内容。

2.1 Spring IoC的基本概念

控制反转(Inversion of Control,IoC)是一个比较抽象的概念,是Spring框架的核心,用来消减计算机程序的耦合问题。依赖注入(Dependency IInjection,DI)是IoC的另一种说法,只是从不同的角度描述相同的概念。

当某个Java对象(调用者,例如您)需要调用另一个Java对象(被调用者,即依赖对象)时,在传统的编程模式下,调用者通常会采用 “new 被调用者” 的代码方式来创建对象。这种方式会增加调用者与被调用者之间的耦合性,不利于后期代码的升级与维护。

当SPring框架出现后,对象的实例不再由调用者来创建,而是由Spring容器来创建。Spring容器会负责控制程序之间的关系,而不是由调用者的程序代码直接控制。这样,控制权由调用者转移到Spring容器,控制权发生了反转,这就是Spring的控制反转

从Spring容器的角度来看,Spring容器负责将被依赖对象赋值给调用者的成员变量,相当于为调用者注入它所依赖的实例,这就是Spring 的依赖注入

2.2 Spring IoC 容器

实现控制反转的是 Spring Ioc 容器。Spring IoC 容器的设计主要是基于 BeanFactoryApplicationContext 两个接口。

2.2.1 BeanFactory

BeanFactory 由 org.springframework.beans.factory.BeanFactory 接口定义,它提供完整的 IoC 服务支持,是一个管理 Bean 的工厂,主要负责初始化各种 Bean。BeanFactory接口有多个实现类,其中比较常用的是 org.springframework.beans.factory.xml.XmlBeanFactory,该类会更具 XML 配置文件中的定义来装配 Bean。

在创建 BeanFactory 实例时需要提供 XML 文件的绝对路径。

  1. public static void main(String[] args){
  2. // 初始化Spring容器,加载配置文件
  3. BeanFactory beanFac = new XmlBeanFactory(
  4. new FileSystemResource("D:\eclipse-workspace\ch1\src\applicationContext.xml")
  5. );
  6. // 通过容器获取test实例
  7. TestDao tt = (TestDao)beanFac.getBean("test");
  8. tt.sayHello();
  9. }

在实际开发中,使用BeanFactory实例加载Spring配置文件并不多见。

2.2.2 ApplicationContext

ApplicationContext 是 BeanFactory 的子接口,也成为应用上下文,由 org.springframework.context.Application 接口定义。

创建 ApplicationContext 接口实例通常有以下 3 种方法:

1. 通过 ClassPathXmlApplicationContext 创建

ClassPathXmlApplicationContext 将从类路径目录(src跟目录)中寻找指定的 XML 配置文件。例:

  1. public static void main(String[] args){
  2. // 初始化Spring容器,加载配置文件
  3. ApplicationContext appCon = new ClassPathXmlApplicationContext
  4. ("application.xml")
  5. // 通过容器获取test实例
  6. TestDao tt = (TestDao)beanFac.getBean("test");
  7. tt.sayHello();
  8. }

该方法是课本针对 Eclipse 编译器说明的,在IDEA编译器中则情况不同,需要在 web 根目录下新建一个 resource文件,然后将所编写的 xml 文件放在 resource 文件目录下,视图如下:

image.png
image.png

2. 通过 FileSystemXmlApplicationContext 创建

FileSystemXmlApplicationContext 将从指定文件的绝对路径中寻找 XML 配置文件,找到并装载完成 ApplicationContext 的实例化工作。例如:

  1. public static void main(String[] args){
  2. // 初始化Spring容器,加载配置文件
  3. ApplicationContext appCon = new ClassPathXmlApplicationContext
  4. ("...绝对路径...")
  5. // 通过容器获取test实例
  6. TestDao tt = (TestDao)beanFac.getBean("test");
  7. tt.sayHello();
  8. }

采用绝对路径的加载方法将导致程序的灵活性变差,一般不推荐使用。我们通常采用 ClassPathXmlApplicationContext 类来实例化 ApplicationCotext 容器的方式。

3. 通过 web 服务器实例化 ApplicationContext 容器

在 web 服务器实例化 ApplicationContext 容器时,一般使用基于 org.springframework.web.context.ContextLoaderListener 的实现方式,此方法只需在 web.xml 中添加如下代码:

  1. <context-param>
  2. <!-- 加载src目录下的applicationContext.xml文件-->
  3. <param-name>contextConfigLocation</param-name>
  4. <param-value>
  5. classpath:applicationContext.xml
  6. </param-value>
  7. </context-param>
  8. <!-- 指定以 ContextLoaderListener 方式启动Spring容器-->
  9. <listener>
  10. <listener-class>
  11. org.springframewoork.web.context.ContextLoaderListener
  12. </listener-class>
  13. </listener>

2.3 依赖注入的类型

在 Spring 中实现 IoC 容器的方法是依赖注入,依赖注入的作用是在使用 Spring 框架创建对象时动态地将其所依赖的对象(例如属性值)注入到 Bean 组件中。Spring 框架的依赖注入通常有两种实现方式,一种是使用构造方法注入,另一种是使用属性的 setter 方法注入

2.3.1 使用构造方法注入

1. 创建dao包

在工程中创建 dao 包,并在该包中创建 TestDIDao 接口和接口实现类 TestDIDaoImpl。创建 dao 包的目的是在 service 中使用构造方法依赖注入 TestDIDao 接口对象。

TestDIDao 接口代码如下:

  1. package dao;
  2. public interface TestDIDao {
  3. public void sayHello();
  4. }

TestDIDaoImpl 实现类的代码如下:

  1. package dao;
  2. public class TestDIDaoImpl implements TestDIDao {
  3. public TestDIDaoImpl(){ // 这是我随便定义的构造函数,目的是为了看 //ApplicationContext使用构造方式依赖注入的结果
  4. System.out.println(123456);
  5. }
  6. @Override
  7. public void sayHello(){
  8. System.out.println("TestDIDao say: Hello, Study hard!");
  9. }
  10. }

2. 创建 service 包

为了使得代码优美一些,我们常常创建一个 service 包,里面存放业务层的代码。在 service 包中创建 TestDIService 接口和接口的实现类 TestDIServiceImpl。在TestDIServiceImpl 中使用构造方法依赖注入 TestDIDao接口对象。

TestDIService 接口的代码如下:

  1. package service;
  2. public interface TestDIService {
  3. public void sayHello();
  4. }

TestDIServiceImpl 实现类的代码如下:

  1. package service;
  2. import dao2.TestDIDao;
  3. public class TestDIServiceImpl implements TestDIService {
  4. private TestDIDao testDIDao;
  5. private int a;
  6. // 构造方法,用于实现依赖注入接口对象 testDIDao
  7. public TestDIServiceImpl(TestDIDao testDIDao, int b){
  8. super();
  9. this.testDIDao = testDIDao;
  10. this.a = b;
  11. }
  12. @Override
  13. public void sayHello(){
  14. // 调用testDIDao 中的 sayHello方法
  15. testDIDao.sayHello();
  16. System.out.println("TestDIService构造方法注入say: Hello, Study hard!" + a);
  17. }
  18. }

3. 编写配置文件

在配置文件 applicationContext.xml 中将 dao.TestDIDaoImpl 类托管给 Spring,让 Spring 创建其对象,然后将 service.TestDIServiceImpl 类托管给 Spring,让 Spring 创建其对象,同时给构造方法传递实参。配置文件的具体代码如下:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <!-- 将指定类TestDIDaoImpl配置给Spring,让Spring创建其实例-->
  6. <bean id="myTestDIDao" class="dao.TestDIDaoImpl"/>
  7. <!--使用构造方法注入-->
  8. <bean id="testDIService" class="service.TestDIServiceImpl">
  9. <!--通过构造函数将myTestDIDao注入到TestDIServiceImpl类的属性testDIDao上-->
  10. <constructor-arg index="0" ref="myTestDIDao"/>
  11. <!--如果参数是常数值,refvalue代替-->
  12. <constructor-arg index="1" value="123"/>
  13. </bean>
  14. </beans>

在配置文件中,constructor-arg 是用于定义类构造方法的参数的标签,index用于定义参数的位置,参数位置从0开始。ref 指定某个实例的引用,如果参数值为非基本数据类型,则可通过ref为参数注入值,其值为另一个 bean 标签 id 或 name 属性的属性值;如果参数是常量值,ref 由 value 代替。

4. 创建 test 包

创建 test 包,并在该包中创建测试类 TestDI,具体代码如下:

  1. package test;
  2. import dao2.TestDIDaoImpl;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;
  5. import service2.TestDIService;
  6. import service2.TestDIServiceImpl;
  7. public class TestDI {
  8. public static void main(String[] args) {
  9. // 初始化spring容器ApplicationContext,加载配置文件
  10. ApplicationContext appCon = new ClassPathXmlApplicationContext("applicationContext.xml");
  11. // 通过容器获取testDIService实例,测试构造方法注入
  12. TestDIService ts = (TestDIService)appCon.getBean("testDIService");
  13. ts.sayHello();
  14. // spring构造方法中做的操作相当于下面的语句,这样使得代码优美许多,不像下面这样看起来 //那么奇怪
  15. TestDIServiceImpl test = new TestDIServiceImpl(new TestDIDaoImpl(), 123);
  16. }
  17. }

2.3.2 使用属性的 setter 方法注入

使用 setter 方法注入是 Spring 框架中最主流的注入方式,它利用 Java Bean 规范所定义的 setter 方法来完成注入,灵活且可读性高。对于 setter 方法注入, Spring 框架也是使用 Java Bean 的反射机制实现的。下面代码承接上面。

1. 创建接口实现类 TestDIServiceImpl1

在service 包中创建接口实现类 TestDIServiceImpl1,在 TestDIServiceImpl1 中使用属性的 setter 方法依赖注入 TestDIDao 接口对象,具体代码如下:

  1. package service;
  2. import dao2.TestDIDao;
  3. public class TestDIServiceImpl1 implements TestDIService{
  4. private TestDIDao testDIDao;
  5. // 添加testDIDao属性的setter方法,用于实现依赖注入
  6. public void setTestDIDao(TestDIDao testDIDao){
  7. this.testDIDao = testDIDao;
  8. }
  9. @Override
  10. public void sayHello(){
  11. // 调用testDIDao中的sayHello方法
  12. testDIDao.sayHello();
  13. System.out.println("TestDIService setter方法注入 say: Hello, study hard!");
  14. }
  15. }

2. 将 TestDIServiceImpl1 类托管给 Spring

将TestDIServiceImpl1 类托管给 Spring,让 Spring 创建其对象,同时调用 TestDIServiceImpl1 类的 setter 方法完成依赖注入。在配置文件中添加如下代码:

  1. <!--使用setter方法注入-->
  2. <bean id="testDIService1" class="service.TestDIServiceImpl1">
  3. <!--通过调用TestDIServiceImpl1类的setter方法,将myTestDIDao注入到TestDIServiceImpl1类的属性testDIDao上-->
  4. <property name="testDIDao" ref="myTestDIDao"/>
  5. </bean>

3. 在 test 中测试 setter 方法注入

在主类中添加如下代码测试 setter 方法注入:

  1. // 通过容器获取testDIService实例,测试setter方法注入
  2. TestDIService tsl = (TestDIService)appCon.getBean("testDIService1");
  3. tsl.sayHello();