理解 Spring IOC
一、IOC是什么?
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建。
解读:
提到控制就要理解控制的含义,控制就是对象的创建、初始化、销毁。创建对象,原来是 new 一个,现在给 Spring 容器创建了;对象初始化,比如 A 依赖 B,原来是我们通过构造器或者 setter 方法赋值,现在给 Spring 容器自动注入了;销毁对象,原来是我们直接赋值 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的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
谁依赖于谁:当然是应用程序依赖于IoC容器;
为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
解读:
DI 即依赖注入,重点就在于 “依赖”、“注入” 两个概念。什么是依赖?对象运行所需要的外部的数据、资源就是依赖,没有这些东西对象不能完成业务处理,必须拿到才能运行。什么是注入?注入这个词真的很形象,就像打针一样,从外部注入到内部,容器加载了外部的文件、URL、配置和对象然后把这些数据、对象按需注入给对象。
IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊,所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。
解读:
IOC 和 DI 是同一个概念的不同角度描述,但实际上又是有区别的。IOC 强调的是容器和对象的控制权发生了反转,而 DI 强调的是对象的依赖由容器进行注入,大部分情况下说两者相同也不算错。 但是广义上 IOC 是一种软件开发模式,也就是说还可以通过别的方式实现,而 DI 只是其中一种,Spring 选择了 DI 从而使 DI 在 Java 开发中深入人心。
四、IoC原理
Spring中的IoC的实现原理就是工厂模式加反射机制。
- 不使用反射机制的工厂模式
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();
}
}
当我们再添加一个子类的时候,就需要修改工厂类了。如果我们添加太多的子类的时候,改的就会很多。
- 利用反射机制的工厂模式
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();
}
}
}
现在就算我们添加任意多个子类的时候,工厂类就不需要修改。
使用反射机制的工厂模式可以通过反射取得接口的实例,但是需要传入完整的包和类名。而且用户也无法知道一个接口有多少个可以使用的子类,所以我们通过属性文件的形式配置所需要的子类。
- 使用反射机制并结合属性文件的工厂模式(即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;
}
}