原文:Dependency Injection in Java EE 6 – Part 1
作者:Reza Rahman
出处:http://www.theserverside.com/tt/articles/article.tss?l=DependencyInjectioninJavaEE6
这一文章系列介绍了Java EE的上下文和依赖注入(Contexts and Dependency Injection for Java EE,CDI),CDI是即将完成的Java EE 6平台的关键组成部分,经由JSR 299进行标准化。CDI是Java EE整个下一代类型安全的依赖注入的事实上的API。JSR 299由Gavin King领导,其目标是综合来自诸如Seam、Guice和Spring一类的解决方案的最好的依赖注入功能,同时加入许多自己的有用创新。
本文是文章系列的第一篇,我们打算从一个高层面来研究CDI,看看它是如何与Java EE整体相配合的,并讨论基础的依赖管理及作用域。在这一系列文章的介绍过程中,我们会涉及组件命名、版型(stereotype)、生产者(producer)、处置者(disposer)、装饰器(decorator)、拦截器(interceptor)、事件、用于可移植扩展的CDI API等,以及更多诸如此类的功能。我们还将讨论CDI如何与Seam、Spring以及Guice保持一致,并通过一些使用CanDI的实现细节来补充这一讨论。CanDI是Caucho对JSR299的独立实现,被收入到Resin应用服务器中。
快速回顾
Java EE 5的主要关注点是凭借POJO编程、注解和约定高于配置(convention-over-configuration)等方面来使得自身易于使用,Java EE 5中确实有依赖注入的基本形式,也许对其最恰当的术语称谓是资源注入(resource injection)。具体来说,你可以借助@Resource、@PersistenceContext、@PersistenceUnit和@EJB等注解把JMS连接工厂、数据源、队列、JPA实体管理器、实体管理器工厂和EJB一类的容器资源注入到Servlet、JSF后台bean(JSF backing bean)和其他的EJB中。这种模型适用于包含了被写成EJB和JSF的JPA领域对象、服务和DAO的应用。
不过,为了一些用例,你不得不求助于像Seam、Spring或Guice一类的更通用的依赖注入技术,例如,你不能把EJB注入到Struts Action或者JUnit测试中,以及不能注入因为不需要事务而没有被编写成EJB的DAO或者助手(helper)类中;另外,很难整合第三方/内部的API或者是使用Java EE 5作为构建这种并非仅仅是严格的业务组件的API的基础。而这些正好是CDI旨在解决的那一类问题,CDI以一种很好地吻合了Java理念的高度类型安全、稳定及可移植的方式来解决这些问题。实际上,Resin容器本身的许多部分都是利用JSR 299这一对Java EE 5来说是难以想象的功能标准来编写的!
如果你很熟悉Spring IoC的话,那么CDI给人的感觉可能是更加类型安全、更未来派的和更加的注释驱动;Seam开发人员则会发现CDI具有更多先进的功能;与Guice比起来,CDI可能更加适合于企业级的开发。最后一点, Java EE 5开发人员一开始可能会觉得CDI更复杂,不过会发现绝大多数的复杂性都是有充分理由的,而且在不需要时可以忽略掉。
严格地说,除了依赖注入的中心功能之外,CDI从两个更重要的方面增强了Java EE的编程模型——两方面都来自Seam。首先,其允许直接使用EJB作为JSF后台bean,这一次我们不会涉及这一主题,不过在这一系列接下来的文章中我们会研究这一做法如何能够从表现层中除去一些常见的粘合代码,并大大增强了Java EE API的凝聚力。其次,CDI允许以一种更声明化的方式来管理对象的作用域、状态、生命周期和上下文,而不是以大多数面向web框架处理请求、会话及应用范围内的管理对象的编程方式,我们会在这一系列接下来的文章的展示CDI与JSF交互的行为过程中看到这一点。
名称的含义 JSR 299的最后这两个方面是其原来的范围,这就是它为什么最初被称作“WebBeans”的缘故。正如其结果证明,目前形式的JSR 299,其包含的远远超出了这个最初的、涵盖了一般意义上的整合和依赖注入关注的范围。这种范围的逐渐变化反映在其目前的、更适合的名称上:Java EE的上下文和依赖注入(Contexts and Dependency Injection for Java EE),CDI中的这一“范围蔓延”一直是个存在争议的话题,但在事物发展的过程中,这可能也算不了什么大的事情…… |
零件是如何被拼装在一起的
重要的是要了解CDI在哪些方面与Java EE进行了整体的配合,以便正确地把握它的作用以及它是如何实现这一作用的。CDI没有自己的组件模型,它实际上是一组被诸如受管bean、Servlet以及EJB一类的Java EE组件消费的服务。
受管bean(managed bean)是Java EE 6引入的一个关键概念,用以解决一些我们在前一节中讨论到的Java EE 5风格的资源注入的局限。受管bean仅仅是Java EE环境中一个极简单的Java对象,不同于Java对象的语义,它有明确定义的创建/销毁生命周期,因此可以借助@PostConstruct和@PreDestroy注解来获取生命周期的回调方法。受管bean可通过@ManagedBean注解来显式地进行标记,但这并不总是需要的,特别是对CDI来说。从CDI的角度来看,这意味几乎任何的Java对象都可以当作受管bean来看待,因此这些对象完全可以参与依赖注入。
实际上,受管bean的目标是成为所有Java EE组件的基石,传统的JSF后台bean现在是受管bean(可能是使用@ManagedBean来注释)。所有的EJB会话bean现在也被重新定义成提供额外服务(缺省是线程安全和事务性的)的受管bean。Servlet尚未被重新定义成受管bean,不过这很有可能会在不久的将来实现。大体来讲,所有其他的Java EE API将会添加基础受管bean概念的各类服务,主要是借助声明式注解的方式。
EJB组件模型是必需的吗? 询问EJB是否需要它自己的、形式如在@Stateless、@Stateful、@Singleton和@MessageDriven等注解中选一的这一组件模型,这无疑是一个有趣的问题。毕竟,不能也不会像位于受管bean之上的业务组件服务那样简单地进行定位不是?Resin赞同这一观点,允许在非EJB的受管bean上使用诸如@TransactionAttribute、@Remote、@DeclareRoles、@RolesAllowed、@Asynchronous、@Schedule和@Lock一类的EJB声明式服务注解(当然你可以以一种标准已定义的方式来使用EJB)。我们相信这是EJB规范在未来能够很容易往前走的一个方向。 |
正如我们之前提到的,除了为各种类型的受管bean提供依赖注入服务之外,CDI还通过Facelet和JSP这一类视图技术的EL bean名称解析以及自动化的作用域管理来集成JSF。CDI与JPA的集成除了把@EJB和@Resource包括进来之外,还支持使用@PersistenceContext和@PersistenceUnit注入注解。需要注意的是,CDI并不直接支持诸如事务、安全、远程、消息以及其他类似的EJB规范范围内的业务组件服务。
JSR 299利用Java依赖注入(Dependency Injection for Java, JSR330)规范作为它的基础API,主要是使用JSR 330的注解,例如@Inject、@Qualifier和@ScopeType等。JSR 330由Rod Johnson和Bob Lee领导,其极简地定义了依赖注入解决方案的API,主要着眼于非Java EE环境。比较特别的地方是,它没有定义服务器端Java常见的作用域类型,例如请求、会话(session)以及业务会话(conversation)等。它也没有定义与JPA、EJB和JSF这一类Java EE API之间具体的整合语义。CDI基本上是使JSR 330能够适应于Java EE环境,同时还添加了一些对企业级应用有用的附加功能。图1展示了CDI是如何与Java EE平台上的主要API协调工作的。