理解 Spring IOC

本文探讨了控制反转的IOC设计思想,如何通过Spring容器实现依赖注入,以及它在松耦合和代码复用中的作用。重点讲述了IOC与DI的关系,以及在大型项目中的实际应用案例和原理解析。

依赖注入是一种实现,而 IOC 是一种设计思想。

一、什么是 IOC

IOC—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。Java 中,IOC 意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

谁控制谁,控制什么:传统 Java SE 程序设计,直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象。而 IOC 是有专门一个容器来创建这些对象,即由 IOC 容器来控制对象的创建。

解读:
提到控制就要理解控制的含义,控制就是对象的创建、初始化、销毁

  1. 创建对象,原来是 new 一个,现在给 Spring 容器创建了。
  2. 对象初始化,比如 A 依赖 B,原来是通过构造器或者 setter 方法赋值,现在给 Spring 容器自动注入了。
  3. 销毁对象,原来是直接赋值 null 或者做一些销毁操作,现在给 Spring 容器管理生命周期负责销毁。

总之,IOC 解决了繁琐的对象生命周期的操作,解耦了代码。

为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由开发者在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象。为何是反转?因为由容器查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转。哪些方面反转了?依赖对象的获取被反转了。

解读:
反转的是什么?反转的是控制权。前面提到 Spring 控制了对象的生命周期,那么对象的控制就完全脱离了开发者的控制,交给了 Spring。这个反转是指,开发者由对象的控制者变成了 IOC 的被动接受者。开发者无法决定对象生命周期的任何一个阶段,最多是借助于 Spring 的扩展机制做一些微小的动作,甚至无法预判依赖的对象真正被注入的是哪一个。反转好比你家的机器人决定了你的生活节奏,必须听他的。

二、IOC 能做什么

IOC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导设计出松耦合、更优良的程序。把创建和查找依赖对象的控制权交给容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

解读:
借助 IOC 容器完美解决了耦合问题,甚至可以让八竿子打不着的类型产生注入关系。比如二方包 a 里面有个类型 A1,三方包 b 里面有个类型 B1,这两个都是 readOnly 的,你不可能去创建或修改内部的依赖。但是借助于 IOC,可以把自己的类 C1 注入进去,让 A1、B1 依赖于 C1,从而改变二方包的行为。这也是大型企业级开发中 IOC 比较常见的用法。

其实 IOC 对编程带来的最大改变不是从代码上,而是从思想上发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在 IOC/DI 思想中,应用程序就变成被动的了,被动的等待 IOC 容器来创建并注入它所需要的资源了。

解读:
在 IOC 模式下,设计流程变成了真正的无关业务完全独立的流程化设计。你只需要设计良好的流程和依赖,定义出需要什么,然后把控制权交给 Spring 即可。Spring 给你什么对象,你就用什么对象,不要对对象做任何的假设,也不要期待对象有什么特性,只需要等待 Spring 提供对象即可。控制反转就像乔布斯的理念,我告诉你你需要什么。

三、IOC 和 DI

DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

解读:
依赖注入是一种实现,而 IOC 是一种设计思想。从 IOC 到 DI,就是从理论到了实践。你把依赖交给了容器,容器帮你管理依赖,这就是依赖注入的核心。越是大型的项目,越难以管理依赖关系,开发工作逐渐变化为一个个节点的开发,而这些节点通过依赖注入关联起来。依赖注入降低了开发的成本、提高了代码的复用率、提高了软件的灵活性,也给软件开发带来了挑战,你根本不知道运行时容器会给你什么。

理解 DI 的关键是:“谁依赖谁?为什么需要依赖?谁注入谁?注入了什么”,深入分析一下:

  1. 谁依赖于谁:当然是应用程序依赖于 IOC 容器。
  2. 为什么需要依赖:应用程序需要 IOC 容器来提供对象需要的外部资源。
  3. 谁注入谁:很明显是 IOC 容器注入应用程序某个对象,应用程序依赖的对象。
  4. 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

解读:
DI 即依赖注入,重点就在于 “依赖”、“注入” 两个概念。什么是依赖?对象运行所需要的外部的数据、资源就是依赖,没有这些东西对象不能完成业务处理,必须拿到才能运行。什么是注入?注入这个词真的很形象,就像打针一样,从外部注入到内部,容器加载了外部的文件、URL、配置和对象然后把这些数据、对象按需注入给对象。

