Spring丨IoC

前言

IoC(Inversion of Control,控制反转)是一种思想。这是 Spring 的核心,贯穿始终。

控制:谁控制谁?控制了什么?答:谁来控制对象的创建。
传统 Java SE 程序设计,我们直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象;而 IoC 是有专门一个容器来创建这些对象,即由 IoC 容器来控制对象的创建。对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方 Spring。

反转:什么是反转?反转了什么?正转,就是传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象。反转,就是程序本身不去主动创建依赖对象了,而是由容器来帮忙创建及注入依赖对象,变成了被动的接受依赖对象。也就是说,依赖对象的获取被反转了。

控制权转移的意义是降低了类之间的耦合度。

IoC 演示

在项目的 src 目录下创建一个名为 com.study.ioc 的包,然后在该包中创建一个名为 PersonDao 的接口,并在接口中添加一个 add() 方法,如下所示。

package com.study.ioc;

public interface PersonDao {
    public void add();
}

创建接口实现类 PersonDaoImpl。

package com.study.ioc;

public class PersonDaoImpl implements PersonDao{

    @Override
    public void add() {
        System.out.println("add()方法执行了");
    }
}

上述代码中,PersonDaoImpl 类实现了 PersonDao 接口中的 add() 方法,并且在方法调用时会执行输出语句。

创建一个名为 PersonService 的服务层接口,并在接口中添加一个 addPerson() 方法,如下所示。

package com.study.ioc;

/**
 * 模拟 服务层
 */
public interface PersonService {
    public void addPerson();
}

创建接口实现类 PersonServiceImpl。

package com.study.ioc;

public class PersonServiceImpl implements PersonService {
    // 原写法,这种写法固定了某个对象,一旦想要替换,代价很大
    private PersonDao personDao = new PersonDaoImpl();

    // 实现 PersonService接口的方法
    @Override
    public void addPerson() {
        // 调用PersonDao中的add()方法
        personDao.add();
        System.out.println("addPerson()执行了...");
    }
}

编写测试类 FirstTest。

package com.study.ioc;

public class FirstTest {

    public static void main(String[] args) {
        PersonService personService = new PersonServiceImpl();
        personService.addPerson();
    }
}

在 PersonServiceImpl类中,由程序实现创建 PersonDaoImpl 对象,这种创建的方法的缺点:一旦需要更换为 PersonDao接口的其他实现类,就需要修改服务层的源代码,代价很大。

改进 PersonServiceImpl 类。

package com.study.ioc;

public class PersonServiceImpl implements PersonService {
    // 原写法,这种写法固定了某个对象,一旦想要替换,代价很大
    // private PersonDao personDao = new PersonDaoImpl();

    // 定义接口声明
    private PersonDao personDao;

    // 提供setter()方法,用于依赖注入
    public void setPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }

    // 实现 PersonService接口的方法
    @Override
    public void addPerson() {
        // 调用PersonDao中的add()方法
        personDao.add();
        System.out.println("addPerson()执行了...");
    }
}

改进测试类 FirstTest。

package com.study.ioc;

public class FirstTest {

    public static void main(String[] args) {
        PersonServiceImpl personService = new PersonServiceImpl();
        // 由测试类(客户端)自定义接口实现类
        personService.setPersonDao(new PersonDaoImpl());
        // personService.setPersonDao(new XXXX());
        personService.addPerson();
    }
}

在 PersonServiceImpl 类中,声明了 PersonDao 实现类对象,但没有创建,就减少了 PersonServiceImpl 对 PersonDaoImpl的依赖;这种情况下,创建权给了客户端,客户端可以自由选择 PersonDao 实现类对象,不一定是 PersonDaoImpl 类。同时,如果 PersonDao 接口的实现发生了改变,应用程序本身也不用发生改变。
Service 层与 Dao 层实现了分离,没有直接的依赖关系

在客户端中,我们依然需要通过 new 字符创建一个 PersonDao 实现类对象,这一步可以交给 Spring 容器完成。导入 Spring 的相关 jar 包后,在 XML 配置文件中配置 bean,Spring 容器就会帮我们创建对象,设置对象的属性。这个过程就叫控制反转

在 Spring 的配置文件中配置:

<!-- 由 Spring容器创建该类的实例对象,其中 id 表示文件中的唯一标识符 -->
<bean id="personDao" class="com.study.ioc.PersonDaoImpl" />

<bean id="personService" class="com.study.ioc.PersonServiceImpl">
    <!-- 将 personDao实例注入 personService实例中 -->
    <!-- 此处的name是setPersonDao()方法中的set后面的PersonDao,首字母要小写。ref是指引用对象,填某个 bean的 id名称 -->
    <property name="personDao" ref="personDao"/>
