锡兰的Weld和Guice依赖注入

我个人对依赖注入的好处持矛盾态度。 一方面,我认识到它在某些容器环境(例如Java EE)中的有用性。 (根据记录,我是JCP专家组的CDI 1.0规范的作者。)另一方面,鉴于我过去几年一直在从事的工作的性质,我实际上并没有在我自己的程序中使用它。

但是有很多人通过依赖注入发誓,问我锡兰在这方面能提供什么。 简短的答案是:没有什么特别的; Ceylon SDK是围绕模块化库的概念构建的。 它既不提供框架,也不提供容器。 这使SDK尽可能地具有通用性,这意味着可以从任何其他容器环境(例如Java EE,vert.x,OSGi或任何其他容器)重用它。

因此,如果您今天想在Ceylon中进行依赖注入,则必须使用用Java编写的容器。 幸运的是,Ceylon 1.2具有与Java的出色互操作性,几乎不会产生任何摩擦。 肯定有一天有人会在锡兰写一个依赖注入容器,但是,正如我们将要看到的那样,根本没有紧迫性。

我将探索:

  • Weld是CDI的参考实现,由Red Hat的同事开发,并且,
  • 为了给“竞争者”平等的时间,谷歌的Guice最初由我的朋友鲍勃·李(Bob Lee)撰写,这是对CDI规范的主要影响之一。

这些是我最喜欢的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重新导出其依赖项的信息。 解决此问题有三种基本策略:

  1. 使用--flat-classpath编译并使用平面类路径运行。 这使得Ceylon像Java一样工作,并且使我们无法进行模块隔离。
  2. 使用--export-maven-dependencies重新导出每个Maven模块的所有依赖关系。
  3. 使用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注解,因此我们可以重用我们从上文开始的SenderReceiverPrintingReceiver的定义。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值