cdi-api
介绍
我可能是Spring的忠实拥boy,但我也坚信技术应该包含标准。 尽管Spring现在已经成为事实上的标准,但它正在与其他产品竞争,例如Google Guice。 这使我作为建筑师的工作变得更加艰辛,因为我的工作是设计最长期的解决方案:标准是实现这一目标的盟友。
CDI (以前称为JSR 299)是一种尝试描述依赖注入的真实标准的尝试。 乍一看,CDI的吸引力在于SpringSource和Google都参与了规范团队。 CDI是Java EE 6堆栈的一部分,这意味着在Java EE 6兼容容器中运行的应用程序可以立即利用CDI。
因此,对于Maven应用程序,所需要做的只是添加以下依赖项:
<dependency>
<groupId> javax.enterprise </groupId>
<artifactId> cdi-api </artifactId>
<version> 1.0-SP1 </version>
<scope> provided </scope>
</dependency>
基础
怎么做? 让我们举一个简单的例子。 我仍然想使用轻量级的服务层:我只需要在servlet中引用此类服务即可。 为此,只需添加对服务的引用作为属性并使用@Inject
对其进行注释:
publicclassWeldServletextendsHttpServlet{
@InjectprivateHelloServicehelloService;
...
}
对Guice用户的注意事项:请注意,这是相同的注释,仅在标准包( javax.enterprise
)中。
就这样! 没有花哨的XML配置,没有要使用的JNDI资源,什么也没有。 唯一要做的就是遵循我所谓的Highlander规则:
只可以有一个人。
该规则强制在扩展HelloService
的类路径上必须只有一个类,从而实现了接口协定。 这是常识:注入必须是确定性的,如果要选择的实现不只一个,则不能。
激活
实际上,如果仅使用@Inject
批注,则可能会遇到NullPointerException
。 为了激活CDI,您需要在Web应用程序的WEB-INF或jars的META-INF下有一个名为beans.xml的XML配置文件。 该文件可以为空,但必须使用此文件才能引导CDI。
<?xml version="1.0" encoding="UTF-8"?>
<beansxmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>
资格赛
但是,大多数情况下这种条件并没有得到满足,因为您可能至少会对单元测试使用另一个模拟实现。 一个地方缺少一个概念:它是限定词。 限定词为注入添加了质量,以便在满足所有注入限定词的所有类之间强制执行Highlander规则。 因此,可以向模拟服务添加一个区分限定符来解决我们的难题。 让我们设计这样的限定词:
- 被认为是限定词,它使用
@Qualifier
批注 - 由于注入是在运行时完成的,因此保留策略必须是运行时
- 目标将是类型,因为将被注释的是类本身
结果如下:
@Qualifier
@Retention(RUNTIME)
@Target({TYPE})
public@interfaceMock{}
只需使用@Mock
和presto注释您的模拟服务,注入将再次成功。 我们还没有看到如何注入模拟服务,但是请耐心等待,稍后再解决。
二传手注射
实际上,这种情况不应该真的发生(至少在使用Maven的情况下),因为标准类路径和测试类路径应该不同。 此外,恕我直言,您的单元测试不应使用注入。 这将略微更改我的servlet,使其在标准上下文和单元测试上下文中都可以使用:我将需要能够在前一种情况下进行注入,而在后一种情况下进行手动设置。 这不是问题,因为CDI也接受setter注入,如以下代码片段所示:
publicclassWeldServletextendsHttpServlet{
privateHelloServicehelloService;
@Inject
publicvoidsetHelloService(HelloServicehelloService){
this.helloService=helloService;
}
...
}
更多预选赛
如我们所见,上面的限定符用例不是一个很好的例子。 更好的方法是需要servlet报告错误。 报告这种情况的方法有很多:邮件,日志,SMS等。用于报告的服务将依赖于servlet,这意味着所有服务都应在类路径上可用。 现在,我们已经跟以前看到的@Mock
,每个服务将与注释@Mail
, @Log
, @SMS
等什么,我们没有看到的是如何注入正确的服务。 没有什么比这更容易了,只需通过提供所需的限定符来告诉CDI您需要哪种服务:
publicclassWeldServletextendsHttpServlet{
@Inject@MailprivateReportServicereportService;
...
}
当您不定义任何限定符时,CDI将在@Default
的掩护下为您使用一个@Default
。 这就是为什么仅使用@Mock
注释模拟服务成功的原因:真实的服务使用@Default
隐式注释,这足以满足Higlander规则。
具有属性的预选赛
使用先前的方法可能会导致与Java EE 6的可读性和可维护性目标相抵触的限定符呈指数增长。 CDI仍然可以让您通过注释成员实现这些目标。 现在,这些类是:
publicenumReportType{
MAIL,SMS,LOG;// Adding one more type here is easy
}
@Qualifier
@Retention(RUNTIME)
@Target({TYPE,FIELD})
public@interfaceReport{
ReportTypevalue();
}
@Report(MAIL)
publicclassMailReportServiceImplimplementsReportService{...}
publicclassWeldServletextendsHttpServlet{
@Inject@Report(MAIL)
privateReportServicereportService;
...
}
创建另一个报告服务包括创建服务实现本身并向枚举添加值。
单身人士
传统上,服务是无状态的,因此,对实例化一次以上几乎没有兴趣:使它们成为单例是一种好习惯。 诸如Spring之类的框架将容器管理的bean作为默认实例。 单例创建是CDI的一项功能,但请注意,单例应明确标记为:
@Singleton
publicclassHelloServiceImplimplementsHelloService{
...
}
范围豆
单例只是示波器的特殊情况。 在Java EE中,范围是关于Web应用程序定义的:应用程序,会话,请求和页面。 CDI不管理页面范围,而是添加与JSF关联的对话范围。 CDI中的作用域用法与Spring相似:注入的Bean将绑定到已定义的作用域,并且其可见性限于该作用域。
例如,与设置和首选项相关的信息很可能发生在会话范围的bean中。 只需向此bean添加正确的作用域注释:
@SessionScoped
publicclassUserSettingsimplementsSerializable{
privatestaticfinallongserialVersionUID=1L;
...
}
在这一点上,您可能应该能够满足80%的需求。 下一篇文章将讨论生产者和替代者等高级主题。
更进一步:
cdi-api