这篇是承接《轻量级 Java 开发框架 设计》系列Blog文的后续文章。本文是最新 《Hasor 开发指南》中依赖注入章节的完整内容,开发指南目前仍然在努力编写中。
概念
“依赖注入(DI)”有时候也被称为“控制反转(IoC)”本质上它们是同一个概念。具体是指,当某个类调用另外一个类的时候通常需要调用者来创建被调用者。但在控制反转的情况下调用者不在主动创建被调用者,而是改为由容器注入,因此而得名。
这里的“创建”强调的是调用者的主动性。而依赖注入则不在需要调用者主动创建被调用者。
举个例子通常情况下调用者(ClassA),会先创建好被调用者(FunBean),然后在调用方法callFoo中调用被调用者(FunBean)的foo方法:
public class ClassA {
private FunBean funBean = new FunBean();
public void callFoo() {
this.funBean.foo();
}
}
public class FunBean {
public void foo() {
System.out.println("say ...");
}
}
使用了依赖注入的情况恰恰相反,调用者(ClassA)事先并不知道要创建哪个被调用者(FunBean)。ClassA调用的是被注入进来的FunBean,通常我们会为需要依赖注入的对象留有set方法,在调用callFoo方法之前是需要先将funBean对象通过setFunBean方法设置进来的。例如:
public class ClassA {
private FunBean funBean = null;
public void setFunBean(FunBean funBean) {
this.funBean = funBean;
}
public void callFoo() {
this.funBean.foo();
}
}
public class FunBean {
……
传统注入方式
严格意义上来说注入的形式分为两种,它们是“构造方法注入”和“set属性注入”。我们经常听到有第三种注入方式叫“接口注入”。其实它只是“set属性注入”的一种接口表现形式。
- A.构造方法注入:是指被注入的对象通过构造方法传入,例如下面代码:
public class ClassA {
private FunBean funBean = null;
public ClassA(FunBean funBean) {
this.funBean = funBean;
}
public void callFoo() {
this.funBean.foo();
}
}
- B.set属性注入:是指被注入的对象通过其get/set读写属性方法注入进来,例如:
public class ClassA {
private FunBean funBean = null;
public void setFunBean(FunBean funBean) {
this.funBean = funBean;
}
public void callFoo() {
this.funBean.foo();
}
}
- C.接口注入:是指通过某个接口的set属性方法来注入,大家可以看到其本质还是set属性注入。只不过调用者(ClassA),需要实现某个注入接口。
public interface IClassA {
public void setFunBean(FunBean funBean);
}
public class ClassA implements IClassA{
private FunBean funBean = null;
public void setFunBean(FunBean funBean) {
this.funBean = funBean;
}
public void callFoo() {
this.funBean.foo();
}
}
Guice与JSR-330
JSR-330相关的API是由“javax.inject.*”软件包提供的一组标准API。通过注解作为其表现形式。Hasor使用Google旗下的开源DI容器Guice作为其JSR-330的标准支持组件。
Guice是Google开发的一个轻量级,基于Java5(主要运用泛型与注释特性)的依赖注入框架(IoC)。Guice非常小而且快。Guice是类型安全的,它能够对构造函数,属性,方法(包含任意个参数的任意方法,而不仅仅是setter方法)进行注入。
Guice还具有一些可选的特性比如:自定义scopes,传递依赖,静态属性注入,与Spring集成和AOP联盟方法注入等。对于DI框架来说,性能是很重要的,Guice比Spring快这是主流说法,在Guice的官方网站上您可以看到它宣称比Spring快1000倍!
Hasor选用Guice是由于它的开发接口十分灵活,比起Spring而言Guice更适合作为一个内嵌DI工具来使用。
由于Guice是JSR-330标准的实现,这也就使得Hasor也具备了支持JSR-330标准的能力。在下面几个小节会讲解如何使用JSR-330标准将其注入到需要的类上。
我们先假定有一个被调用者(PojoBean),下面是PojoBean类的源代码:
public class PojoBean {
private String uuid = UUID.randomUUID().toString();
private String name = "马三";
private String address = "北京马连洼街道办...";
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
……
}
构造方法注入
public class ConstructorInject {
private PojoBean userBean;
@javax.inject.Inject/*依赖注入*/
public ConstructorInject(PojoBean userBean) {
this.userBean = userBean;
}
public String getUserName() {
return this.userBean.getName();
}
}
属性方式注入
public class MethodInject {
private PojoBean userBean;
@javax.inject.Inject/*依赖注入*/
public void setUserBean(PojoBean userBean) {
this.userBean = userBean;
}
public String getUserName() {
return this.userBean.getName();
}
}
字段方式注入
字段注入是DI容器对“set属性注入”的一种改进.这种改进使得被注入的对象不在需要实现一个set方法,DI容器会主动的将要注入的对象赋值到给定的字段上。
public class FieldInject {
@javax.inject.Inject/*依赖注入*/
private PojoBean userBean;
public String getUserName() {
return this.userBean.getName();
}
}
单例Bean
单例,通常是指整个应用程序范围内某个类型对象只有一个。Hasor使用AppContext接口表示一个应用程序,在一个AppContext内Hasor通过下面这样的代码可以保证单例:
@javax.inject.Singleton/*声明单例*/
public class SingletonBean {
private long time = 0;
public SingletonBean() {
time = System.currentTimeMillis();
}
public void foo() {
System.out.println("create at time:" + time);
}
}