Spring IoC


1. IoC 的基本概念

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

想吃面包,你可以自己做。也可以选择在面包店下单,告诉店家你的需求,然后等着吃就行。

想买汽车,直接给工厂下单付款即可。

上面的例子包含了控制反转的思想:把制作面包的主动权交给面包店。

当某个Java对象(调用者,例如我),需要调用另一个Java对象(被调用者,即被依赖对象,例如面包)时,以前我们通常会“new 被调用者”来创建对象(例如我们自己做面包)。这种方式会增加调用者与被调用者之间的耦合性,不利于后期代码的升级和维护。

Spring出现后,对象的实例由Spring容器(例如面包店)来创建。Spring容器会负责控制程序之间的关系(例如面包店负责控制我们与面包的关系)。这样控制权就由调用者转移到Spring容器,控制权发生了反转。

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

综上所述,控制反转是一种通过描述(XML或者注解)并通过第三方去产生或获取特定对象的方式。实现控制反转的是IoC容器,其实现方式是依赖注入。

2.IoC 容器

前面我们知道,实现控制反转的是IoC容器。IoC容器的设计主要是基于BeanFactory 和 ApplicationContext 两个接口。

2.1 BeanFactory 接口

BeanFactory 由org.springframework.beans.factory.BeanFactory接口定义,提供了完整的IoC服务支持,是一个管理Bean的工厂,主要负责初始化各种Bean。

BeanFactory接口有很多实现类,常用的是XmlBeanFactory,根据XML配置文件中的定义来配置Bean。

使用BeanFactory实例加载Spring配置文件实际并不多见,仅作了解。

2.2 ApplicationContext 接口

ApplicationContext 是BeanFactory的子接口,也称应用上下文。除了包含BeanFactory的所有功能以外,还添加了对国际化、资源访问、事件传播等内容的支持。

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

2.2.1 通过ClassPathXmlApplicationContext
public static void main(String[] args) {
    // 初始化Spring容器ApplicationContext,加载指定的XML配置文件
    ApplicationContext appCon = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 通过容器获取test实例
    TestDao tt = (TestDao)appCon.getBean("test");
    tt.sayHello();
}
2.2.2通过FileSystemXmlApplicationContext创建
// 仅需要修改这一行代码
ApplicationContext appCon = new FileSystemXmlApplicationContext("D:\test\applicationContext.xml");

FileSystemXmlApplicationContext 将从指定文件的绝对路径中寻找XML配置文件,但是绝对路径会导致程序的灵活性变差,不推荐使用。通常Spring的Java应用采用ClassPathXmlApplicationContext类来实例化ApplicationContext容器,而Web应用中,将交给Web服务器完成。

2.2.3通过Web服务器实例化ApplicationContext容器

Web服务器实例化ApplicationContext容器时,一般使用基于org.springframework.web.context.ContextLoaderListener的实现方式。

<context-param>
    <!-- 加载src目录下的applicationContext.xml文件 -->
    <param-name>contextConfigLocation</param-name>
    <param-value>
        classpath:applicationContext.xml
    </param-value>
</context-param>

<!-- 指定以ContextLoaderListener方式启动Spring容器 -->
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

3.依赖注入

Spring中实现IoC容器的方法是依赖注入。依赖注入的作用是在使用Spring创建对象时,动态地将其所依赖的对象(例如属性值)注入Bean组件中。

3.1使用属性的setter方法注入

依赖注入通常有两种实现方式:构造方法注入、属性的setter方法注入。(这两种方式都是基于Java的反射机制)。

使用setter方法注入是Spring中最主流的注入方式,利用Java Bean规范所定义的setter方法来完成注入,灵活且可读性高。

下面将两者放在一起进行,方便对比。

3.1.1创建dao包

在dao包创建TestDIDao接口及其实现类TestDIDaoImpl

package dao;
public interface TestDIDao {
    public void sayHello();
}
package dao;
public class TestDIDaoImpl implements TestDIDao {
    @Override
    public void sayHello() {
        System.out.println("TestDIDao say: Hello");
    }
}
3.1.2创建service包

service包中创建TestDIService接口及其实现类TestDIServiceImpl

package service;
public interface TestDIService {
    public void sayHello();
}
package service;
import dao.TestDIDao;
public class TestDIServiceImpl implements TestDIService {
    private TestDIDao testDIDao;
    // 添加testDIDao属性的setter方法,用于实现依赖注入
    public void setTestDIDao(TestDIDao testDIDao) {
        this.testDIDao = testDIDao;
    }
    /* 构造方法,用于实现依赖注入接口对象testDIDao
    public TestDIServiceImpl(TestDIDao testDIDao) {
        super();
        this.testDIDao = testDIDao;
    }
    */
    @Override
    public void sayHello() {
        // 调用testDIDao中的sayHelllo方法
        testDIDao.sayHello();
        System.out.println("TestDIService setter方法注入 say: Hello");
    }
}
3.1.3编写配置文件

在src根目录下创建Spring配置文件 applicationContext.xml。将TestDIServiceImpl类托管给Spring,让Spring创建其对象,同时调用其setter方法完成依赖注入。

<!-- 将TestDIDaoImpl类配置给Spring,让Spring创建其实例 -->
<bean id="myTestDIDao" class="dao.TestDIDaoImpl"/>

<!-- 使用setter方法注入 -->
<bean id="testDIService" class="service.TestDIServiceImpl">
    <!-- 调用TestDIServiceImpl类的setter方法,将myTestDIDao注入到TestDIServiceImpl类的属性testDIDao上-->
    <property name="testDIDao" ref="myTestDIDao"/>
</bean>

/*
<!-- 使用构造方法注入 -->
<bean id="testDIService" class="service.TestDIServiceImpl">
    <!-- 将myTestDIDao注入到TestDIServiceImpl类的属性testDIDao上-->
    <!-- constructor-arg元素用于定义类构造方法的参数,index定义参数的位置-->
    <!-- ref指定某个实例的引用,如果参数是常量值,ref由value代替-->
    <constructor-arg index="o" ref="myTestDIDao"/>
</bean>
*/
3.1.4测试

创建test包,并创建TestDI测试类

package test;
public class TestDI {
    public static void main(String[] args) {
        ApplicationContext appCon = new ClassPathXmlApplicationContext("applicationContext.xml")
        TestDIservice ts = (TestDIService)appCon.getBean("testDIService");
        ts.sayHello();
    }
}

  TOC