IOC 概念
IOC,本来是老外搞出来的一个词汇,人家英文的描述是 Inversion of Control,中文翻译呢是,控制反转。其实我觉得大多数人都是被这个词给坑了。
举个例子,有一个绑架案。说绑匪绑架了个一个千金小姐,需要小姐家人花钱赎身。在这个场景中,绑匪是控制着,控制着千金小姐的身家性命。但是呢,由于这个千金小姐,比较厉害,会武术。俗话说,女人会武术,神仙挡不住。那,咔咔,三下五除二,解决了绑匪。把所有的绑匪关入囚牢,等待官差的到来。那么,此时是不是控制发生了反转。由被控方,变成了控制方。整个过程,存在的交互,只是绑匪和千金小姐。在中国人的思维模式下,这种解释,才是真正的控制反转。
但,我们去了解 IOC 的实在内涵,完全不是这个意思。因此,建议大家忘记所谓的控制反转的翻译。个人觉得,可以理解为控制转移。就是绑匪失去了控制权。但是具体谁接受了控制权,有可能是小姐家人,有可能是官差,总归是第三方。
当然,后来出现的 DI(依赖注入)的解释,就是从另外一个层面去描述这个问题。我觉得理解下,我讲的上边的例子就够了。
IOC,就是将两个关联依赖的对象,不再由其中一个对象,完成另外对象的实例创建,而是,把这种创建,交付给第三方实现。这两个对象间,仅仅是交互即可,无需关心实例创建问题。维护的是,对象与对象间的关系,更符合自然世界中的对象含义。
蜜蜂采花蜜,应该是蜜蜂 、花,两个对象,采蜜的过程,是蜜蜂依赖花,完成动作。那么,肯定是,花已经在,蜜蜂来踩。不能蜜蜂自己种一朵花,然后到了开花时节再来采。
Java 是面向对象编程语言,要尽力的,以对象的语言,描述自然事物,这就是这个道理所在了。
IOC 方式
正如我们在概念里聊得,已经比较透彻的聊了,IOC 是什么,为什么产生这么一个方式。那么,具体的依赖注入,是如何实现的呢?
-
属性注入
-
构造器
-
setter 方法注入
-
接口实现注入 (不推荐)
注:针对 Spring 采用 xml 配置 bean 方式有同样的实现,只不过需要配置而已。
我们再此以 SpringBoot 为例说明,同时,我们以上边讲的蜜蜂采蜜这个来进行代码描述。
蜜蜂
@Component
public class Bee {
/**
* 性别
*/
private String sex;
/**
* 年龄
*/
private Integer age;
/**
* 采蜜
*/
public void pickingHoney() {
// 得到蜜
flower.getHoney();
}
// 省略属性 setter getter
}
花
@Component
public class Flower {
/**
* 颜色
*/
private String color;
/**
* 采蜜
*/
public void getHoney() {
//
}
// 省略属性 setter getter
}
两个普通对象,通过添加注解 @Component 很容易就成为了 bean,可以被 Spring 容易进行接管。
那么,蜜蜂,需要花的对象。如何得到,flower 呢,我们了解到采用 Spring 容器提供的 IOC 技术,那么我们就来描述下
属性注入
@Component
public class Bee {
/**
* 性别
*/
private String sex;
/**
* 年龄
*/
private Integer age;
@Autowired
private Flower flower;
/**
* 采蜜
*/
public void pickingHoney() {
// 得到蜜
flower.getHoney();
}
// 省略属性 setter getter
}
通过,注解 @Autowired,即可进行属性注入。
构造器注入
@Component
public class Bee {
/**
* 性别
*/
private String sex;
/**
* 年龄
*/
private Integer age;
private Flower flower;
@Autowired
public Bee(Flower flower) {
this.flower = flower;
}
/**
* 采蜜
*/
public void pickingHoney() {
// 得到蜜
flower.getHoney();
}
// 省略属性 setter getter
}
接口实现注入
接口实现,是通过实现接口,重写方法,进行实现,不推荐,此处不多说。
setter 方法注入
@Component
public class Bee {
/**
* 性别
*/
private String sex;
/**
* 年龄
*/
private Integer age;
private Flower flower;
@Autowired
public void setFlower(Flower flower) {
this.flower = flower;
}
/**
* 采蜜
*/
public void pickingHoney() {
// 得到蜜
flower.getHoney();
}
// 省略属性 setter getter
}
其中多种方式的优劣,可以查看公众号另外的文章 《SpringBoot 依赖字段注入三种方式》。
聊完,依赖注入,必不可少的,牵扯到高频面试题,依赖注入产生循环依赖,如何解决?那么期待,后续解读文章。用最简单的话,让你明白记住,原理,面试不再发愁。
JVM 类加载机制
聊完 IOC,那么我们不妨聊下,Spring 容器是如何把一个个的 bean 实例,进行加载的 JVM 中去的。
-
编译的 Class 文件
-
ZIP 包、Jar 包等
-
动态代理处理后的 Class
-
其他支持 java 语言文件生成,比如 JSP 文件
加载,完事后,就是一顿猛操作,进行在 JVM 中,处理,解析类对象是否符合规范等,解析类的内容。
再之后,初始化,开始执行类内代码。
除了,加载阶段,其他的都是在 JVM 控制下,处理。加载,是依靠类加载器。包括如下:
-
根类加载器
-
扩展类加载器
-
应用类加载器
-
自定义类加载器
根类加载器
根类加载器,是由 C++开发,不在 jvm 中,因此,在 jvm 中读取,都会是 null,属于所有类加载器的最根本的祖先级
扩展类加载器
Ext,用于读取 Jdk 中的 Ext 文件夹中的 jar 中的对象,比如 rt.jar
应用类加载器
用于,加载应用的类对象
自定义类加载器
用于,自定义实现,实现类的加载
双亲委派机制
委派,就是委托处理;双亲委派,就是所有的加载最终都是由其双亲执行加载操作。
那么,当存在两个版本的 jar,比如一个版本的功能是包含了最新的 a 操作,另一个版本没有。
那么,应用类加载器,虽然能发现最新的 jar,但是双亲,扩展类加载器,发现不了,就会发生,类找不到异常。
当然,使用双亲委派是有好处的。
采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object 对象。