1.简介
本文将研究Google Guice的基础知识。我们将研究在Guice中完成基本的依赖注入(DI)任务的方法。
我们还将把Guice方法与更成熟的DI框架(例如Spring和Contexts and Dependency Injection(CDI))进行比较和对比。
本文假定读者对依赖注入模式的基本知识有所了解。
2.设定
为了在您的Maven项目中使用Google Guice,您需要将以下依赖项添加到pom.xml中:
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.1.0</version>
</dependency>
3.使用Guice进行基本依赖注入
3.1。我们的样品程序
我们将在一个场景中工作,在该场景中设计支持在帮助台业务中三种通讯方式的类:电子邮件,SMS和IM。
考虑一下类:
public class Communication {
@Inject
private Logger logger;
@Inject
private Communicator communicator;
public Communication(Boolean keepRecords) {
if (keepRecords) {
System.out.println("Message logging enabled");
}
}
public boolean sendMessage(String message) {
return communicator.sendMessage(message);
}
}
该通信类是通信的基本单元。此类的实例用于通过可用的通信通道发送消息。如上所示,Communication具有一个Communicator,我们可以使用它进行实际的消息传输。
Guice的基本入口是Injector:
public static void main(String[] args){
Injector injector = Guice.createInjector(new BasicModule());
Communication comms = injector.getInstance(Communication.class);
}
这个主要方法检索我们的Communication类的一个实例。它还介绍了Guice的基本概念:模块(在此示例中使用BasicModule)。该模块是绑定定义的基本单元(或布线,因为它在弹簧的已知)。
Guice对依赖项注入和管理采用了代码优先的方法,因此您无需开箱即用地处理大量XML。
在上面的示例中,如果类具有默认的无参构造函数,则将使用称为即时绑定的功能隐式注入Communication的依赖树。自创建以来,这一直是Guice的功能,并且仅从v4.3开始在Spring中可用。
绑定到Guice就像连线到Spring一样。使用绑定,您可以定义Guice如何将依赖项注入到类中。
绑定是在com.google.inject.AbstractModule的实现中定义的:
public class BasicModule extends AbstractModule {
@Override
protected void configure() {
// bind(Communicator.class).to(DefaultCommunicatorImpl.class);
bind(Communicator.class)
.annotatedWith(Names.named("DefaultCommunicator"))
.to(DefaultCommunicatorImpl.class);
}
}
此模块实现指定在找到Communicator变量的任何地方都将注入DefaultCommunicatorImpl的实例。
这种机制的另一种形式是命名绑定。考虑以下变量声明:
@Inject @Named("DefaultCommunicator")
Communicator communicator;
此绑定将为Communicator的实例提供一个变量,该变量使用@Named(“ DefaultCommunicator”)批注进行批注。
您会注意到@Inject和@Named注释似乎是来自Jakarta EE的CDI的借贷注释。它们位于com.google.inject。*包中-使用IDE时,请注意从正确的包中导入。
提示:尽管我们刚刚说过使用Guice提供的@Inject和@Named,但值得注意的是,Guice确实支持javax.inject.Inject和javax.inject.Named以及其他Jakarta EE注释。
您还可以使用构造函数绑定来注入没有默认no-arg构造函数的依赖项:
public class BasicModule extends AbstractModule {
@Override
protected void configure() {
bind(Boolean.class).toInstance(true);
bind(Communication.class).toConstructor(
Communication.class.getConstructor(Boolean.TYPE));
}
上面的代码片段将使用带有布尔参数的构造函数注入Communication的实例。我们通过定义Boolean类的非目标绑定为构造函数提供true参数。
该非目标绑定将被急切地提供给接受布尔参数的绑定中的任何构造函数。通过这种方法,将注入通信的所有依赖项。
特定于构造函数的绑定的另一种方法是实例binding,其中我们直接在绑定中提供一个实例:
public class BasicModule extends AbstractModule {
@Override
protected void configure() {
bind(Communication.class)
.toInstance(new Communication(true));
}
}
无论在何处声明了Communication变量,此绑定都将提供Communication类的实例。
但是,在这种情况下,该类的依赖关系树将不会自动连接。在没有必要进行大量初始化或依赖注入的情况下,应限制使用此模式。
4.依赖注入的类型
Guice支持您使用DI模式期望的标准注射类型。在Communicator类中,我们需要注入不同类型的CommunicationMode。
4.1。字段注入
@Inject @Named("SMSComms")
CommunicationMode smsComms;
使用可选的@Named注释作为限定符,以基于名称实现目标注入
4.2。方法注入
在这里,我们使用setter方法来实现注入:
@Inject
public void setEmailCommunicator(@Named("EmailComms") CommunicationMode emailComms) {
this.emailComms = emailComms;
}
4.3。构造函数注入
您还可以使用构造函数注入依赖项:
@Inject
public Communication(@Named("IMComms") CommunicationMode imComms) {
this.imComms= imComms;
}
4.4。隐式注射
Guice将隐式注入一些通用组件,例如Injector和java.util.Logger实例。您会注意到我们在所有示例中都使用记录器,但找不到它们的实际绑定。
5.Guice的范围界定
Guice支持我们在其他DI框架中已经习惯的范围和作用域机制。Guice默认提供一个新的已定义依赖项实例。
5.1。单例
让我们向应用程序中注入一个单例:
bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator"))
.to(Communicator.class).in(Scopes.SINGLETON);
将在(Scopes.SINGLETON)表示:任何通讯与现场@Named(“AnotherCommunicator”)将得到一个单注射。默认情况下,此单例是延迟启动的。
5.2。饿汉单例模式
现在,让我们插入一个饿汉单例模式:
bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator"))
.to(Communicator.class)
.asEagerSingleton();
所述asEagerSingleton()调用定义为饿汉实例化的单例。
除了这两个作用域之外,Guice还支持自定义作用域以及Jakarta EE提供的仅Web的@RequestScoped和@SessionScoped注释(这些注释没有Guice提供的版本)。
6. Guice中面向方面的编程
Guice符合AOP Alliance面向方面编程的规范。我们可以实现典型的日志记录拦截器,仅用四个步骤就可以在示例中使用它来跟踪消息的发送。
步骤1 –实现AOPAlliance的MethodInterceptor:
public class MessageLogger implements MethodInterceptor {
@Inject
Logger logger;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object[] objectArray = invocation.getArguments();
for (Object object : objectArray) {
logger.info("Sending message: " + object.toString());
}
return invocation.proceed();
}
}
第2步–定义纯Java注释:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MessageSentLoggable {
}
步骤3 –为匹配器定义绑定:
Matcher是一个Guice类,我们使用它来指定AOP注释将应用于的组件。在这种情况下,我们希望注释能够应用于CommunicationMode的实现:
public class AOPModule extends AbstractModule {
@Override
protected void configure() {
bindInterceptor(
Matchers.any(),
Matchers.annotatedWith(MessageSentLoggable.class),
new MessageLogger()
);
}
}
我们在此处指定了一个Matcher,它将把MessageLogger拦截器应用于任何类,该类的MessageSentLoggable注释已应用到其方法。
步骤4 –将注释应用于我们的通信模式并加载我们的模块
@Override
@MessageSentLoggable
public boolean sendMessage(String message) {
logger.info("SMS message sent");
return true;
}
public static void main(String[] args) {
Injector injector = Guice.createInjector(new BasicModule(), new AOPModule());
Communication comms = injector.getInstance(Communication.class);
}
7.结论
看完基本的Guice功能之后,我们可以看到Guice的灵感来自Spring。
除了对JSR-330的支持外,Guice的目标是成为一个以注入为重点的DI框架(而Spring提供了整个编程生态系统,而不仅仅是DI带来了编程上的便利),针对需要DI灵活性的开发人员。
Guice的扩展性也很高,它允许程序员编写可移植的插件,从而灵活,富有创意地使用该框架。这是Guice已经为大多数流行的框架和平台(例如Servlet,JSF,JPA和OSGi等)提供的广泛集成的补充。
您可以在我们的GitHub项目中找到本教程中使用的所有源代码。