依赖注入,看起来好像是一个高大上的词语。不过事实上,依赖注入也应用到了我们日常开发中的很多很多地方。可以说,依赖注入(Dependency Injection)是一个很巧妙的思想,今天就来简单地做一个总结。
1,依赖注入的三种方式
在Java中,通常有三种依赖注入的方式:
- 构造器注入
- setter注入
- 接口注入
通常来说,构造器注入和setter注入可以说是最常用的方式,而接口注入是从别的地方注入。先主要来讲解构造器注入和setter注入。
在此之前,我们先新建一个电脑类和打印机类作为例子讲解,如下:
打印机类:
packagecom.example.di.model; /*** 打印机类*/publicclassPrinter { /*** 打印机的打印方法** @param content 打印内容*/publicvoidprint(Stringcontent) { System.out.println("打印了:"+content); } }
电脑类:
packagecom.example.di.model; /*** 电脑类*/publicclassComputer { /*** 电脑的打印机*/privatePrinterprinter; // 省略getter和setter}
可见,电脑类中有一个打印机类字段,这说明组成电脑类中有打印机类成员,那么很显然:打印机是电脑的依赖。
大家一定先要理解依赖这个词。
也很显然,电脑类中的打印机还没有实例化,如果只实例化电脑,其打印机还是用不了的。
那有的同学把电脑类改装如下:
packagecom.example.di.model; /*** 电脑类*/publicclassComputer { /*** 电脑的打印机*/privatePrinterprinter=newPrinter(); }
这样显然是不行的,基本上好像也没有人这么写。在电脑类中实例化打印机实例,这会导致电脑类和打印机类高度耦合,不利于扩展。譬如说现在打印机只是一个接口或者是抽象类,接口(抽象类)的实现类有彩色打印机类和黑白打印机类,结果不同电脑实例要用不同的打印机,那显然就不能再电脑类里面先实例化打印机了。
因此我们之所以使用依赖注入,是为了减少依赖性,增强可重用性,增加可读性、可扩展性等等。
(1) 构造器注入
在电脑类中写一个带参构造函数,用于给其字段打印机赋值:
/*** 电脑类带参构造器* @param printer 传入打印机实例*/publicComputer(Printerprinter) { // 构造器注入this.printer=printer; }
然后我们在主方法中,实例化电脑类,并注入打印机实例试试:
Printerprinter=newPrinter(); // 实例化电脑类,通过构造器注入了打印机实例(依赖)Computercomputer=newComputer(printer); computer.getPrinter().print("构造器注入");
结果:
利用构造器传参,赋值,这就完成了构造器注入,很简单。
可见依赖注入也很好理解:不是让一个对象自己生成或者创造自己的依赖而是从外部注入。
(2) setter注入
在讲这个方法之前,请好好看看你的setter
方法,相信你已经对它再熟悉不过了:
publicvoidsetPrinter(Printerprinter) { this.printer=printer; }
没错,我们平时创建类,离不开getter
和setter
,那么其实setter
就是利用了依赖注入的思想。
利用专门的一个setter
方法实现依赖注入,就是setter
注入的方式,也可以说是方法注入。
我们再在主方法中,来实例化一下试试:
Printerprinter=newPrinter(); // 实例化电脑类Computercomputer=newComputer(); // 通过setter方法注入了打印机实例(依赖)computer.setPrinter(printer); computer.getPrinter().print("setter注入");
到此,相信大家知道依赖注入到底是什么了。通俗地讲,依赖注入就是在实例化一个类对象的时候不要同时把其中的依赖也给在类的内部实例化了,而是要先实例化这个类,再在外部实例化其依赖,然后把依赖通过构造器或者setter
方法传入进去。
2,Spring中的自动装配
相信大家无论是开发Spring
还是Spring Boot
工程,对@Autowired
这个注解已经再熟悉不过了,这其实就是用注解的方式实现了依赖注入。
只不过,利用这个注解,Spring框架就会自动帮你实例化对应的对象赋值帮你完成注入这个过程。例如上面在实例化电脑类时我们还要实例化它的依赖,也就是打印机类,最后再注入。而在Spring中利用@Autowired
注解,就不需要我们去手动实例化依赖了,框架会帮我们实例化好。这就是自动装配。
还是以一个例子开始,这里定义了一个服务类接口CharacterService
及其对应的实现类CharacterServiceImpl
,这个服务类可以调用MyBatis DAO查询数据库中所有的游戏角色数据。几个类代码以及Mapper XML如下:
DAO:
packagecom.example.firstssm.dao; importcom.example.firstssm.dataobject.Character; importorg.apache.ibatis.annotations.Mapper; importjava.util.List; publicinterfaceCharacterDAO { List<Character>getAll(); }
Mapper XML:
<mappernamespace="com.example.firstssm.dao.CharacterDAO"><resultMapid="characterResultMap"type="com.example.firstssm.dataobject.Character"><idcolumn="id"property="id"/><resultcolumn="name"property="name"/><resultcolumn="nickname"property="nickname"/><resultcolumn="type"property="type"/><resultcolumn="guild"property="guild"/><resultcolumn="gmt_created"property="gmtCreated"/><resultcolumn="gmt_modified"property="gmtModified"/></resultMap><selectid="getAll"resultMap="characterResultMap"> select * from `character` </select></mapper>
服务类接口:
packagecom.example.firstssm.service; importcom.example.firstssm.dataobject.Character; importorg.springframework.stereotype.Service; importjava.util.List; publicinterfaceCharacterService { /*** 查询全部角色** @return 全部角色列表*/List<Character>queryAll(); }
实现类:
packagecom.example.firstssm.service.impl; importcom.example.firstssm.dao.CharacterDAO; importcom.example.firstssm.dataobject.Character; importcom.example.firstssm.service.CharacterService; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.stereotype.Component; importjava.util.List; publicclassCharacterServiceImplimplementsCharacterService { privateCharacterDAOcharacterDAO; publicList<Character>queryAll() { returncharacterDAO.getAll(); } }
角色实体类:
packagecom.example.firstssm.dataobject; importlombok.Getter; importlombok.NoArgsConstructor; importlombok.Setter; importjava.io.Serializable; importjava.time.LocalDateTime; publicclassCharacterimplementsSerializable { /*** 主键id*/privateintid; /*** 角色名*/privateStringname; /*** 外号*/privateStringnickname; /*** 角色定位*/privateStringtype; /*** 公会*/privateStringguild; /*** 创建时间*/privateLocalDateTimegmtCreated; /*** 修改时间*/privateLocalDateTimegmtModified; publicStringtoString() { return"主键id:"+id+" 角色名:"+name+" 角色外号:"+nickname+" 角色定位:"+type+" 角色公会:"+guild; } }
同样,@Autowired
也有如下的注入方式。
(1) 字段注入
这应该是我们实际开发很常见的搞法了,我们创建一个测试类试一试:
packagecom.example.firstssm; importcom.example.firstssm.dataobject.Character; importcom.example.firstssm.service.CharacterService; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.stereotype.Component; importjavax.annotation.PostConstruct; importjava.util.List; publicclassTest { // 字段注入privateCharacterServicecharacterService; publicvoidinitTest() { // 调用一下试试List<Character>characterList=characterService.queryAll(); for (Charactercharacter : characterList) { System.out.println(character); } } }
结果:
可见,我们并没有手动new
一个实例赋值给字段characterService
,但是我们仍然可以在这里正常使用它,因为我们对这个字段标注了自动装配,那么启动的时候,框架就会去找CharacterService
的实现类,并自动实例化一个对应的实例赋值给这个字段,就实现了依赖注入。
(2) 构造器注入
@Autowired
还可以标注在构造函数上面或者构造函数中的参数上:
packagecom.example.firstssm; importcom.example.firstssm.dataobject.Character; importcom.example.firstssm.service.CharacterService; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.stereotype.Component; importjavax.annotation.PostConstruct; importjava.util.List; publicclassTest { privateCharacterServicecharacterService; // 标注在构造函数上publicTest(CharacterServicecharacterService) { this.characterService=characterService; } publicvoidinitTest() { // 调用一下试试List<Character>characterList=characterService.queryAll(); for (Charactercharacter : characterList) { System.out.println(character); } } }
还可以:
packagecom.example.firstssm; importcom.example.firstssm.dataobject.Character; importcom.example.firstssm.service.CharacterService; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.stereotype.Component; importjavax.annotation.PostConstruct; importjava.util.List; publicclassTest { privateCharacterServicecharacterService; // 标注在构造函数参数上publicTest(CharacterServicecharacterService) { this.characterService=characterService; } publicvoidinitTest() { // 调用一下试试List<Character>characterList=characterService.queryAll(); for (Charactercharacter : characterList) { System.out.println(character); } } }
运行,效果一样。
这样,就通过构造器完成自动装配,这就是Spring中构造器注入方式。
(3) setter注入
同样,自动装配也可以标注在setter方法上:
packagecom.example.firstssm; importcom.example.firstssm.dataobject.Character; importcom.example.firstssm.service.CharacterService; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.stereotype.Component; importjavax.annotation.PostConstruct; importjava.util.List; publicclassTest { privateCharacterServicecharacterService; // 方法注入publicvoidsetCharacterService(CharacterServicecharacterService) { this.characterService=characterService; } publicvoidinitTest() { // 调用一下试试List<Character>characterList=characterService.queryAll(); for (Charactercharacter : characterList) { System.out.println(character); } } }
3,总结
可见依赖注入并不难,依赖注入的思想,也贯穿于我们日常的开发中。不过大家至少还是要熟悉几种常见方式。在Spring框架中,也可见依赖注入更加方便,不需要我们实例化依赖,自动装配就能够搞定。