什么是依赖注入(DI, Dependency Injection)?
之前写Web一直是用Python+Django,短暂得写过一小段时间的Java,那时的Java Web真的是很麻烦,需要各种各样的配置,一会儿是注解,一会儿又是XML,写一个CRUD要先后在好几个不同的文件里写各种配置,有DAO,有TAO,还有XML,还有Entity,总之是非常复杂。相比之下,Django就方便的多,基本上是零配置,只需要专心写业务逻辑就好。最近又开始看了Java Web相关的Spring Boot 2.0,感觉好像好了很多,很多之前的麻烦东西都不存在了,越来越像Django了。
这次重拾Spring,又遇到了当年也没有搞明白的依赖注入(Dependency Injecting),借着这次机会,终于是搞懂了。
首先,依赖注入(Dependency Injecting)是一种保持代码解耦的程序设计方式。在正式理解依赖注入之前,需要先搞明白两个概念,一个是client(管控某些流程、业务逻辑),一个是service(提供某种服务、功能)。依赖注入,说的就是把client所依赖的service,注入到client中。下面我们举例说明:
public class AK47 {
public void fire() {
System.out.println("Fire!!!!")
}
}
public class Soldier {
private AK47 ak47;
Soldier() {
this.ak47 = new AK47();
}
void fire() {
this.ak47.fire()
}
public static void main(String[] args) {
Soldier soldier = new Soldier();
soldier.fire();
}
}
上面的脚本中,我们的client就是Soldier,它依赖于AK47,然后它就在自己内部new一个AK47,然后在fire里面使用。这是没有依赖注入的写法,这样写的问题是当后期士兵的武器升级之后,武器的代码肯定得改,但是逻辑上讲士兵是不需要改的,但是按照我们上面的写法,士兵也得改,这就是两个类之间耦合度太高,需要解耦一下。
public interface Weapon {
public void fire();
}
public class AK47 implements Weapon {
public void fire() {
System.out.println("Fire!!!!")
}
}
public class Soldier {
private Weapon weapon;
Soldier(Weapon weapon1) {
this.weapon = weapon1;
}
void fire() {
this.weapon.fire()
}
public static void main(String[] args) {
Weapon ak47 = new AK47();
Soldier soldier = new Soldier(ak47);
soldier.fire();
Weapon m1 = new M1();
Soldier soldier2 = new Soldier(m1);
soldier2.fire();
}
}
在新的方法中,我们把武器抽象出来了一个接口,然后在士兵初始化的时候传入武器,这样都有武器更新换代的时候,士兵的代码是不用动的,这样就实现了一定程度的士兵与武器的解耦。注意,这时,依赖(武器)就是通过Client(士兵)的构造方法被注入到Client中的,这就是一种依赖注入的方式。
上面这样通过构造方法注入的方式,有一定的缺点,那就是一旦Client创建好,就无法再更改了,于是还有一种通过setter方法进行注入,如下所示:
public interface Weapon {
public void fire();
}
public class AK47 implements Weapon {
public void fire() {
System.out.println("Fire!!!!")
}
}
public class Soldier {
private Weapon weapon;
Soldier() {
}
void setWeapon(Weapon weapon1) {
this.weapon = weapon1;
}
void fire() {
this.weapon.fire()
}
public static void main(String[] args) {
Weapon ak47 = new AK47();
Soldier soldier = new Soldier();
soldier.setWeapon(ak47);
soldier.fire();
}
}
上面的代码中,具体的实现一直在变,但是main方法一直其实没啥大的变化,在实际的工程项目中,Spring之类的框架,就是用来完成本文中main方法所做的事儿的,它们负责new各种个样的实例(术语Bean),然后按照一定的指引(XML或者是注解),讲service功能的Bean注入到client功能的Bean中。所以Spring这类的框架一般都叫做Injector,其与代码的关系,可以简单的用下图来展示:
