一文让你理解Spring的核心思想之IoC


IoC全名为Inversion of Control,中文名是控制反转,可以降低程序之间的耦合。第一次听到这个说法时,我和你一样,也是一头雾水:它为什么叫控制反转了?它怎么就降低程序之间的耦合了?不急,让我们从一个简单的添加账户的功能说起。

不使用IoC

我们先新建一个空白的Maven工程。
然后我们将要编写的代码分为三部分:Service层、Dao层和UI。UI调用Service层,Service层再根据业务调用Dao层,Dao层再实际进行数据库的操控。
此时项目结构如下图所示:
在这里插入图片描述
下面将Java中的文件列出来。

dao

dao包中包含IAccountDao接口以及包含IAccountDao接口的实现类的impl包

// IAccountDao.java

package com.yonmin.dao;

public interface IAccountDao {
    // 模拟保存账户功能
    void saveAccount();
}

impl

// IAccountDao接口的实现类 AccountDaoImpl.java

package com.yonmin.dao.impl;

import com.yonmin.dao.IAccountDao;

public class AcccountDaoImpl implements IAccountDao {
    @Override
    public void saveAccount() {
        System.out.println("保存账户成功");
    }
}

service

service包中包含IAccountService接口以及包含IAccountService接口的实现类的impl包

// IAccountService.java

package com.yonmin.service;
// 模拟保存账户信息服务
public interface IAccountService {
    void saveAccount();
}

impl

// IAccountService接口的实现类 AccountServiceImpl.java

package com.yonmin.service.impl;

import com.yonmin.dao.IAccountDao;
import com.yonmin.dao.impl.AcccountDaoImpl;
import com.yonmin.service.IAccountService;

public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao = (IAccountDao)new AcccountDaoImpl();

    @Override
    public void saveAccount() {
        accountDao.saveAccount();
    }
}

ui

我们在ui包下新建Client.java用来发出保存账户的服务请求

// Client.java

package com.yonmin.ui;

import com.yonmin.service.IAccountService;
import com.yonmin.service.impl.AccountServiceImpl;

public class Client {
    public static void main(String[] args) {
        IAccountService accountService = (IAccountService) new AccountServiceImpl();
        accountService.saveAccount();
    }
}

耦合之处

AccountServiceImpl.java中有这么一行代码,使用了new关键字创建了对象。

private IAccountDao accountDao = (IAccountDao)new AcccountDaoImpl();

Client.java中也有使用new关键字创建对象地一行代码

IAccountService accountService = (IAccountService) new AccountServiceImpl();

当我们使用new关键字时,就加强了程序之间的耦合,IoC所要做的就是尽可能地降低程序之间的耦合,即尽可能地消除new关键字。

使用IoC

那我们应该怎么做呢?前辈们给出的答案是使用工厂模式和配置文件。

我们在resources目录下新建一个名为bean.properties的配置文件,
新建一个和dao包同级的factory包,包下新建一个名为BeanFactory的用于创建Bean对象的工厂类。此时项目结构如下
在这里插入图片描述
Bean就是可以重用的组件,JavaBean就是用Java语言编写的可重用组件。

bean.properties

bean.properties中的内容就是键值对,读取配置文件后可以通过等号前面的键获取等号后面的值

# bean.properties
accountService=com.yonmin.service.impl.AccountServiceImpl
accountDao=com.yonmin.dao.impl.AccountDaoImpl

factory

factory包中放置的就是BeanFactory,即创建我们的service和dao对象的工厂

// BeanFactory.java

package com.yonmin.factory;

import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class BeanFactory {
    // 定义一个properties对象
    private static Properties props;

    // 定义一个Map,用于存放我们要创建的对象,我们将其称之为容器
    private static Map<String,Object> beans;

    // 使用静态代码块对Properties对象赋值
    static {
        try {
            // 实例化对象
            props = new Properties();
            // 获取properties文件的流对象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            props.load(in);
            // 实例化容器
            beans = new HashMap<String,Object>();
            // 取出配置文件中所有的key
            Enumeration<Object> keys = props.keys();
            // 遍历枚举
            while (keys.hasMoreElements()){
                // 取出每个key
                String key = keys.nextElement().toString();
                // 根据key获取value
                String beanPath = props.getProperty(key);
                // 反射创建对象
                Object value = BeanFactory.getBean(beanPath);
                // 把bean放到容器中
                beans.put(key,value);
            }

        } catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 根据bean的名称获取对象
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName) {
        return beans.get(beanName);
    }
}

上述代码将配置文件bean.properties中的键和值所对应的对象存入到了名为beans的Map中,我们可以通过getBean静态方法传入beanName获取Map中相应的bean对象。此时的Map就相当于Spring中的IoC容器

service

此时IAccountService接口不用变化,IAccountService接口的实现类AccountServiceImpl中要去掉new关键字。
通过BeanFactory类的静态方法getBean,传入accountDao参数即可获取AccountDaoImpl的bean对象

// AccountServiceImpl.java

package com.yonmin.service.impl;

import com.yonmin.dao.IAccountDao;
import com.yonmin.factory.BeanFactory;
import com.yonmin.service.IAccountService;

public class AccountServiceImpl implements IAccountService {

	// private IAccountDao accountDao = new AccountDaoImpl();
    private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
    @Override
    public void saveAccount() {
        accountDao.saveAccount();
    }
}

ui

通过BeanFactory类的静态方法getBean,传入accountService参数即可获取AccountServiceImpl的bean对象

// Client.java

package com.yonmin.ui;

import com.yonmin.factory.BeanFactory;
import com.yonmin.service.IAccountService;

public class Client {

    public static void main(String[] args) {
		// AccountServiceImpl accountService = new AccountServiceImpl();
        IAccountService accountService = (IAccountService) BeanFactory.getBean("accountService");
        accountService.saveAccount();
    }
}

此时我们将原来代码中的new关键字,但是可能你会说了,BeanFactory类中实例化对象操作new Properties()又多了个new关键字,这就是之前说的尽可能消除new关键字,而不是让代码中不出现一个new关键字。

何为IoC?

说了这么多,也到了解释IoC为何物的时候了:当我们不使用IoC时创建对象,我们的应用直接和资源联系,而且二者的联系消除不掉;而当我们使用IoC创建对象时,过程就发生了变化,我们的应用和资源断开了联系,而是找工厂要资源,由工厂与资源联系,并获得需要的对象,转到应用中,这样就消除了应用和资源的联系,这种思想就是IoC。因为实体类将其自主创建对象的权力交给了工厂创建,因此也叫做控制反转,带来的好处就是减少代码中的依赖关系,也即削减程序的耦合。

猜你喜欢

转载自blog.csdn.net/weixin_44547562/article/details/106145686