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);
匹配集合类型依赖时,容器会将所有匹配成功的组件添加到集合中,不会产生因为多实现组件而导致的歧义问题
上一篇: 统计一个整数二进制格式中包含多少个“1”
下一篇: 数组的快速排序
推荐阅读
-
IoC容器的一般概念和问题
-
主流web容器(jetty,tomcat,jboss)的classloader机制对比和相关问题分析
-
主流web容器(jetty,tomcat,jboss)的classloader机制对比和相关问题分析
-
Oracle回滚段的概念,用法和规划及问题的解决
-
Oracle回滚段的概念,用法和规划及问题的解决
-
详解C#中的依赖注入和IoC容器
-
进程间的实时通讯方案: local socket(解决扩展和容器应用的实时通讯问题)
-
Spring概述 IOC容器和bean的配置
-
# Spring IOC容器:BeanFactory、ApplicationContext和Bean的加载
-
ORACLE回滚段的概念,用法和规划及问题的解决