点击上方Java极客技术,右上角选择“设为星标”
每天精彩原创文章,准时送上
后台回复“java”,获取Java知识体系/面试必看资料
吐血推荐
今天,正式介绍一下Java极客技术知识星球
SpringBoot 精髓之 SpringBoot-starter
跟我学spring security系列文章第一章 实现一个基本的登入
Spring 源码分析:不得不重视的 Transaction 事务
我们谈谈面试技巧(初入职场年轻人该学的)
很长一段时间里,我对控制反转和依赖注入这两个概念很模糊,闭上眼睛想一想,总有一种眩晕的感觉。但为了成为一名优秀的 Java 工程师,我花了一周的时间,彻底把它们搞清楚了。
在我们编码的过程中,通常都需要两个或者更多的类通过彼此的合作来实现业务逻辑,也就是说,某个对象需要获取与其合作对象的引用,如果这个获取的过程需要自己实现,代码的耦合度就会高,维护起来的成本就比较高。
我们来通过实战模拟一下。假如老王是少林寺的主持,他想让小二和尚去扫达摩院的地,代码可以这样实现。
小二类的代码如下所示:
public class Xiaoer { public void saodi() { System.out.println("小二我在扫达摩院的地"); }}class Xiaoer {
public void saodi() {
System.out.println("小二我在扫达摩院的地");
}
}
老王类的代码如下所示:
public class Laowang { public void mingling() { new Xiaoer().saodi(); }}class Laowang {
public void mingling() {
new Xiaoer().saodi();
}
}
测试类的代码如下所示:
public class Test { public static void main(String[] args) { Laowang laowang = new Laowang(); laowang.mingling(); }}class Test {
public static void main(String[] args) {
Laowang laowang = new Laowang();
laowang.mingling();
}
}
Laowang 类的 mingling 方法中使用 new 关键字创建了一个 Xiaoer 类的对象——这种代码的耦合度就很高,维护起来的成本就很高,为什么这么说呢?
某一天,达摩院的地又脏了,老王主持想起了小二和尚,可小二和尚去练易筋经了,让谁去扫地呢,老王主持想起了小三和尚,于是 Laowang 类就不得不重新下一个新的命令,于是代码变成了这样:
public class Xiaosan { public void saodi() { System.out.println("小三我在扫达摩院的地"); }}public class Laowang { public void mingling() { new Xiaoer().saodi(); } public void mingling1() { new Xiaosan().saodi(); }}class Xiaosan {
public void saodi() {
System.out.println("小三我在扫达摩院的地");
}
}
public class Laowang {
public void mingling() {
new Xiaoer().saodi();
}
public void mingling1() {
new Xiaosan().saodi();
}
}
假如小三和尚去挑水了,老王主持没准要下命令给小四和尚去扫达摩院的地。这样下去的话,Laowang 这个类会疯掉的。
老王主持觉得自己堂堂一届高僧,下个扫地的命令竟然这样麻烦,他觉得很不爽。
我们得替老王主持想个办法对不对?
不如把这个扫地的差事交给老王的师弟老方吧,老方负责去叫小二和尚还是小三和尚还是小四和尚去执行老王主持的命令。代码可以这样实现。
定义一个扫地和尚的接口,代码如下所示:
public interface Heshang { void saodi();}interface Heshang {
void saodi();
}
小二类的代码修改如下所示:
public class Xiaoer implements Heshang { @Override public void saodi() { System.out.println("小二我在扫达摩院的地"); } public boolean isYijinjing() { // 星期三的时候小二和尚要练易筋经 return false; }}class Xiaoer implements Heshang {
@Override
public void saodi() {
System.out.println("小二我在扫达摩院的地");
}
public boolean isYijinjing() {
// 星期三的时候小二和尚要练易筋经
return false;
}
}
小三类的代码修改如下所示:
public class Xiaosan implements Heshang { @Override public void saodi() { System.out.println("小三我在扫达摩院的地"); }}class Xiaosan implements Heshang {
@Override
public void saodi() {
System.out.println("小三我在扫达摩院的地");
}
}
老方类的代码如下所示:
public class Laofang { public static Heshang getSaodiseng() { Xiaoer xiaoer = new Xiaoer(); if (xiaoer.isYijinjing()) { return new Xiaosan(); } return xiaoer; }}class Laofang {
public static Heshang getSaodiseng() {
Xiaoer xiaoer = new Xiaoer();
if (xiaoer.isYijinjing()) {
return new Xiaosan();
}
return xiaoer;
}
}
如果老方确认小二和尚在练易筋经,就叫小三和尚。
老王类的代码修改如下所示:
public class Laowang { public void mingling() { Laofang.getSaodiseng().saodi(); }}class Laowang {
public void mingling() {
Laofang.getSaodiseng().saodi();
}
}
测试类的代码不改变,如下所示:
public class Test { public static void main(String[] args) { Laowang laowang = new Laowang(); laowang.mingling(); }}class Test {
public static void main(String[] args) {
Laowang laowang = new Laowang();
laowang.mingling();
}
}
老王现在是不是省心多了,他只管下命令,该叫谁去扫达摩院的地由他师弟老方去负责。
我们替老王想的这个办法就叫控制反转(Inversion of Control,缩写为 IoC),它不是一种技术,而是一种思想——指导我们设计出松耦合的程序。
控制反转从词义上可以拆分为“控制”和“反转”,说到控制,就必须找出主语和宾语,谁控制了谁;说到反转,就必须知道正转是什么。
你看,在紧耦合的情况下,老王下命令的时候自己要通过 new 关键字创建依赖的对象(小二和尚或者小三和尚);而控制反转后,老王要找的扫地和尚由他师弟老方负责,也就是说控制权交给了老方,是不是反转了呢?
依赖注入(Dependency Injection,简称 DI)是实现控制反转的主要方式:在类 A 的实例创建过程中就创建了依赖的 B 对象,通过类型或名称来判断将不同的对象注入到不同的属性中。大概有 3 种具体的实现形式:
1)基于构造函数。实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。
老王类的代码修改如下所示:
public class Laowang { private Heshang saodiseng; public Laowang(Heshang saodiseng) { this.saodiseng = saodiseng; } public void mingling() { this.saodiseng.saodi(); }}class Laowang {
private Heshang saodiseng;
public Laowang(Heshang saodiseng) {
this.saodiseng = saodiseng;
}
public void mingling() {
this.saodiseng.saodi();
}
}
测试类的代码修改如下所示:
public class Test { public static void main(String[] args) { Laowang laowang = new Laowang(new Xiaosan()); laowang.mingling(); }}class Test {
public static void main(String[] args) {
Laowang laowang = new Laowang(new Xiaosan());
laowang.mingling();
}
}
这时候,控制权掌握在测试类的手里,它决定派小二和尚还是小三和尚去执行老王的扫地命令。
2)基于 set 方法。实现特定属性的 public set 方法,让外部容器调用传入所依赖类型的对象。
老王类的代码修改如下所示:
public class Laowang { private Heshang saodiseng; public Heshang getSaodiseng() { return saodiseng; } public void setSaodiseng(Heshang saodiseng) { this.saodiseng = saodiseng; } public void mingling() { this.getSaodiseng().saodi(); }}class Laowang {
private Heshang saodiseng;
public Heshang getSaodiseng() {
return saodiseng;
}
public void setSaodiseng(Heshang saodiseng) {
this.saodiseng = saodiseng;
}
public void mingling() {
this.getSaodiseng().saodi();
}
}
测试类的代码修改如下所示:
public class Test { public static void main(String[] args) { Laowang laowang = new Laowang(); Xiaosan xiaosan = new Xiaosan(); laowang.setSaodiseng(xiaosan); laowang.mingling(); }}class Test {
public static void main(String[] args) {
Laowang laowang = new Laowang();
Xiaosan xiaosan = new Xiaosan();
laowang.setSaodiseng(xiaosan);
laowang.mingling();
}
}
这时候,控制权仍然掌握在测试类的手里,它决定派小二和尚还是小三和尚去执行老王的扫地命令。
3)基于接口。实现特定接口以供外部容器注入所依赖类型的对象,这种做法比较构造函数和 set 方法更为复杂,这里就此略过。
可能有人会把控制反转等同于依赖注入,但实际上它们有着本质上的不同:控制反转是一种思想,而依赖注入是实现控制反转的一种形式。
当我们搞清楚控制反转和依赖注入的概念后,就可以顺带了解一下大名鼎鼎的 Spring 框架。控制反转是 Spring 框架的核心,贯穿始终。Spring 中依赖注入有两种实现方式:set 方式(传值方式)和构造器方式(引用方式)。
首先,我们需要在 pom.xml 文件中加入 Spring 的依赖项,代码如下所示:
org.springframework spring-context-support 4.3.2.RELEASE
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-supportartifactId>
<version>4.3.2.RELEASEversion>
dependency>
其次,我们将 Laowang 类修改为如下内容:
public class Laowang { private Heshang saodiseng; public Laowang(Heshang saodiseng) { this.saodiseng = saodiseng; } public void mingling() { this.saodiseng.saodi(); }}class Laowang {
private Heshang saodiseng;
public Laowang(Heshang saodiseng) {
this.saodiseng = saodiseng;
}
public void mingling() {
this.saodiseng.saodi();
}
}
然后,我们创建一个 Spring 的配置文件 application.xml,内容如下所示:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="laowang" class="com.cmower.java_demo.ioc.Laowang">
<constructor-arg ref="saodiseng" />
bean>
<bean id="saodiseng" class="com.cmower.java_demo.ioc.Xiaosan" />
beans>
通过元素配置了两个对象,一个老王主持,一个小三和尚,使用 元素将小三和尚作为老王主持的构造参数。
准备工作完成以后,我们来测试一下,代码示例如下:
public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); Laowang laowang = (Laowang) context.getBean("laowang"); laowang.mingling(); }}class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Laowang laowang = (Laowang) context.getBean("laowang");
laowang.mingling();
}
}
你看,我们将控制权交给了 IoC 框架 Spring,这样也可以完美的解决代码耦合度较紧的问题。
总结一下:
1)控制反转是一种在软件工程中解耦合的思想,把控制权交给了第三方,在运行的时候由第三方决定将具体的依赖对象“注入”到调用类的对象中。
2)依赖注入可以作为控制反转的一种实现方式,将实例变量传入到一个对象中去。
3)通过 IoC 框架,类 A 依赖类 B 的强耦合关系可以在运行时通过容器建立,也就是说把创建 B 实例的工作移交给容器,类 A 只管使用就可以。
End
如有收获,请帮忙转发,您的鼓励是作者最大的动力!
我是本周的小编「沉默王二」,现在要隆重地告诉大家一个好消息,对于加入「Java极客技术」知识星球的同学提供基本的福利:
文章有疑问的地方可以提问,其他工作问题都可以提问出来,作者免费作答。
https://t.zsxq.com/Y3fYny7
每周都有大牛分享一些面试题,和面试注意的知识点!
https://t.zsxq.com/2bufE2v
每周由Java极客技术独家编制的设计模式与大家分享!
https://t.zsxq.com/3bUNbEI
每两周还会分享一个话题,和大家一起成长!
https://t.zsxq.com/BI6Unm2
还有Java极客技术团队亲自录制了一套 Spring Boot 视频,这套视频加密,加密后放到云盘上,下载链接加密之后,一机一码,每个星球的用户一个播放授权码。
我们做知识星球的目的和其他星主一样,就是为了帮助大家一起更好的成长,与高手拉近距离,减少差距,其实你也是高手!
前1000人,50元/每年,现在大约还剩407名额。
长按二维码
欢迎长按下图关注公众号Java极客技术,后台回复“资料”,获取作者独家秘制精品资料
Java 极客技术公众号,是由一群热爱 Java 开发的技术人组建成立,专注分享原创、高质量的 Java 文章。如果您觉得我们的文章还不错,请帮忙赞赏、在看、转发支持,鼓励我们分享出更好的文章。