IoC容器的一般概念和问题

Posted starryi-alpha

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IoC容器的一般概念和问题相关的知识,希望对你有一定的参考价值。

简单模型

class Dependent {
}

class Component {
    private Dependent dependent;
    // 通过构造函数声明依赖
    Component(Dependent dependent) {
        this.dependent = dependent;
    }
    public work() {
        // do work with dependent
    }
}

Container container = new Container();
container.addComponent(Dependent.class);
container.addComponent(Component.class);
Component instance = container.getComponent(Component.class);
instance.work();

以上代码表示一个IoC容器能提供的最基础功能,但完备的IoC容器绝不会止步于此。IoC容器的诸多特性,都是面对现实需要,应运而生。

作用域或缓存

class CustomThread extends Thread {
    public Component  componentInstance;
    public void run() {
        Component instance1 = container.getComponent(Component.class);      
        this.componentInstance= instance1;
        Component instance2 = container.getComponent(Component.class);
        assert(instance1 == instance2); // 在同一个线程中,不管何时请求,容器始终返回同一个实例

    }
}
CustomThread threadA = new CustomThread();
threadA.start();
CustomThread threadB = new CustomThread();
threadB.start();
assert(threadA.componentInstance != threadB.componentInstance) // 在不同线程中,容器返回不同的实例

为了更彻底的满足上述需求,容器一般会提供作用域或缓存的概念。在同一个作用域或缓存中,容器始终返回同一个实例,在不同作用域或缓存中,容器返回不同的实例。

一些常用的作用域或缓存有:

  • Container Scope / Container Caching
  • Prototype Scope / No Caching
  • Thread Scope / Thread Caching
  • Session Scope / Session Caching
  • Request Scope / Request Caching

生命周期

对于某些具有生命周期的组件,如使用了容器外部资源,定时器等的组件等,我们希望容器可以提供对组件生命周期管理的机制, 防止资源泄露

interface Lifecycle {
    void start();
    void stop();
}
class Dependent implement Lifecycle {

    public void start() {
        // 初始化外部资源,启动定时器等
    }

    public void stop() {
        // 清理外部资源,关闭定时器等
    }
    
    public work() {
        // do work
    }
}

class Component {
    private Dependent dependent;

    Component(Dependent dependent) {
        this.dependent = dependent;
    }
    public work() {
        // do work with dependent
    }
}

container.start();
// ...
container.stop();

容器在实现生命周期机制时,必须满足如下特性:

  • start顺序:Container -> Dependent -> Compoennt
  • stop顺序:Container -> Component -> Dependent

防止依赖还未启动时就被使用,或还在使用时就被停止,这也是为什么当对象间的依赖关系比较复杂时,由组件自身控制生命周期是不现实的原因,而恰恰IoC容器可以很轻易的实现按依赖关系启动或停止组件实例。

一般而言,容器仅对具有容器作用域或容器级缓存的对象提供生命周期管理功能,因为只有这些对象是容器直接管理的,可以被容器引用到的。对于声明为其他作用域的组件,因为每次请求组件实例时,容器都有可能产生新的实例,容器不太可能一一记录并管理其引用,这会导致资源泄露。所以,对于声明为其他作用域的组件,如果该作用域没有实现退出作用域后清理引用及资源,那么必须在组件其完成任务后由调用者主动清理

多样化的匹配方式

首先看下两种典型的匹配依赖时产生的歧义问题

多实现组件导致的歧义

interface Dependent {}
class DependentA implements Dependent {}
class DependentB implements Dependent {}

class Component {
    private Dependent dependent;
    // 容器注入 DependentA 还是 DependentB ? 
    Component(Dependent dependent) {
        this.dependent = dependent;
    }
    public work() {
        // do work with dependent
    }
}

多构造函数导致的歧义

class Dependent {}

class DependentFactory {}

class Component {
    private Dependent dependent;

    // 容器注入 DependentFactory 还是 Dependent ? 
    Component(DependentFactory factory) {
        this.dependent= factory.create();
    }

    Component(Dependent dependent) {
        this.dependent = dependent;
    }
    public work() {
        // do work with dependent
    }
}

提供更加多样的依赖匹配方式,可以精确指定依赖的组件类型,解决上述歧义。

常用的依赖匹配方式包括:

  • 容器按类型自动匹配
// 最基础的匹配方式,组件类型和依赖参数类型相同时匹配成功,不能解决歧义
container.addComponent(DependentA.class);
container.addComponent(DependentB.class);
  • 容器按名称自动匹配
// 装配组件时指定组件名称,组件名称和依赖参数名称相同时匹配成功,名称具有唯一性,可以解决歧义
container.addComponent("dependent1", DependentA.class);
container.addComponent("dependent2", DependentB.class);
  • 明确指定依赖,容器不自动匹配
// 参数名称匹配组件类型
container.addComponent("component", Component.class, new Parameter(new HashMap<String, Class>() {{
    put("parameterName", Dependent.class);
}})
// 参数类型匹配组件类型
container.addComponent("component", Component.class, new Parameter(new HashMap<Class, Class>() {{
    put("parameterName", Dependent.class);
}})
// 参数索引匹配类型
container.addComponent(Component.class, new Parameter(new Class[] {DependentA.class, DependentB.class}));

原始类型或集合类型依赖

interface Dependent {}
class DependentA implements Dependent {}
class DependentB implements Dependent {}

class Component {
    public String name;
    public int age;
    public List<Dependent> dependents;

    Component(String name, int age, List<Dependent> dependents) {
        this.name = name;
        this.age = age;
        this.dependents = dependents;
    }
    public work() {
        // do work with dependent
    }
}

容器要能提供某种机制可以匹配原始类型和集合类型依赖,比如

container.addComponent(Component.class, new Parameter(new HashMap<String, Object>() {{
    put("name", new Value("starryi"));
    put("name", new Value(18));
}}));

Component instance = container.getComponent(Component.class);
assertEqual(instance.name, "starryi");
assertEqual(instance.age, 18);
assertEqual(instance.dependent.size, 2);

匹配集合类型依赖时,容器会将所有匹配成功的组件添加到集合中,不会产生因为多实现组件而导致的歧义问题

以上是关于IoC容器的一般概念和问题的主要内容,如果未能解决你的问题,请参考以下文章

PHP依赖注入,控制反转,反射Ioc容器和服务提供者各个概念的理解和使用

Spring容器的理解

Spring基础:IOC概念引入

Spring 与 IoC

Spring的IOC容器加载

Spring——IOC容器基本概念