浅析IoC控制反转的原理

控制反转这个词可能是目前框架设计中提到最流行的词了,象SmallTalk、c++、java等都采用了这些原理进行实现。其中我们所熟知和常用的Spring Framework的核心亦不例外。其实早在2004年Martin Fowler就提出了“哪些方面的控制被反转了?”这个问题。他总结出是依赖对象的获得被反转了。这个做何理解呢?我们知道目前的许多稍复杂的应用基本都是由两个或多个类通过彼此合作来实现具体业务逻辑的,这使得每个对象都需要与合作的对象之间保持引用的关系。如果这个获取过程靠自身来实现,将导致代码高耦合且难以测试。

所谓控制反转可以理解为对象在创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也就是依赖注入到对象中。所以控制反转即关于一个对象如何获取它所依赖对象的引用,这个责任的反转。由之前自身去引用变为由调控系统进行注入。

目前常用的IoC类型主要分为:基于接口的实现方式、基于Setter方法注入的方式和基于构造函数的方式。对于上述提到的3种类型的注入方式,它们之间的优缺点主要体现在:接口注入相对来说有一定的侵入性,对于复杂的应用来说需要编写大量的接口。当然对于较小的应用未必不是一种好的方式,当接口过多时需要较多的考虑接口间的配置及依赖关系。因此现在的应用较多于偏向于选择基于Setter方法的注入及构造函数的注入。对于二者间的优劣主要争论点在于你是倾向于在构造函数中传入多个参数,还是通过域设置Setter的方式。

基于构造函数的方式有其自己的优势,它能够明确地创建出带有特定构造参数的对象,另外它对于创建一些在类中不需要经常变化的域有明显的优势。如果用setter方法来做这种事情会显得很不协调,但通常可以采用init的方法在创建时就将其初始化。当然对于某些类可能有很多的域,构造函数不可能包含所有的情况,而且其中能够包含的构造参数也是有限的,此时Setter方法注入即可以发挥其余地。

下面我们以一个例子来分析一下依赖注入所带来的好处。假定一个想从电影列表中获取某个导演所有的影片列表的一个功能。我们该如何实现呢?

class MovieLister...
    public Movie[] moviesDirectedBy(String arg) {
        List allMovies = finder.findAll();
        for (Iterator it = allMovies.iterator(); it.hasNext();) {
            Movie movie = (Movie) it.next();
            if (!movie.getDirector().equals(arg)) it.remove();
        }
        return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
    }

对于上述的实现,有一个地方需要注意即finder.findAll()。它的功能是从罗列出所有的影片信息。那它的实现又是如何呢?按照常规的思路,我们一般会按如下的设计方式进行。

public intereface MovieFinder {
    List findAll();
}

在MovieLister中进行构造时再采用如下的方式进行调用:

Public class MovieLister {
MovieFinder movieFinder;
Public MovieLister() {
    movieFinder = new MovieFinderImpl();
}
}
采用上述这种方案来设计时,明显的会将MovieLister与MovieFinder进行耦合起来,当可能MovieFinder有另外一种实现时,需要强制修改MovieLister来修改调用方式。换一种思路我们来考虑一下该问题,如果该MovieLister在调用该功能时,能够由一个配置或容器主动的将所需要的类注入进来,将使得双方间解耦。接下来的设计如下:


采用该种解决方案后,其实是多了一个assemble,它用于协调双方的关系,当MovieLister在创建时,不需要显示的创建MovieFinder,而是在assemble中进行了依赖关系的定义。可以借用较为流行的Spring的实现方式如下:

Class MovieLister {
MovieFinder Finder; 
public void setFinder(MovieFinder finder) {
        this.finder = finder;
}
}

Assemble可以采用xml的形式来组织二者间的关系,具体配置如下:

<beans>
<bean id=”movieLister” class=”MovieLister”>
    <property name=”finder”>
    <ref local=”movieFinder”/>
</property>
</bean>
<bean id=”movieFinder” class=”MovieFinderImpl”>
</beans>
最终的功能调用方式如下所示:

public void testWithSpring() throws Exception {
        ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml");
        MovieLister lister = (MovieLister) ctx.getBean("movieLister");
        Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
       System.out.println(movies.length);
   }
当然为了实现解耦,并不是只有依赖注入可以完成,象serviceLocator的方式亦可以完成上述功能的解耦。它的基本思想即是有一个service locator可以定位到该应用所的所有服务。具体的设计可以参见如下的方式:


基于serviceLocator实现的代码片段如下:

