Java依赖注入(Dependency Injection,DI )
引言
一个老板雇了一个程序员,为他编写一个看电影的方法。
老板最开始家境贫寒,只有走路去,程序员将方法实现为
public void watchMovie(){
Ststem.out.println("go to cinema on foot;" );
}
突然有一天,老板一夜暴富,买了一辆车,那么以后就可以开车去看电影了,程序员将方法实现改为
public void watchMovie(){
Ststem.out.println("go to cinema by car;" );
}
又过了一段时间,老板发现身边潮流变成了自行车出行,要求程序员继续修改,程序员又将方法实现改为
public void watchMovie(){
Ststem.out.println("go to cinema by bike;" );
}
长此以往,每次都要改,程序员觉得实在太麻烦了,既然刁老板这么爱改,怎么去电影院干脆不写死了,留给老板定。这在写方法的时候很容易想到解决方案:定义一个形参,通过传参的方法确定去电影院的方式
public void watchMovie(String transportation){
Ststem.out.println("go to cinema " + transportation);
}
如此一来,以后就不用每次修改代码了
依赖
一个类依赖于另一个类的定义
举例
在SpringBoot中,Controller和ServiceImpl之间就是一种依赖关系,前者需要使用后者提供的服务
假如没有依赖注入,Controller想要使用ServiceImpl最简单直接的方式是new一个实例
class PeopleController{
PeopleServiceImpl p = new PeopleServiceImpl;
p.watchMovie();
}
class PeopleServiceImpl{
public void watchMovie(){
Ststem.out.println("go to cinema by car;" );
}
}
问题
那么问题来了,就像引言里提到的那样,看电影可以有多种实现方式,开车、骑车等等,根据单一职责原则,这些不同的方法实现应该写在不同的PeopleServiceImpl类里,按照上面的写法,Controller每次选择不同的出行方式都要实例化一个新的类,这就需要修改PeopleController里的源代码。
这个问题该如何解决呢?
依赖注入
同样的,跟引言里设置形参的思路相同,在PeopleController里也不把实例化哪个类直接定死,而是交给外部决定。或者说把实例化依赖的方式更改为外部注入依赖。(这也是实现控制反转IoC的重要方式,将对类的控制交给外部,也就是Spring容器),这就是依赖注入
具体实现
依赖注入有三种常见的方式,在介绍这三种方式之前,首先介绍另一个遗留的问题。
上述包含看电影方法的不同类都只有一个看电影方法,根据面向接口编程的原则,首先可以抽象出一个PeopleService接口,它提供了看电影的声明,以后想要添加别的实现方式只用实现这个接口就行了。
@Service
interface PeopleService{
public void watchMovie();
}
这个接口也是后续实现依赖注入必不可少的组成。因为在上面的看电影方法设置形参时,我们默认交通工具是一种String。但在依赖注入时,由于不知道将来会将哪个类注入进Controller,所以无法在形参中确定数据类型。有了这个接口之后,就可以将数据类型统一声明为PeopleService。因为实现了这个接口的类都可以用PeopleService声明
基于构造器
class PeopleController{
private PeopleService p;
PeopleController(PeopleService peopleService){
this.p = peopleService;
}
}
基于setter的依赖注入
class PeopleController{
private PeopleService p;
public void setService(PeopleService peopleService){
this.p = peopleService;
}
}
基于field的依赖注入
使用 @Autowired注解进行依赖注入
public class PeopleController {
@Autowired
private PeopleService p;
}
这种方法看起来更加简单,但不推荐使用,因为有下列缺陷
- 该方法使用反射来注入依赖项,这比基于构造函数或基于 setter 的注入成本更高。
- 使用这种方法继续添加多个依赖项真的很容易。如果我们使用构造函数注入,有多个参数会让我们认为类做不止一件事,这可能违反单一职责原则
- 当同一个接口有多个实现时,需要进一步配置
扩展阅读JavaGuide
-
Autowired 属于 Spring 内置的注解,默认的注入方式为byType(根据类型进行匹配),也就是说会优先根据接口类型去匹配并注入 Bean (接口的实现类)。
-
这会有什么问题呢? 当一个接口存在多个实现类的话,byType这种方式就无法正确注入对象了,因为这个时候 Spring 会同时找到多个满足条件的选择,默认情况下它自己不知道选择哪一个。
-
这种情况下,注入方式会变为 byName(根据名称进行匹配),这个名称通常就是类名(首字母小写)。就比如说下面代码中的 smsService 就是我这里所说的名称,这样应该比较好理解了吧。