1.背景
刚开始接触 spring ,一定不会少看到 IoC、DI 这两个单词。
先把 spring 撇开,单独看看 IoC 和 DI 是什么意思呢?
找两个定义:
IoC,全称 Inverse of Control,控制反转。是指,由容器/第三方系统(例如 spring )来控制业务对象之间的依赖关系,而不是像传统方式中由代码来直接控制。
DI,全称 Dependency Injection,依赖注入。是指,自身对象中的内置对象是通过注入的方式进行创建。
呃,看完了,似懂非懂。。。
2.举个“吃饭”的例子
场景:在沙县小吃/学校食堂吃饭。
沙县小吃:
- 顾客到店:老板,我要一份炒米线。
- 老板:好嘞,这就给你做。
学校食堂:
- 学生到食堂:阿姨,我要这个西红柿炒蛋,加4两米饭。
- 食堂阿姨:好嘞,这就给你装盘。
区别在哪呢?
- “沙县小吃”是由顾客到店后主动要求“我要吃什么”,然后餐馆才去 new 一个你要吃的菜。
- “学校食堂”是由食堂提前 new 很多可以提供给顾客吃的菜,顾客来了之后直接挑选装盘就可以了。
3.再举个例子
场景:接口参数检查
假设,我们的后台服务拆两个类:MyService 和 Validator 。很明显,MyService 依赖 Validator。
考虑一下, 最简单的实现方式是什么呢?
3.1 传统实现,new
简单介绍一下用到的类和接口:
-
IValidate.java
,接口定义了 validate() 方法,对一个 String 类型参数做检查。 -
NotnullValidator.java
实现了 IValidate 接口的validate()方法,要求参数不能为空。 -
MyService.java
,定义了一个Serve() 方法,用来接收外部参数,然后对参数做非空检查。 -
Client.java
,模拟调用方,请求MyService。
代码示例:
// IValidator.java
interface IValidator.java {
public void validate(String param);
}
// NotnullValidator.java
class NotnullValidator implements IValidator{
@Override
public void validate(String param){
System.out.println(this.getClass().getSimpleName()+ " validating param {"+param+"}");
if(null == param || 0 == param.length()){
System.out.println(this.getClass().getSimpleName() + " param can not be null or empty");
}
}
}
// MyService.java
class MyService.java {
void Serve(String param){
System.out.println(this.getClass().getSimpleName() + " param = " + param);
NotnullValidator notnullValidator = new NotnullValidator();
notnullValidator.validate(param);
}
}
// Client.java
public class Client {
public static void main(String[] args){
MyService myService = new MyService();
myService.Serve("");
myService.Serve("123");
/**
* 这样做的弊端是,依赖隐藏在函数内部,模块化、可用性、扩展性、易测性都不好。
*/
}
}
在这个例子中,MyService 直接在 Serve() 方法内部 new 了一个 NotNullValidator 实例,进行参数检查。
这样做的问题在于:
- 扩展性差,如果需要其他校验器,就要在 MyService 内部做大调整了。
- 依赖管理困难,IValidator 实例的生命周期在 MyService 之内,无法管理。
- 可测性差,耦合太严重,依赖和行为完全堆到一起去了,没法做单元测试。
那么,有什么解决方法呢?
很简单,把 new NotNullValidator 这个操作拿出来就可以了。
3.2 构造子注入
同样的例子,我们可以在 MyService 内部定义一个 IValidator 成员变量,然后再 定义一个 MyService 的带参构造函数。
代码示例:
// IValidator.java
interface IValidator {
public void validate(String param);
}
// NotnullValidator.java
class NotnullValidator implements IValidator{
@Override
public void validate(String param){
System.out.println(this.getClass().getSimpleName()+ " validating param {"+param+"}");
if(null == param || 0 == param.length()){
System.out.println(this.getClass().getSimpleName() + " param can not be null or empty");
}
}
}
// MyService.java
class MyService {
private IValidator validator;
MyService(IValidator validator) {
System.out.println("构造子注入");
this.validator = validator;
}
void Serve(String param){
System.out.println(this.getClass().getSimpleName() + " param = " + param);
validator.validate(param);
}
}
// Client.java
public class Client {
public static void main(String[] args){
NotnullValidator notnullValidator = new NotnullValidator();
MyService myService = new MyService(notnullValidator);
myService.Serve("");
myService.Serve("123");
/**
* 依赖由外部管理,可测性变强了。
* 这其实就是一种依赖注入方法。
* 通过构造方法注入依赖,叫做"构造子注入"。
*/
}
}
在这里,Client 自己 new 了一个 NotNullValidator 实例,通过构造方法传给新的 MyService 实例。
这样,类与类之间的依赖关系由外部管理,可测试性、可复用性都得到了很大的提升。
从应用程序的角度描述,依赖关系不再是 MyService 正向获得一个 IValidator 实现的实例,而是 Client 先实例化一个自己想要的 IValidator 实现,然后注入到 MyService,这就是“控制反转”(IoC)。
从 Client 的角度描述,Client 负责 IValidator 的实例化,并将其注入到 MyService,这就是“依赖注入”(DI)。
IoC其实就是把主动变被动,并有效的分离了对象和它所需要的外部资源,使得它们松散耦合,有利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
除了通过构造器注入外,还有另外两种依赖注入方法。
3.3 设值注入
设值注入是利用类成员变量的 setter 方法进行依赖注入的一种方式。
代码示例:
// IValidator.java
interface IValidator {
public void validate(String param);
}
// NotnullValidator.java
class NotnullValidator implements IValidator{
@Override
public void validate(String param){
System.out.println(this.getClass().getSimpleName()+ " validating param {"+param+"}");
if(null == param || 0 == param.length()){
System.out.println(this.getClass().getSimpleName() + " param can not be null or empty");
}
}
}
// MyService.java
class MyService {
private IValidator validator;
void setValidator(IValidator validator) {
System.out.println("设值注入");
this.validator = validator;
}
void Serve(String param){
System.out.println(this.getClass().getSimpleName() + " param = " + param);
validator.validate(param);
}
}
// Client.java
public class Client {
public static void main(String[] args){
NotnullValidator notnullValidator = new NotnullValidator();
MyService myService = new MyService();
myService.setValidator(notnullValidator);
myService.Serve("");
myService.Serve("123");
/**
* 通过成员变量的setter方法注入依赖,叫做"设值注入"
*/
}
}
这里,为 MyService 的成员变量 validator 定义了setValidator() 方法,从而使得外部可以通过这个 setValidator() 方法注入 IValidator 依赖。
3.4 接口注入
接口注入是指,通过实现特定接口的依赖注入方法实现依赖注入的一种方式。
代码示例:
// IValidator.java
interface IValidator {
public void validate(String param);
}
// MyInject.java
interface MyInject {
public void inject(IValidator validator);
}
// NotnullValidator.java
class NotnullValidator implements IValidator{
@Override
public void validate(String param){
System.out.println(this.getClass().getSimpleName()+ " validating param {"+param+"}");
if(null == param || 0 == param.length()){
System.out.println(this.getClass().getSimpleName() + " param can not be null or empty");
}
}
}
// MyService.java
class MyService implements MyInject {
private IValidator validator;
void Serve(String param){
System.out.println(this.getClass().getSimpleName() + " param = " + param);
validator.validate(param);
}
@Override
public void inject(IValidator validator) {
System.out.println("接口注入");
this.validator = validator;
}
}
// Client.java
public class Client {
public static void main(String[] args){
NotnullValidator notnullValidator = new NotnullValidator();
MyService myService = new MyService();
myService.inject(notnullValidator);
myService.Serve("");
myService.Serve("123");
/**
* 通过实现一个单独的依赖注入接口,实现依赖注入,叫做"接口注入"。
* 不常见的用法,构造函数和setter就够用了。
*
* 通过构造函数、setter、接口注入依赖,仍然存在问题。
* 加入一个类A依赖非常多的其他类,每一个都要手动new,然后set,太麻烦了。
* 怎么办呢?上框架。
*/
}
}
这里定义了一个 MyInject
接口,接口内部参数 inject()
用于注入 IValidator 。
MyService 实现了这个接口,Client 可以通过 inject() 注入IValidator 实例。
上面提到的三种注入方式:构造子注入、设值注入、接口注入,实际上都是提供一个“入口”函数,允许外部调用方向内注入 IValidator 依赖,只是形式不同罢了。
但是,外部系统管理这些 “new” 操作的话,如果接口内有多层传递依赖,或者依赖项比较多的时候,Client 负担就太重了。
怎么办呢?上框架。
IoC/DI 框架要做的事情,就是:new 什么,什么时候 new ,new 完了要给谁。。。
IoC/DI 框架有很多,这里主要介绍两个,Spring 和 Guice。
4. IoC/DI 框架
4.1 spring
IoC是spring的核心,对于spring框架来说,就是由 spring 来负责控制对象的生命周期和对象间的关系。
想要使用 spring ,至少需要两个核心包:group: 'org.springframework', name: 'spring-core'
和group: 'org.springframework', name: 'spring-context'
。
spring 通过一个叫做“容器”的东西来管理所有的 bean 对象。
当 Spring 应用程序被加载到内存中时,spring 框架通过读取 “配置元数据”,来完成对象的实例化、配置和组装。应用程序可以从容器中取bean,方法是getBean()
。
在这里,接口和类定义同上一章。
只是,额外增加 xml 配置文件用于定义 bean 的生命周期及依赖关系。
PS:“配置元数据” 可以通过 XML、Java注解、Java代码来标识,代码示例只给出xml配置。
代码有点多,我分开说哈。
记得以下几步:
- 引入上面说的两个包。
- 写 java 代码 定义 bean。
- 写 xml 配置文件。
- Client。
构建工具用的是gradle,所以这么引包:
// build.gradle
group 'com.ann.javas.topics'
version '1.0-SNAPSHOT'
apply plugin: 'java'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile group: 'org.springframework', name: 'spring-core', version: '4.3.11.RELEASE'
compile group: 'org.springframework', name: 'spring-context', version: '4.3.11.RELEASE'
}
本例中,给出了5种xml配置方法:构造子注入、设值注入、自动装载byName,自动装载byType,自动装载constructor。
先列下java代码,bean 定义没变,就是Client.java里面的内容多了点:
// IValidator.java
public interface IValidator {
public void validate(String param);
}
// NotnullValidator.java
public class NotnullValidator implements IValidator{
@Override
public void validate(String param) {
System.out.println(this.getClass().getSimpleName()+ " validating param {"+param+"}");
if(null == param || 0 == param.length()){
System.out.println(this.getClass().getSimpleName() + " param can not be null or empty");
}
}
}
// MyService.java
public class MyService {
private IValidator validator;
public MyService(IValidator validator) {
this.validator = validator;
}
public void setValidator(IValidator validator) {
this.validator = validator;
}
public MyService() {
}
void Serve(String param){
System.out.println(this.getClass().getSimpleName() + " param = " + param);
validator.validate(param);
}
}
// Client.java
public class Client {
/**
* 构造子注入 和 设值注入,唯一的区别就是标签中定义依赖的元素不同。
* 构造子注入使用:constructor-arg,设值注入使用:property。
*
* @param args
*/
public static void main(String[] args) {
System.out.println("case1:测试spring容器构造子注入(基于XML配置)...");
demo1();
System.out.println("..................................................");
System.out.println("case2:测试spring容器设值注入(基于XML配置)...");
demo2();
System.out.println("..................................................");
System.out.println("case3:测试spring容器自动装载byName(基于XML配置)...");
demo3();
System.out.println("..................................................");
System.out.println("case4:测试spring容器自动装载byType(基于XML配置)...");
demo4();
System.out.println("..................................................");
System.out.println("case5:测试spring容器自动装载constructor(基于XML配置)...");
demo5();
System.out.println("..................................................");
}
private static void demo1(){
// 读取配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("demo5_configioc-byxml-case1.xml");
// 构造子注入实例
MyService service = (MyService)context.getBean("s");
service.Serve("");
service.Serve("123");
}
private static void demo2(){
// 读取配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("demo5_configioc-byxml-case2.xml");
// 设值注入实例
MyService service = (MyService)context.getBean("s");
service.Serve("");
service.Serve("123");
}
private static void demo3(){
// 读取配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("demo5_configioc-byxml-case3.xml");
// 自动装载,byName
MyService service = (MyService)context.getBean("s");
service.Serve("");
service.Serve("123");
}
private static void demo4(){
// 读取配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("demo5_configioc-byxml-case4.xml");
// 自动装载,byType
MyService service = (MyService)context.getBean("s");
service.Serve("");
service.Serve("123");
}
private static void demo5(){
// 读取配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("demo5_configioc-byxml-case5.xml");
// 自动装载,constructor
MyService service = (MyService)context.getBean("s");
service.Serve("");
service.Serve("123");
}
}
然后是xml配置文件,有5个:
// demo5_configioc-byxml-case1.xml
<?xml version="1.0" encoding="UTF-8"?>
<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,并指明他们的依赖关系: MyService 依赖 NotnullValidator -->
<!-- 被依赖的 NotnullValidator 会在 MyService 构造之前构造 -->
<!-- 用 constructor-arg 配置,就是构造子注入 -->
<bean id="s" class="com.ann.javas.topics.iocdi.demo5.usespring.MyService">
<constructor-arg ref="p"/>
</bean>
<bean id="p" class="com.ann.javas.topics.iocdi.demo5.usespring.NotnullValidator">
</bean>
</beans>
// demo5_configioc-byxml-case2.xml
<?xml version="1.0" encoding="UTF-8"?>
<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,并指明他们的依赖关系: MyService 依赖 NotnullValidator -->
<!-- 被依赖的 NotnullValidator 会在 MyService 构造之前构造 -->
<!-- 用 property 配置,就是设值注入 -->
<bean id="s" class="com.ann.javas.topics.iocdi.demo5.usespring.MyService">
<property name="validator" ref="p"/>
</bean>
<bean id="p" class="com.ann.javas.topics.iocdi.demo5.usespring.NotnullValidator">
</bean>
</beans>
// demo5_configioc-byxml-case3.xml
<?xml version="1.0" encoding="UTF-8"?>
<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,并指明他们的依赖关系: MyService 依赖 NotnullValidator -->
<!-- 被依赖的 NotnullValidator 会在 MyService 构造之前构造 -->
<!-- 自动装载 byName,根据 id 或 name 查找 bean 并自动装配 -->
<bean id="s" class="com.ann.javas.topics.iocdi.demo5.usespring.MyService" autowire="byName">
</bean>
<!-- 这里的 id 必须和 MyService 中定义的IValidator 变量名一致,否则 byName 无法识别 -->
<bean id="validator" class="com.ann.javas.topics.iocdi.demo5.usespring.NotnullValidator">
</bean>
</beans>
// demo5_configioc-byxml-case4.xml
<?xml version="1.0" encoding="UTF-8"?>
<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,并指明他们的依赖关系: MyService 依赖 NotnullValidator -->
<!-- 被依赖的 NotnullValidator 会在 MyService 构造之前构造 -->
<!-- 自动装载byType,根据 class 查找并自动装配 bean -->
<bean id="s" class="com.ann.javas.topics.iocdi.demo5.usespring.MyService" autowire="byType">
</bean>
<bean id="b" class="com.ann.javas.topics.iocdi.demo5.usespring.NotnullValidator">
</bean>
</beans>
// demo5_configioc-byxml-case5.xml
<?xml version="1.0" encoding="UTF-8"?>
<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,并指明他们的依赖关系: MyService 依赖 NotnullValidator -->
<!-- 被依赖的 NotnullValidator 会在 MyService 构造之前构造 -->
<!-- 自动装载 constructor,根据 bean 的构造函数定义进行查找并自动装配 -->
<bean id="s" class="com.ann.javas.topics.iocdi.demo5.usespring.MyService" autowire="constructor">
</bean>
<bean id="b" class="com.ann.javas.topics.iocdi.demo5.usespring.NotnullValidator">
</bean>
</beans>
代码中的注释有简单介绍,详细的内容就不再赘述~
4.2 guice
guice 是谷歌开发的轻量级 DI 框架,号称比 spring 快10倍,虽然我没测过,但确实蛮好用的就是了。
想要使用guice,需要引包:group: 'com.google.inject', name: 'guice'
guice 允许你使用 @Inject 进行注解构造函数的方式,来进行自动实例化构造参数。也可以通过实现 Module 的 configure(Binder binder) 方法定义依赖注入规则。
在程序启动时,你可以使用 Guice.createInjector(自定义Module)
来生成一个injector,之后就可以从 injector 中获取你需要 guice 为你实例化的类,方法是 injector.getInstance(你想要的类.class)
。
用guice非常简单:
- 引包。
- java代码。
同样gradle构建:
// build.gradle
group 'com.ann.javas.topics'
version '1.0-SNAPSHOT'
apply plugin: 'java'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile group: 'com.google.inject', name: 'guice', version: '4.0'
}
java代码:
// IValidator.java
interface IValidator {
public void validate(String param);
}
// NotnullValidator.java
class NotnullValidator implements IValidator {
@Override
public void validate(String param){
System.out.println(this.getClass().getSimpleName()+ " validating param {"+param+"}");
if(null == param || 0 == param.length()){
System.out.println(this.getClass().getSimpleName() + " param can not be null or empty");
}
}
}
// MyService.java
class MyService {
@Inject
private IValidator validator;
void Serve(String param){
System.out.println(this.getClass().getSimpleName() + " param = " + param);
validator.validate(param);
}
}
// MyModule.java
public class MyModule implements Module{
@Override
public void configure(Binder binder) {
System.out.println("guice注入规则,绑定 IValidator 到 NotnullValidator");
binder.bind(IValidator.class).to(NotnullValidator.class);
}
}
// Client.java
public class Client {
public static void main(String[] args){
// 定义依赖注入规则
MyModule module = new MyModule();
// 根据注入规则,生成 injector
Injector injector = Guice.createInjector(module);
// 获取 MyService 实例
MyService myService = injector.getInstance(MyService.class);
// 来两个case
myService.Serve("");
myService.Serve("123");
}
}
如你所见,MyModule中定义了依赖注入规则,将 IValidator 绑定到 NotnullValidator。这样,当某个类需要一个IValidator的时候,guice就会给它一个NotNullValidator实例。
其他的请看注释说明~