class MovieLister {
MovieFinder finder = ServiceLocator.movieFinder();
….
}

class ServiceLocator {
private static ServiceLocator serviceLocator;
private MovieFinder movieFinder;

public ServiceLocator(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
}
public static void load(ServiceLocator arg) {
     if(serviceLocator == null) {
          serviceLocator = arg;
}
}
public static MovieFinder movieFinder() {
    return serviceLocator.movieFinder;
}
}

class Test {
public void configure() {
     ServiceLocator.load(new ServiceLocator(new MovieFinderImpl()));
}
public void testSimple() {
        configure();
        MovieLister lister = new MovieLister();
        Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
        assertEquals("Once Upon a Time in the West", movies[0].getTitle());
    }
}

参考文献:

【Inversion ofControl Containers and the Dependency Injection pattern】http://martinfowler.com/articles/injection.html

【The Dependency Inversion Principle】

http://www.objectmentor.com/resources/articles/dip.pdf

<think>我们正在讨论Spring框架中的控制反转IoC原理。根据引用内容,IoC的核心思想是将对象的创建和依赖关系的管理交给容器处理,而不是由开发者直接控制。我们将详细解释其实现原理。 关键点包括: 1. IoC容器的角色:负责实例化、配置和组装对象。 2. 依赖注入(DI)作为实现IoC的主要方式。 3. 容器如何通过配置(XML或注解)获取对象信息,并在运行时进行依赖注入。 我们将分步骤详细解释。</think>### Spring IoC 控制反转实现原理详解 Spring IoC控制反转)是框架的核心机制,其原理可通过以下关键点解析: #### 一、核心概念 1. **控制反转本质** 将对象的创建、依赖管理权从开发者转移至容器。传统方式需手动`new`对象并管理依赖(高耦合),而IoC模式下开发者仅声明依赖关系,容器负责实例化和装配[^1][^2]。 2. **依赖注入(DI)** DI是实现IoC的具体方式。容器通过构造函数、Setter方法或字段注入,动态满足对象的依赖需求。例如: ```java // 传统方式:主动创建依赖 UserService service = new UserService(new UserDaoImpl()); // IoC方式:声明依赖关系 @Autowired private UserDao userDao; // 容器自动注入 ``` #### 二、IoC容器实现原理 1. **容器工作流程** ```mermaid graph LR A[配置文件/注解] --> B[Bean定义加载] B --> C[BeanFactory初始化] C --> D[依赖解析] D --> E[Bean实例化] E --> F[依赖注入] F --> G[Bean就绪] ``` 2. **核心组件** - **BeanFactory**:基础容器,提供DI支持 - **ApplicationContext**:扩展容器(支持AOP、事件等) - **BeanDefinition**:存储Bean的元数据(类名、作用域、依赖等)[^4] 3. **关键步骤** - **加载配置**:解析XML/注解生成`BeanDefinition` - **实例化Bean**:通过反射调用构造函数创建对象 - **依赖注入**:递归解析依赖树并注入属性 - **生命周期管理**:调用初始化方法(`@PostConstruct`)和销毁回调 #### 三、依赖注入方式 | 注入方式 | 实现示例 | 特点 | |----------------|------------------------------|------------------------------| | **构造器注入** | `<bean><constructor-arg/></bean>` | 强依赖性,不可变对象 | | **Setter注入** | `<property name="dao" ref="userDao"/>` | 灵活性高 | | **注解注入** | `@Autowired private UserDao dao;` | 简化配置,主流方式 | #### 四、技术实现底层 1. **反射机制** 容器使用`Class.forName()`加载类,通过`newInstance()`动态创建对象: ```java Class<?> clazz = Class.forName("com.example.UserService"); UserService service = (UserService) clazz.newInstance(); ``` 2. **工厂模式** `BeanFactory`作为中央工厂,根据请求返回Bean实例,隐藏创建逻辑[^4]。 3. **设计模式应用** - **策略模式**:多种依赖注入方式 - **模板方法**:统一Bean生命周期处理 - **单例模式**:默认作用域的Bean管理 #### 五、IoC的优势 1. **解耦**:对象间依赖由容器管理,降低耦合度 2. **可测试性**:依赖可替换(如Mock对象注入) 3. **可维护性**:配置集中管理,修改无需编译代码 4. **扩展性**:通过接口注入实现组件替换 > **典型场景**:Web应用中Controller依赖Service,Service依赖Dao。容器自动构建完整对象树,开发者仅需关注业务逻辑[^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值