Spring - IoC 控制反转

1. Inversion of Control (IoC)

控制反转,也称为依赖注入(Dependency Injection, DI)。这里的反转指获得依赖对象的过程被反转,传统模式中我们在类内部主动创建依赖对象(直接new一个依赖对象)。用控制反转的方式,我们不需要主动实例化依赖类,而是通过依赖注入来实现,即IoC容器帮我们创建好对象,当我们需要依赖对象时,向IoC容器获取。这种方式减少了类之间的耦合。

接下来以程序为例,首先我们不使用控制反转,即我们在类内部主动创建依赖对象(直接new一个依赖对象)

比如,现在有三个类:

  1. AccountDaoImpl:账户持久层实现类,用户的具体操作对数据库进行增删改查

  2. AccountServiceImpl:账户业务层实现类,用户通过这个类操作业务

  3. Client:模拟一个表现层,用于调用业务层

/**
 * 1 账户持久层实现类
 */
public class AccountDaoImpl implements IAccountDao {
    public void saveAccount(){
        System.out.println("保存了账户");
    }
}

/**
 * 账户持久层接口
 */
public interface IAccountDao {
    void saveAccount();
}
/**
 * 2 账户业务层实现类
 */
public class AccountServiceImpl implements IAccountService {
    // 通过创建一个AccountDaoImpl来实际操作数据库,高耦合
    private IAccountDao accountDao = new AccountDaoImpl();
    // 假设现在的操作是保存账户
    public void saveAccount(){
        accountDao.saveAccount();   // 通过调用AccountDao中方法实现
    }
}
​
/**
 * 账户业务层接口
 */
public interface IAccountService {
    void saveAccount();
}
/**
 * 模拟一个表现层,用于调用业务层
 */
public class Client {
    public static void main(String[] args) {
        // 通过创建一个AccountServiceImpl对象来开始具体业务,高耦合
        IAccountService as = new AccountServiceImpl();
        as.saveAccount();
    }
}

从上面代码可以看到,Client类需要主动创建AccountServiceImpl对象来展开业务,而AccountServiceImpl类需要主动创建AccountDaoImpl类来对数据库进行操作完成具体业务。这种行为使得类与类之间高耦合,具体表现:如果我们没有AccountDaoImpl类(在项目中删除AccountDaoImpl类),再运行程序,此时会报错:Error:(4, 28) java: 程序包com.itheima.dao.impl不存在。但这是一个编译错误,即程序并没有编译成功,这是我们不希望看到的,我们希望当缺失类时报的是运行时错误。

所以为了减少耦合,我们会使用工厂模式来创建对象,即在上述三个类的基础上,增加一个工厂类,用于创建AccountServiceImpl和AccountDaoImpl对象:

/**
 * BeanFactory:根据配置文件创建service和dao对象
 */
public class BeanFactory {
    // 定义一个Properties对象
    private static Properties props;
    // 使用静态代码块为Properties对象赋值
    static{
        try {
            // 实例化对象
            props = new Properties();
            // 获取properties文件的流对象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            props.load(in);
        } catch(Exception e){
            throw new ExceptionInInitializerError("初始化properties失败!");
        }
    }
    /**
     * 根据beanName获取bean对象
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName){
        Object bean = null;
        try{
            // 根据beanName实例化对象
            String beanPath = props.getProperty(beanName);
            bean = Class.forName(beanPath).newInstance();
        } catch(Exception e){
            e.printStackTrace();
        }
        return bean;
    }
}
// bean.properties配置文件内容
accountService=com.itheima.service.impl.AccountServiceImpl
accountDao=com.itheima.dao.impl.AccountDaoImpl

同时更改Client和AccountDaoImpl类中代码

public class Client {
    public static void main(String[] args) {
//        IAccountService as = new AccountServiceImpl();
        // 通过工厂类创建AccountServiceImpl类,减少耦合
        IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
        as.saveAccount();
    }
}
public class AccountServiceImpl implements IAccountService {
//    private IAccountDao accountDao = new AccountDaoImpl();
    // 通过工厂类创建AccountDaoImpl类,减少耦合
    private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
    public void saveAccount(){
        accountDao.saveAccount();
    }
}

以上通过工厂模式减少了类与类之间的耦合,我们再删除AccountDaoImpl类运行代码,此时程序报运行时错误,抛出异常。

而spring通过配置XML文件来帮我们创建对象(bean对象,保存在IoC容器中),减少了类之间的耦合度,与工厂模式相比,spring配置起来更加方便,功能也更加强大。

public class Client {
    /**
     * 获取spring的IOC核心容器,并根据id获取对象
     * @param args
     */
    public static void main(String[] args) {
        // 1.获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        // 2.根据id获取bean对象
        IAccountService as = (IAccountService) ac.getBean("accountService");
        IAccountService ass = (IAccountService) ac.getBean("accountService");
        IAccountDao adao = (IAccountDao) ac.getBean("accountDao", IAccountDao.class);
        System.out.println(as);
        System.out.println(ass);
        System.out.println(adao);
        as.saveAccount();
    }
}
<?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">
​
    <!--  把对象的创建交给spring来管理  -->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
​
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">      </bean>
</beans>

粘贴spring doc中IoC的定义:

It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method.

翻译:这是一个对象仅1)通过构造函数参数、2)工厂方法的参数或3)对象实例构造后设置类成员或从工厂方法返回,来定义其依赖项(即该对象使用其他对象)的过程。

接下来我们用Bean的三种创建方式来帮助理解:

1. 使用类中默认构造函数创建bean对象,若类中无构造函数,则无法创建

<bean id="targetInstance" class="TargetClass"></bean>

 2. 使用普通工厂类中的方法创建bean对象,并存入spring容器中

<!-- 首先创建工厂类,再根据工厂类中的方法创建目标类 -->
<bean id="factoryInstance" class="FactoryClass"></bean>
<bean id="targetInstance" factory-bean="factoryInstance" factory-method="FactoryClass.method"></bean>

3. 使用普通工厂类中的静态方法创建bean对象,并存入spring容器中

<!-- 通过工厂类中的静态方法创建目标类 -->
<bean id="targetInstance" class="FactoryClass" factory-method="FactoryClass.staticMethod"></bean>

所创建的bean对象默认为单例模式,其生命周期与IoC容器一致,当IoC容器被创建时,bean对象就被创建(即我们还未使用依赖对象时,对象已经被创建),IoC容器销毁时,bean对象也被销毁。可以通过bean标签的scope属性更改:

  1. singleton:单例(默认值)

  2. prototype:多例

  3. request:作用于web应用的请求范围

  4. session:作用于web应用的会话范围

  5. global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,就是session

Bean对象的生命周期

单例对象:

  • 出生:当容器创建时创建bean对象

  • 活着:只要容器还在,bean对象一直活着

  • 死亡:容器销毁,bean对象也销毁

多例对象:

  • 出生:当我们使用对象时spring框架创建bean对象

  • 活着:bean对象只要在使用过程中就一直活着

  • 死亡:当对象长时间不用,且没有别的对象引用时,有Java垃圾回收器回收

 

本文通过学习黑马spring教学视频所写,视频链接:https://www.bilibili.com/video/av47952931?p=1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值