IOC 和 DI 有什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊,所以 2004 年大师级人物 Martin Fowler 又给出了一个新的名字:“依赖注入”,相对 IOC 而言,“依赖注入”明确描述了“被注入对象依赖 IOC 容器配置依赖对象”。

解读:
IOC 和 DI 是同一个概念的不同角度描述,但实际上又是有区别的。IOC 强调的是容器和对象的控制权发生了反转,而 DI 强调的是对象的依赖由容器进行注入,大部分情况下说两者相同也不算错。 但是广义上 IOC 是一种软件开发模式,也就是说还可以通过别的方式实现,而 DI 只是其中一种,Spring 选择了 DI,从而使 DI 在 Java 开发中深入人心。

四、IOC 原理

Spring 中的 IOC 的实现原理就是工厂模式反射机制

  1. 不使用反射机制的工厂模式
 public class ReflectFactory {
	/**
	 * 工厂模式     
	 */    
	interface fruit{      
		public abstract void eat();    
	}    
	class Apple implements fruit{      
		public void eat(){       
			System.out.println("Apple"); 
		}   
	}    
	class Orange implements fruit{    
		public void eat(){      
			System.out.println("Orange"); 
		}    
	} 
	// 构造工厂类    
	// 也就是说以后如果再添加其他的实例的时候只需要修改工厂类就行了   
	public fruit getInstance(String fruitName){     
		fruit f=null;         
		if("Apple".equals(fruitName)){     
			f=new Apple();        
		}          
		if("Orange".equals(fruitName)){   
			f=new Orange();     
		}
		return f; 
	}
	public static void main(String[] args){
		ReflectFactory rf = new ReflectFactory();
		fruit f=rf.getInstance("Orange"); 
		f.eat();     
	}
}

当再添加一个子类的时候,就需要修改工厂类了。如果添加太多的子类的时候,改的就会很多。

  1. 利用反射机制的工厂模式
public class ReflectFactory {
	interface fruit{    
		public abstract void eat(); 
	}   
	class Apple implements fruit{    
		public void eat(){      
			System.out.println("Apple");  
		}   
	}   
	class Orange implements fruit{  
		public void eat(){    
			System.out.println("Orange");   
		}   
	}  
	public static fruit getInstance(String ClassName){  
		fruit f=null;       
		try{         
			f=(fruit)Class.forName(ClassName).newInstance();   
		}catch (Exception e) {      
			e.printStackTrace();      
		}        return f;   
	}   
	public static void main(String[] args) {
		fruit f=ReflectFactory.getInstance("Reflect.Apple"); 
		if(f!=null){        
			f.eat();   
		}    
	}    
}

现在就算添加任意多个子类的时候,工厂类就不需要修改。

使用反射机制的工厂模式可以通过反射取得接口的实例,但是需要传入完整的包和类名。而且用户也无法知道一个接口有多少个可以使用的子类,所以通过属性文件的形式配置所需要的子类。

  1. 使用反射机制并结合属性文件的工厂模式(即IoC)

先创建一个fruit.properties的资源文件:

apple=Reflect.Apple    
orange=Reflect.Orange

然后编写主类代码:

public class ReflectFactory {

	interface fruit{  
		public abstract void eat();  
	}   
	class Apple implements fruit{   
		public void eat(){      
			System.out.println("Apple");  
		}    
	}   
	class Orange implements fruit{  
		public void eat(){     
			System.out.println("Orange"); 
		}  
	}  
	public static fruit getInstance(String ClassName){ 
		fruit f=null;     
		try{         
			f=(fruit)Class.forName(ClassName).newInstance(); 
		}catch (Exception e) {       
			e.printStackTrace();  
		}       
		return f;   
	}  
	public static void main(String[] args) throws FileNotFoundException, IOException{
		Properties pro=init.getPro();    
		fruit f=ReflectFactory.getInstance(pro.getProperty("apple"));  
		if(f!=null){        
			f.eat(); 
		}      
	}
}    
//[运行结果]:Apple

操作属性文件类:

public class init {
	public static Properties getPro() throws FileNotFoundException, IOException{  
		Properties pro=new Properties();    
		File f=new File("fruit.properties"); 
		if(f.exists()){          
			pro.load(new FileInputStream(f));  
		}else{           
			pro.setProperty("apple", "Reflect.Apple");   
			pro.setProperty("orange", "Reflect.Orange");   
			pro.store(new FileOutputStream(f), "FRUIT CLASS"); 
		}        return pro;    
	} 
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JFS_Study

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值