我个人对依赖注入的好处持矛盾态度。 一方面,我认识到它在某些容器环境(例如Java EE)中的有用性。 (根据记录,我是JCP专家组的CDI 1.0规范的作者。)另一方面,鉴于我过去几年一直在从事的工作的性质,我实际上并没有在我自己的程序中使用它。
但是有很多人通过依赖注入发誓,问我锡兰在这方面能提供什么。 简短的答案是:没有什么特别的; Ceylon SDK是围绕模块化库的概念构建的。 它既不提供框架,也不提供容器。 这使SDK尽可能地具有通用性,这意味着可以从任何其他容器环境(例如Java EE,vert.x,OSGi或任何其他容器)重用它。
因此,如果您今天想在Ceylon中进行依赖注入,则必须使用用Java编写的容器。 幸运的是,Ceylon 1.2具有与Java的出色互操作性,几乎不会产生任何摩擦。 肯定有一天有人会在锡兰写一个依赖注入容器,但是,正如我们将要看到的那样,根本没有紧迫性。
我将探索:
这些是我最喜欢的Java容器,尽管Spring当然有很多粉丝。 也许有一天我会找时间玩。
您可以在以下Git存储库中找到示例代码:
焊接
我发现在锡兰使用Weld非常简单,除了一个相对较小的问题,我将在下面提到。
焊接的模块描述符
Weld在Maven Central中提供了一个胖子罐,这使得它在锡兰特别容易使用。 我使用以下模块描述符从Maven Central下载Weld并将其导入到我的项目中:
native("jvm")
module weldelicious "1.0.0" {
import "org.jboss.weld.se:weld-se" "2.3.1.Final";
import ceylon.interop.java "1.2.0";
}
其中org.jboss.weld.se
是Maven 组ID ,而weld-se
是Maven 工件ID 。 (我丝毫不知道这些东西的实际含义,我只知道其中有两个。)
我还导入了Ceylon SDK模块ceylon.interop.java
因为我将使用其javaClass()
函数。
自举焊接
尽管它不是CDI规范的一部分,但Weld提供了一个非常简单的API来创建容器。 我从stackoverflow复制/粘贴了以下代码:
import org.jboss.weld.environment.se { Weld }
shared void run() {
value container = Weld().initialize();
//do stuff with beans
...
container.shutdown();
}
我试图运行此功能。
知道了!
就像所有其他的CDI开发商曾经 ,我忘了beans.xml
文件。 幸运的是,Weld给了我一个非常清晰的错误消息。 也许不像“ seescapóla tortuga”那么富有诗意,但足以使我想起规格的这一要求。 (是的,我写的规范。)
为了解决该问题,我在目录resource/weldelicious/ROOT/META-INF
添加了一个名为beans.xml
的空文件,这是您希望Ceylon将文件放入文件的META-INF
目录中的神奇位置。模块档案。
定义焊豆
我为希望注入的bean定义了以下接口:
interface Receiver {
shared formal void accept(String message);
}
接下来,我定义了一个依赖于此接口实例的bean:
import javax.inject { inject }
inject class Sender(Receiver receiver) {
shared void send() => receiver.accept("Hello!");
}
( inject
注释是您用Java编写@Inject
的东西。)最后,我们需要一个实现Receiver
的bean:
class PrintingReceiver() satisfies Receiver {
accept = print;
}
获取并调用一个bean
回到run()
函数,我添加了一些代码以从容器中获取Sender
,并调用send()
:
import org.jboss.weld.environment.se { Weld }
import ceylon.interop.java { type = javaClass }
shared void run() {
value container = Weld().initialize();
value sender
= container
.select(type<Sender>())
.get();
sender.send();
weld.shutdown();
}
请注意,我正在使用javaClass()
函数来为Ceylon类型的Sender
获取java.lang.Class
的实例。 仅使用CDI API且也适用于泛型类型的另一种方法是使用javax.enterprise.inject.TypeLiteral
:
value sender
= container
.select(object extends TypeLiteral<Sender>(){})
.get();
不幸的是,这有点冗长。
命名构造函数注入
使用IDE中的一些快速修复程序,我们可以将Sender
类转换为具有默认构造函数的类:
class Sender {
Receiver receiver;
inject shared new (Receiver receiver) {
this.receiver = receiver;
}
shared void send() => receiver.accept("Hello!");
}
就Weld而言,这与我们以前的情况相同。 但是我们甚至可以给我们的构造函数起一个名字:
class Sender {
Receiver receiver;
inject shared new inject(Receiver receiver) {
this.receiver = receiver;
}
shared void send() => receiver.accept("Hello!");
}
由于意外的偶然性,这实际上是可行的。
方法和场注入
我认为方法或现场注入在锡兰中不是很自然的事情,因此我不建议这样做。 但是,它确实有效,只要您用late
注解标记通过注入初始化的任何字段:
这可行,但感觉不太像Ceylonic:
class Sender() {
inject late Receiver receiver;
shared void send() => receiver.accept("Hello!");
}
这也适用:
class Sender() {
late Receiver receiver;
inject void init(Receiver receiver) {
this.receiver = receiver;
}
shared void send() => receiver.accept("Hello!");
}
使用CDI生产者
有关使用锡兰与焊接的好处之一是,你可以使用produces
于一个顶级功能注释。
import javax.enterprise.inject { produces }
produces Receiver createReceiver()
=> object satisfies Receiver {
accept = print;
};
CDI限定词
我们可以在锡兰定义CDI限定词注释:
import javax.inject { qualifier }
annotation Fancy fancy() => Fancy();
final qualifier annotation class Fancy()
satisfies OptionalAnnotation<Fancy> {}
限定符注释必须同时应用于注入点以及bean或生产者函数。 首先,我注释了bean类:
fancy class FancyReceiver() satisfies Receiver {
accept(String message)
=> print(message + " \{BALLOON}\{PARTY POPPER}");
}
接下来,我尝试注释注入的初始值设定项参数:
//this doesn't work!
inject class Sender(fancy Receiver receiver) {
shared void send() => receiver.accept("Hello!");
}
不幸的是,这没有用。 当编译为Java字节码时,Ceylon实际上将此fancy
注释放在Sender
的生成的getter方法上,而不是在参数上,并且Weld仅在注入的参数上查找限定符注释。 我必须使用构造函数注入来使限定符正常工作:
//this does work
class Sender {
Receiver receiver;
inject shared new (fancy Receiver receiver) {
this.receiver = receiver;
}
shared void send() => receiver.accept("Hello!");
}
作为记录,限定符注释也可以与方法注入一起使用。 它们不适用于野外注入。
这是我对锡兰使用Weld的唯一失望,我相信我已经知道如何在锡兰1.2.1中解决此问题。
范围豆
您可以在Ceylon中定义作用域的bean(具有CDI规范称为正常作用域的bean),只需将作用域注释应用于bean:
import javax.enterprise.context { applicationScoped }
applicationScoped
class PrintingReceiver() satisfies Receiver {
accept = print;
}
但是,这里需要注意一些事项:CDI为有作用域的bean创建代理,并且由于Ceylon类的操作默认情况下是“最终的”,因此您可以选择:
- 注释bean
default
所有操作,或者 - 注入接口而不是具体的bean类。
我认为第二种选择是更好的选择,甚至可能是Java中最好的方法。
当然,同样的警告适用于具有CDI拦截器或装饰器的bean,尽管我没有对此进行测试。
Weld提供了许多我没有时间测试的附加功能,但是我希望它们可以在锡兰工作。
吉斯
Guice也很容易设置,尽管我在Maven方面浪费了一些时间。
Guice的模块替代
Guice不会装在一个大罐子里,因此在使用Ceylon的Maven模块时,我们必须处理一个常见的问题。 Maven是为平面Java类路径设计的,因此Maven模块不带有元数据,该元数据有关通过公共API重新导出其依赖项的信息。 解决此问题有三种基本策略:
- 使用
--flat-classpath
编译并使用平面类路径运行。 这使得Ceylon像Java一样工作,并且使我们无法进行模块隔离。 - 使用
--export-maven-dependencies
重新导出每个Maven模块的所有依赖关系。 - 使用
overrides.xml
文件来明确指定要重新导出的依赖项。
我们将选择选项3,因为它是最难的。
但是等等-您一定在想-XML吗? 是的,不用担心,我们和您一样讨厌XML。 在锡兰拥有真正的装配体之前,这是权宜之计。 有了程序集后,您将能够在Ceylon程序集描述符中覆盖模块依赖性。
无论如何,在那段漫长的序言之后,我要做的就是将javax.inject
标记为共享依赖项:
<overrides xmlns="http://www.ceylon-lang.org/xsd/overrides">
<module groupId="com.google.inject"
artifactId="guice"
version="4.0">
<share groupId="javax.inject"
artifactId="javax.inject"/>
</module>
</overrides>
非常欢迎您将以上样板复制并粘贴到自己的Ceylon和Guice项目中。
Guice的模块描述符
以下模块描述符从Maven Central获取Guice及其依赖项,并将Guice导入项目中:
native("jvm")
module guicy "1.0.0" {
import "com.google.inject:guice" "4.0";
import ceylon.interop.java "1.2.0";
}
我们可以从Weld示例中重用的代码
由于Guice可以识别javax.inject
定义的inject
注解,因此我们可以重用我们从上文开始的Sender
, Receiver
和PrintingReceiver
的定义。
import javax.inject { inject }
inject class Sender(Receiver receiver) {
shared void send() => receiver.accept("Hello!");
}
interface Receiver {
shared formal void accept(String message);
}
class PrintingReceiver() satisfies Receiver {
accept = print;
}
引导吉士
Guice具有模块对象的概念,该模块对象具有类型到对象的绑定的集合。 与Weld不同,后者会自动扫描我们的模块档案以查找bean,而绑定必须在Guice中显式注册。
import ceylon.interop.java {
type = javaClass
}
import com.google.inject {
AbstractModule,
Guice {
createInjector
},
Injector
}
Injector injector
= createInjector(
object extends AbstractModule() {
shared actual void configure() {
bind(type<Receiver>()).to(type<PrintingReceiver>());
}
});
此代码将实现PrintingReceiver
绑定到接口Receiver
。
获取并调用对象
现在很容易获取并调用容器绑定的Sender
实例:
import ceylon.interop.java {
type = javaClass
}
shared void run() {
value sender = injector.getInstance(type<Sender>());
sender.send();
}
我们再次使用javaClass()
,但是Guice有自己的TypeLiteral
。 (根据记录,CDI从Guice窃取了TypeLiteral
。)
import com.google.inject {
Key,
TypeLiteral
}
shared void run() {
value key = Key.get(object extends TypeLiteral<Sender>(){});
value sender = injector.getInstance(key);
sender.send();
}
构造器注入
注入默认构造函数的工作原理与Weld看起来完全一样。 但是,在Ceylon 1.2.0和Guice 4.0中,注入命名构造函数无效。 这在我们这边很容易解决 ,因此在锡兰1.2.1中应该可以使用。
方法和场注入
Guice的创建者强烈喜欢构造函数注入,正如我们所观察到的那样,在锡兰也更自然。 但是,如果您将注入的场标记为late
,则方法和场注入可以像Weld一样正常工作。
提供者方法
Guice扫描模块对象以查找provides
注释的方法。
import com.google.inject {
AbstractModule,
Guice {
createInjector
},
Injector,
provides
}
Injector injector
= createInjector(
object extends AbstractModule() {
shared actual void configure() {}
provides Receiver createReceiver()
=> object satisfies Receiver {
accept = print;
};
});
我发现这明显不如CDI中的生产者方法可以定义为顶级函数的方法。
绑定注解
Guice的绑定注释的工作原理几乎与CDI限定符注释一样(因为这是CDI从中复制它们的位置)。 定义绑定注释的代码与Weld完全相同。
import javax.inject { qualifier }
annotation Fancy fancy() => Fancy();
final binding annotation class Fancy()
satisfies OptionalAnnotation<Fancy> {}
定义绑定时必须指定限定符批注:
Injector injector
= createInjector(
object extends AbstractModule() {
shared actual void configure() {
bind(type<Receiver>())
.to(type<PrintingReceiver>());
bind(type<Receiver>())
.annotatedWith(Fancy()) //binding annotation
.to(type<FancyReceiver>());
}
});
就像在Weld中一样,限定符注释可用于构造函数或方法注入,但当前不适用于初始化程序参数或字段注入。
范围豆
像CDI一样,Guice也具有范围对象。
import com.google.inject { singleton }
singleton
class PrintingReceiver() satisfies Receiver {
accept = print;
}
我没有时间广泛测试Guice的此功能,但我碰巧知道Guice不使用代理,因此没有必要使用接口代替具体的类。
结论
如果要在Ceylon中进行依赖注入,很明显,您至少有两个不错的选择。
翻译自: https://www.javacodegeeks.com/2015/12/dependency-injection-ceylon-weld-guice.html