</bean>

改进测试类。

public class FirstTest {

    @Test
    public void test1() {
        // 定义 Spring 配置文件的路径,需要把 config 文件夹设置为 Resources
        String xmlPath = "applicationContext.xml";
        // 初始化 Spring 容器,加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
        // 通过容器中 bean 的 id 获取 personDao实例
        PersonDao personDao = (PersonDao) applicationContext.getBean("personDao");
        // 调用 personDao 的 add()方法
        personDao.add();
    }

    @Test
    public void test2() {
        // 定义 Spring 配置文件的路径
        String xmlPath = "applicationContext.xml";
        // 初始化 Spring 容器,加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
        // 通过容器中 bean 的 id 获取 personService 实例
        PersonService personService = (PersonService) applicationContext.getBean("personService");
        // 调用 personService 的 addPerson()方法
        personService.addPerson();
    }
}

使用了 Spring 之后,即使需要更换 PersonServiceImpl 的传入对象,源代码文件也不需要改动。只需要改 XML 配置文件中的 ref 的值即可。

BeanFactory

BeanFactory 是基础类型的 IoC 容器,它由 org.springframework.beans.facytory.BeanFactory 接口定义,并提供了完整的 IoC 服务支持。简单来说,BeanFactory 就是一个管理 Bean 的工厂,它主要负责初始化各种 Bean,并调用它们的生命周期方法。
BeanFactory 接口有多个实现类,最常见的是 org.springframework.beans.factory.xml.XmlBeanFactory,它是根据 XML 配置文件中的定义装配 Bean 的。
创建 BeanFactory 实例时,需要提供 Spring 所管理容器的详细配置信息,这些信息通常采用 XML 文件形式管理。其加载配置信息的写法如下所示:

BeanFactory beanFactory = new XmlBeanFactory(new FileSystemResource("D://applicationContext.xml"));

ApplicationContext

ApplicationContext 是 BeanFactory 的子接口,也被称为应用上下文。该接口的全路径为 org.springframework.context.ApplicationContext,它不仅提供了 BeanFactory 的所有功能,还添加了对 i18n(国际化)、资源访问、事件传播、支持 AOP 等方面的良好支持。
ApplicationContext 接口有两个常用的实现类,具体如下。

ClassPathXmlApplicationContext

该类从类路径 ClassPath 中寻找指定的 XML 配置文件,找到并装载完成 ApplicationContext 的实例化工作,具体如下所示。

ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);

在上述代码中,configLocation 参数用于指定 Spring 配置文件的名称和位置,如 applicationContext.xml。

FileSystemXmlApplicationContext

该类从指定的文件系统路径中寻找指定的 XML 配置文件,找到并装载完成 ApplicationContext 的实例化工作,具体如下所示。

ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);

它与 ClassPathXmlApplicationContext 的区别是:在读取 Spring 的配置文件时,FileSystemXmlApplicationContext 不再从类路径中读取配置文件,而是通过参数指定配置文件的位置,它可以获取类路径之外的资源,如 “D:/workspaces/applicationContext.xml”。
在使用 Spring 框架时,可以通过实例化其中任何一个类创建 Spring 的 ApplicationContext 容器。

区别

通常在 Java 项目中,会采用通过 ClassPathXmlApplicationContext 类实例化 ApplicationContext 容器的方式;而在 Web 项目中,ApplicationContext 容器的实例化工作会交由 Web 服务器完成。
Web 服务器实例化 ApplicationContext 容器通常使用基于 ContextLoaderListener 实现的方式,它只需要在 web.xml 中添加如下代码:

<!-- 指定 Spring配置文件的位置,有多个配置文件时,以逗号分隔 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <!-- Spring将加载 spring目录下的 applicationContext.xml文件-->
    <param-value>
        classpath:spring/applicationContext.xml
    </param-value>
</context-param>

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

需要注意的是,BeanFactory 和 ApplicationContext 都是通过 XML 配置文件加载 Bean 的。
二者的主要区别在于,如果 Bean 的某一个属性没有注入,则使用 BeanFacotry 加载后,在第一次调用 getBean() 方法时会抛出异常;而 ApplicationContext 则在初始化时自检,这样有利于检查所依赖的属性是否注入。
因此,在实际开发中,通常都选择使用 ApplicationContext,而只有在系统资源较少时,才考虑使用 BeanFactory。

参考

博客园 - Spring实现IoC的多种方式
CSDN - Spring第一课
打赏
评论区
头像
文章目录