实现一个简易IoC容器

April 5, 2020 · 开发 · 341次阅读

<!-- index-menu -->

关于IoC

IoC是Inverse of Controller的缩写,顾名思义控制反转,就是两个作用:控制和反转。控制:控制对象的创建及销毁;反转:将对象的控制权从需要该对象的类反转到IoC容器。

为什么要用IoC?

我们首先来看一个例子:
假设这里有一个叫李明LiMing的这样一个类,他有一台Lenovo电脑,他可以playMusic方法让电脑播放音乐,也可以用office方法让电脑打开办公软件。然后电脑通过一系列操作方法operate1,2,3分别完成了李明的需求。

public class LiMing {
    public void playGame(){
        Lenovo lenovo = new Lenovo();
        lenovo.operate1();
        lenovo.operate3();
    }
    
    public void office(){
        Lenovo lenovo = new Lenovo();
        lenovo.operate2();
        lenovo.operate3();
    }
}
public class Lenovo {
    public void operate1(){
        System.out.println("Lenovo执行操作1");
    }
    public void operate2(){
        System.out.println("Lenovo执行操作2");
    }
    public void operate3(){
        System.out.println("完成");
    }
}

这时候有一个问题来了,如果李明之后换了一台Dell电脑,那其中的代码我们是不是都要挨着全部进行替换。

public class LiMing {
    public void playGame(){
        Dell dell = new Dell();
        dell.operate1();
    }

    public void office(){
        Dell dell = new Dell();
        dell.operate2();
    }
}

因此我们可以思考两个问题:

  1. 李明是否需要的是一个联想电脑,还是需要dell电脑,或者说他需要的就是一个电脑?
  2. 李明会制造电脑吗?

上面两个问题很明显,李明很明显他只是需要一个电脑来用于玩游戏和工作这和品牌无关。而李明也不会自己制造电脑,因此电脑这个对象不应该由李明创建。
因此,我们首先应该抽象出一个Computer接口,接口包含电脑中的各种方法

public interface Computer {
    void operate1();
    void operate2();
    void operate3();
}

接下来再看LIMING类中,liming的电脑就不应由liming创建了,因此我们需要通过构造方法给李明传入车辆对象,至此,李明将对车的控制权转让了出去,但车辆的创建应有谁来创建呢,因此这时候就有了IoC容器的概念。

public class LiMing {
    private Computer computer;
    // 通过构造参数传入
    public LiMing(Computer computer){
        this.computer = computer;
    }
    public void playGame(){
        computer.operate1();
    }

    public void office(){
        computer.operate2();
    }
}

构建一个简单的IoC容器

约定:

  • 所有Bean的生命周期交由IoC容器来管理
  • 所有被依赖的Bean通过构造方法执行注入
  • 被依赖的Bean要优先创建
/**
 * 1.实例化bean
 * 2.保存bean
 * 3.提供bean
 * 4.每一个bean要产生一个唯一的id与之相对应
 */
public class IoCContainer {

    private Map<String,Object> beans = new ConcurrentHashMap<String, Object>();

    /**
     * 根据beanId 获取一个Bean
     * @param beanId beanId
     * @return 返回bean
     */
    public Object getBean(String beanId){
        return beans.get(beanId);
    }

    /**
     * 委托ioc容器创建一个bean
     * @param clazz 要创建的bean的class
     * @param beanId beanId
     * @param paramBeanIds 要创建的bean的class的构造方法所需要的参数的beanId们
     */
    public void setBeans(Class<?> clazz, String beanId,String... paramBeanIds){
        // 1.组装构造方法所需要的参数值
        Object[] paramValues = new Object[paramBeanIds.length];
        for (int i = 0; i < paramValues.length; i++) {
            paramValues[i] = beans.get(paramBeanIds[i]);
        }
        // 2.调用构造方法实例化bean
        Object bean = null;
        for (Constructor<?> constructor : clazz.getConstructors()) {
            try {
                bean = constructor.newInstance(paramValues);
            } catch (InstantiationException e) {
            } catch (IllegalAccessException e) {
            } catch (InvocationTargetException e) {
            }
            if (bean == null){
                throw new RuntimeException("找不到合适的构造方法去实例化bean");
            }
        }
        //3.将实例化的bean放入beans
        beans.put(beanId,bean);
    }
}

至此,我们的IoC容器就写好了。使用的方式如下:

  • 在容器种注册所需要的所有bean
  • 通过容器获取bean
public class TestIoC {
    private IoCContainer ioCContainer = new IoCContainer();
    // 首先在容器种注册bean(创建bean)
    @Before
    public void before(){
        ioCContainer.setBean(Lenovo.class,"lenovo");
        // liming的依赖为联想电脑的bean,因此通过beanid的形式加入依赖
        ioCContainer.setBean(LiMing.class,"liming","lenovo");
    }
    // 通过beanId获取liming的对象
    @Test
    public void test(){
        Human liming = (Human) ioCContainer.getBean("liming");
        liming.playGame();
    }
}

总结

使用IoC容器的好处可以从上面的例子看出:

  1. 所有的依赖关系被集中统一了起来,清晰明了
ioCContainer.setBean(LiMing.class,"liming","lenovo");

2.每个类只需关注自己的业务逻辑
李明无需关心电脑的创建,甚至不用关心电脑是什么牌子的电脑,它只用于专注自己的办公和游戏:

public class LiMing {
    private Computer computer;
    // 通过构造参数传入
    public LiMing(Computer computer){
        this.computer = computer;
    }
    public void playGame(){
        computer.operate1();
    }

    public void office(){
        computer.operate2();
    }
}

3.修改依赖关系将变得很容易,只需在注册进容器中时更改依赖关系即可

ioCContainer.setBean(LiMing.class,"liming","lenovo");
//lenovo ---->  dell
ioCContainer.setBean(LiMing.class,"liming","dell");        

标签:none

最后编辑于:2020/05/02 09:58

添加新评论

控制面板