Guice(读作“Juice”)是由Bob Lee在大约2006年发起的开源项目,项目站点地址为http://code.google.com/p/google-guice/。你可以在网站上看到该项目的设计初衷、相关文档并下载运行本节示例代码所需的二进制JAR文件。
在Guice这样的DI框架里,你可以配置依赖项、绑定依赖项,并在使用@Inject
注解(和它在JSR-330中的朋友们)注入依赖项时声明它们的作用域。
Guice 3是JSR-330规范的完整参考实现,本节所有内容都是基于Guice 3的。虽然Guice不仅仅是一个简单的DI框架,但我们关注的主要还是它的DI能力和示例代码,其中你可以使用JSR-330标准注解和Guice一起编写DI代码。
3.3.1 Guice新手指南
现在你已经了解JSR-330的各种注解了。可以借助Guice构建一个Java注入对象集合(包括它们的依赖项)。用Guice的话说,为了让注入器创建对象关系图,需要创建声明各种绑定关系的模块,其中绑定是用来明确要注入的具体实现类的。晕了没?不用担心,看到代码你就明白了,这些概念实际上非常简单。
提示 对象关系图、绑定、模块和注入器都是Guice里的常用语,如果你想用好Guice,最好尽快搞清楚它们是什么意思。
本节我们还是用HollywoodService
的例子。一开始先创建一个拥有各种绑定关系的配置类(模块)。实际上这是Guice框架要管理的那些依赖项的外部配置。
在哪里下载Guice?
最新版的Guice 3可以从http://code.google.com/p/google-guice/downloads/list上下载,对应的文档可以在http://code.google.com/p/google-guice/wiki/Motivation?tm=6上找到。要得到完整的IoC容器和DI支持,还需要下载Guice zip文件并将它解压到你选定的位置。为了在Java代码中使用Guice,要确保这些JAR文件包含在CLASSPATH中。
对于本书中后续代码示例而言,构建Maven时也会自动下载Guice 3的JAR文件。
我们先来创建一个定义绑定关系的AgentFinderModule
。这个AgentFinderModule
类扩展了AbstractModule
,绑定关系在重写的configure
方法中声明。在本例中,当客户类HollywoodService
要求@Inject
一个AgentFinder
的时候,就会绑定WebServiceAgentFinder
类作为注入对象。我们在这里遵循构造方法注入的惯例,具体实现请见代码:
代码清单3-7 HollywoodService
——用 Guice 注入AgentFinder
import com.google.inject.AbstractModule;public class AgentFinderModule extends AbstractModule //扩展AbstractModule{ @Override //重写configure方法 protected void configure { bind(AgentFinder.class). to(WebServiceAgentFinder.class); //①绑定要注入的实现类 }}public class HollywoodServiceGuice{ private AgentFinder finder = null; @Inject public HollywoodServiceGuice(AgentFinder finder) { this.finder = finder; } public List<Agent> getFriendlyAgents { List<Agent> agents = finder.findAllAgents; List<Agent> friendlyAgents = filterAgents(agents, /"Java Developers/"); return friendlyAgents; } public List<Agent> filterAgents(List<Agent> agents, String agentType) { ...//同代码清单3-2 }}
绑定关系的确立在调用Guice的bind方法时发生,把要绑定的类(AgentFinder
)传给它,然后调用to
方法指明要注入到哪个实现类①。
现在已经在模块中声明了绑定关系,可以让注入器构建对象关系图了。接下来我们要看看在独立Java程序和Web应用程序这两种情况下分别要如何实现。
1. 构建Guice对象关系图——独立Java程序
在标准的Java程序中,可以通过public static void main(String args)
方法构建对象关系图。代码清单3-8如下所示。
代码清单3-8 HollywoodServiceClient
——用Guice构建对象关系图
import com.google.inject.Guice;import com.google.inject.Injector;import java.util.List;public class HollywoodServiceClient{ public static void main(String args) { Injector injector = Guice.createInjector(new AgentFinderModule); HollywoodServiceGuice hollyWoodService = injector.getInstance(HollywoodServiceGuice.class); List<Agent> agents = hollywoodService.getFriendlyAgents; ... } }
对于Web应用,情况稍有不同。
2. 构建Guice对象关系图——Web应用程序
在Web应用程序中,需要把guice-servlet.jar加到Web应用的类库中,然后在web.xml中添加下面的配置项:
<filter> <filter-name>guiceFilter</filter-name> <filter-class>com.google.inject.servlet.GuiceFilter</filter-class></filter><filter-mapping> <filter-name>guiceFilter</filter-name> <url-pattern>/*</url-pattern></filter-mapping>
然后是标准动作,扩展ServletContextListener
以便使用Guice的ServletModule
(与代码清单3-7中的AbstractModule
类似)。
public class MyGuiceServletConfig extends GuiceServletContextListener { @Override protected Injector getInjector { return Guice.createInjector(new ServletModule); }}
最后一步,把下面这些配置加到web.xml文件中,以便servlet容器在部署应用时触发该类。
<listener> <listener-class>com.java7developer.MyGuiceServletConfig</listener-class></listener>
经由注入器创建HollywoodServiceGuice
,你得到了一个配置完备的类,马上就可以调用其中的getFriendlyAgents
方法。
非常简单,对不对?没错,但这种把WebServiceAgentFinder
绑定到AgentFinder
上的情况很简单,而你所需要的绑定方式可能要比这个复杂,所以我们还需要了解一下如何定义更复杂的绑定方式。
3.3.2 水手绳结:Guice的各种绑定
Guice提供了多种绑定方式,官方文档列出的绑定类型如下所示:
- 链接绑定
- 绑定注解
- 实例绑定
@Provides
方法- Provider绑定
- 无目标绑定
- 内置绑定
- 即时绑定
我们无意在此重复Guice的官方文档,因此只挑最常用的讲解一下,其中包括链接绑定、绑定注解和@Provides
方法及Provider<T>
绑定。
1. 链接绑定
链接绑定是最简单的绑定方式,代码清单3-6中配置AgentFinderModule
时用的就是这种方式。这种绑定方式只是告诉注入器运行时应该注入实现类或扩展类(是的,可以直接注入子类)。
@Overrideprotected void configure{ bind(AgentFinder.class).to(WebServiceAgentFinder.class);}
你已经见过这种绑定代码了,让我们看看另外一种最常用的绑定方式:绑定注解。
2. 绑定注解
绑定注解是指将注入类的类型和额外的标识符组合起来,以标识恰当的注入对象。你可以自定义绑定注解(参见Guice在线文档),不过我们还是先介绍一下JSR-330标准注解@Named
的用法。
在下面的例子中,你所熟悉的@Inject
依然会出现,但它这次会与@Named
注解联袂登场,以注入特定名称的AgentFinder
。为了配置@Named
绑定,需要在AgentModule
中调用annotatedWith
方法,代码清单3-9如下所示:
代码清单3-9 HollywoodService
——使用@Named
public class HollywoodService{ private AgentFinder finder = null; @Inject public HollywoodService(@Named(“primary”) AgentFinder finder) //使用@Named注解 { this.finder = finder; }}public class AgentFinderModule extends AbstractModule{ @Override protected void configure { bind(AgentFinder.class) .annotatedWith(Names.named(“primary”)) //与命名参数绑定在一起 .to(WebServiceAgentFinder.class); }}
现在你已经知道如何配置命名依赖项了,可以继续学习另一种绑定方式了。下面就用@Provides
注解和Provider<T>
接口绑定完全由你自己定制的依赖项吧。
3. @Provides和Provider:提供完全定制的对象
你可以用@Provides
注解,或者在configure
方法中绑定,以返回一个完全由你自己定制的对象。比如说,你可能想注入一个非常特别的SpreadsheetAgentFinder
(微软的Excel电子表格实现)。
注入器会查看所有标记了@Provides
注解方法的返回类型,以决定要注入哪个对象。比如在下面的代码中,HollywoodService
会用由provideAgentFinder
方法提供的并带有@Provides
注解的AgentFinder
。
代码清单3-10 AgentFinderModule
——使用@Provides
public class AgentFinderModule extends AbstractModule{ @Override protected void configure { } @Provides AgentFinder provideAgentFinder //返回注入器需要的类型 { /**创建SpreadsheetAgentFinder的实例并设定具体值*/ SpreadsheetAgentFinder finder = new SpreadsheetAgentFinder; finder.setType(“Excel 97”); finder.setPath(“C:tempagents.xls”) return finder; }}
@Provides
方法会变得越来越多,为了不把模块类撑爆,你可能要把它们拆分出去建立自己的类。因此,Guice支持JSR-330的Provider<T>
接口;如果你还记得第3.2.6节的内容,应该不会忘记T get
方法。当在AgentFinderModule
类中通过toProvider
方法绑定到AgentFinderProvider
时,就会调用这个方法。代码如下所示:
代码清单3-11 AgentFinderModule
——使用Provider 接口
public class AgentFinderProvider implements Provider<AgentFinder>{ @Override public AgentFinder get//使用T get 方法 { SpreadsheetAgentFinder finder = new SpreadsheetAgentFinder; finder.setType(“Excel 97”); finder.setPath(“C:tempagents.xls”) return finder; }}public class AgentFinderModule extends AbstractModule{ @Override protected void configure { bind(AgentFinder.class) .toProvider(AgentFinderProvider.class);//绑定provider }}
这是最后一个关于绑定的例子。现在你应该能用Guice绑定你需要的依赖项了。但我们还没讨论依赖项的生命周期范围。了解生命周期非常重要,因为如果对象生命周期设置错误的话,它们可能会存在更长时间并占用更多的内存空间。
3.3.3 在Guice中限定注入对象的生命周期
Guice为注入对象提供了不同级别的生命周期。其中最短的是@RequestScope
,然后是@SessionScope
,还有就是JSR-330规范中定义的@Singleton
,也就是应用级别的生命周期。
在代码中有以下几种方式应用依赖项的生命周期:
- 在要注入的类中;
- 作为绑定声明的一部分(比如
bind.to.in
); - 和
@Provides
一起使用注解声明。
上面的列表有点儿抽象,我们还是用限定依赖项生命周期的一小段代码来说明上面这些方式究竟是什么意思吧。
1. 限定注入项的生命周期
假设你希望程序的整个生命周期中只用一个SpreadsheetAgentFinder
实例。为此需在类声明中设置@Singleton
,如下所示:
@Singletonpublic class SpreadsheetAgentFinder{ ...}
用这个方法还有个好处,可以提醒开发人员注入项对线程安全的要求。因为从理论上来说,SpreadsheetAgentFinder
类可以多次注入,@Singleton
范围意味着要保证这个类的线程安全性(第4章会讨论线程安全)。
如果你更喜欢在绑定依赖项时声明其生命周期,你就可以这样做。
2. 用BIND方法设置生命周期
有些开发人员可能喜欢把所有和注入对象相关的规则都放在一起。还记得代码清单3-9中如何绑定“primary”AgentFinder
吗?在绑定时设置生命周期与此类似,只要在bind
方法串后面再加上.in(<Scope>.class)
就行了。
下面的代码改进了代码清单3-9,在bind
方法串后面加上了in(Session.class)
,使得在会话(session)范围中能够获取“primary” AgentFinder
对象。
public class AgentFinderModule extends AbstractModule{ @Override protected void configure { bind(AgentFinder.class) .annotatedWith(Names.named(“primary”)) .to(WebServiceAgentFinder.class) .in(Session.class); }}
还有最后一种设置注入对象生命周期的方法:与@Provides
注解联合设置。
3. 设置@Provides对象的生命周期
你可以在@Provides
注解旁边加上一个生命周期,以定义由该方法所提供对象的生命周期。比如在代码清单3-9中,可以加上@Request
注解,将最终提供的SpreadsheetAgentFinder
实例限定在请求(request)范围中。
@Provides @RequestAgentFinder provideAgentFinder{ SpreadsheetAgentFinder finder = new SpreadsheetAgentFinder; finder.setType(“Excel 97”); finder.setPath(“C:tempagents.xls”); return finder;}
Guice也提供了针对Web应用的注入项生命周期(比如Servlet request范围),当然你也可以根据需要实现自己的注入项生命周期。
现在你已经基本掌握了在Guice中用JSR-330注解满足你的依赖注入需求了。其实Guice还提供许多非JSR-330特性,比如它还支持面向方面的编程(AOP),让你可以实现安全性和日志处理的横切关注点。要了解这些内容,请参考Guice的在线文档和代码示例。
谨慎选择对象的生命周期!
一个成熟的Java开发人员总是会认真考虑对象的生命周期。创建无状态对象相对来说成本低廉,无需考虑其生命周期。JVM会在需要时创建和销毁它们,毫无障碍。(第6章详细讨论了JVM和性能。)
另一方面,状态对象总是需要设定生命周期!你应该认真考虑是否要让一个对象的生命周期贯穿整个应用程序的运行期,或者仅存在于当前会话,还是只在当前请求中。接下来就要考虑对象的线程安全性了(第4章会进一步讨论这个问题)。