首先让我做一些澄清:
托管 bean 定义:通常托管 bean 是一个对象,其生命周期(构造、销毁等)由容器管理。
在 Java ee 中,我们有许多容器来管理其对象的生命周期,例如 JSF 容器、EJB 容器、CDI 容器、Servlet 容器等。
所有这些容器都是独立工作的,它们在应用程序服务器初始化中启动,并在部署时扫描所有工件的类,包括 jar、ejb-jar、war 和 ear 文件,并收集和存储一些关于它们的元数据,然后当你需要一个对象时在运行时,他们会给你这些类的实例,完成工作后,他们会销毁它们。
所以我们可以说我们有:
- JSF 托管 bean
- CDI 托管 bean
- EJB 托管 bean
- 甚至 Servlet 也是托管 bean,因为它们是由一个容器实例化和销毁的,该容器是一个 servlet 容器。
因此,当您看到 Managed Bean 一词时,您应该询问它的上下文或类型。(JSF、CDI、EJB 等)
然后你可能会问为什么我们有很多这样的容器:AFAIK,Java EE 的人想要一个依赖注入框架,但他们无法将所有需求收集到一个规范中,因为他们无法预测未来的需求,他们制作了 EJB 1.0,然后2.0,然后是 3.0,现在是 3.1,但 EJB 的目标只是满足一些需求(事务、分布式组件模型等)。
同时(同时)他们意识到他们也需要支持 JSF,然后他们制作了 JSF 托管 bean 和另一个 JSF bean 容器,他们认为它是一个成熟的 DI 容器,但它仍然不是完整和成熟的容器。
在那之后,Gavin King 和其他一些好人;) 制作了 CDI,这是我见过的最成熟的 DI 容器。CDI(受 Seam2、Guice 和 Spring 启发)是为了填补 JSF 和 EJB 之间的空白以及许多其他有用的东西,如 pojo 注入、生产者方法、拦截器、装饰器、集成 SPI、非常灵活等,它甚至可以做EJB 和 JSF 托管 bean 正在做什么,那么我们就可以拥有一个成熟且强大的 DI 容器。但是出于一些向后兼容性和政治原因,Java EE 的人想要保留它们!!!
在这里,您可以找到每种类型的区别和用例:
JSF 托管 Bean、CDI Bean 和 EJB
JSF 最初是使用自己的托管 bean 和依赖注入机制开发的,该机制在 JSF 2.0 中得到了增强,包括基于注释的 bean。当 CDI 与 Java EE 6 一起发布时,它被认为是该平台的托管 bean 框架,当然,EJB 已经过时了,它们已经存在了十多年。
问题当然是知道使用哪一个以及何时使用它们。
让我们从最简单的 JSF 托管 bean 开始。
JSF 托管 Bean
简而言之,如果您正在为 Java EE 6 开发并使用 CDI,请不要使用它们。它们为依赖注入和为网页定义支持 bean 提供了一种简单的机制,但它们远不如 CDI bean 强大。
它们可以使用@javax.faces.bean.ManagedBean
带有可选名称参数的注释来定义。此名称可用于从 JSF 页面引用 bean。
范围可以使用javax.faces.bean
包中定义的不同范围之一应用于 bean,包括请求、会话、应用程序、视图和自定义范围。
@ManagedBean(name="someBean")
@RequestScoped
public class SomeBean {
....
....
}
如果没有某种手动编码,JSF bean 不能与其他类型的 bean 混合。
CDI 豆
CDI 是作为 Java EE 6 的一部分发布的 bean 管理和依赖注入框架,它包括一个完整、全面的托管 bean 工具。CDI bean 比简单的 JSF 托管 bean 更加先进和灵活。他们可以使用拦截器、会话范围、事件、类型安全注入、装饰器、原型和生产者方法。
要部署 CDI bean,您必须将名为 beans.xml 的文件放在类路径上的 META-INF 文件夹中。一旦执行此操作,包中的每个 bean 都将成为 CDI bean。CDI 中有很多特性,这里就不一一赘述了,但作为 JSF 类特性的快速参考,您可以使用javax.enterprise.context
包中定义的范围之一定义 CDI bean 的范围(即请求、会话,会话和应用范围)。如果要使用 JSF 页面中的 CDI bean,可以使用javax.inject.Named
注释为其命名。要将一个 bean 注入到另一个 bean 中,请使用注解对字段进行javax.inject.Inject
注解。
@Named("someBean")
@RequestScoped
public class SomeBean {
@Inject
private SomeService someService;
}
像上面定义的自动注入可以通过使用限定符来控制,它可以帮助匹配你想要注入的特定类。如果您有多种付款类型,您可以添加一个限定符来判断它是否是异步的。虽然您可以将@Named
注释用作限定符,但您不应该使用它,因为它是为在 EL 中公开 bean 而提供的。
CDI 通过使用代理来处理具有不匹配范围的 bean 的注入。因此,您可以将请求范围的 bean 注入到会话范围的 bean 中,并且该引用对每个请求仍然有效,因为对于每个请求,代理都会重新连接到请求范围 bean 的实时实例。
CDI 还支持拦截器、事件、新的会话范围和许多其他特性,这使其成为比 JSF 托管 bean 更好的选择。
EJB
EJB 早于 CDI bean,在某些方面与 CDI bean 相似,而在其他方面则非常不同。首先,CDI bean 和 EJB 之间的区别在于 EJB 是:
- 事务性的
- 远程或本地
- 能够钝化有状态的 bean 以释放资源
- 能够使用定时器
- 可以是异步的
这两种类型的 EJB 被称为无状态和有状态。无状态 EJB 可以被认为是线程安全的一次性 bean,它不维护两个 Web 请求之间的任何状态。有状态的 EJB 确实保持状态,并且可以在需要时被创建和保留,直到它们被处理掉。
定义 EJB 很简单,您只需在类中添加一个javax.ejb.Stateless
或javax.ejb.Stateful
注释。
@Stateless
public class BookingService {
public String makeReservation(Item Item, Customer customer) {
...
...
}
}
无状态 bean 必须具有依赖范围,而有状态会话 bean 可以具有任何范围。默认情况下它们是事务性的,但您可以使用事务属性注释。
虽然 EJB 和 CDI bean 在特性方面非常不同,但编写代码来集成它们非常相似,因为 CDI bean 可以注入到 EJB 中,而 EJB 可以注入到 CDI bean 中。将一种注入另一种时无需区分。同样,CDI 通过使用代理来处理不同的范围。一个例外是 CDI 不支持远程 EJB 的注入,但可以通过为其编写一个简单的生产者方法来实现。
javax.inject.Named
注释以及任何限定符都可以在 EJB 上使用,以将其与注入点匹配。
什么时候使用哪个bean
你怎么知道什么时候使用哪个bean?简单的。
除非您在 servlet 容器中工作并且不想尝试让 CDI 在 Tomcat 中工作,否则永远不要使用 JSF 托管 bean(尽管有一些 Maven 原型,所以没有任何借口)。
通常,您应该使用 CDI bean,除非您需要 EJB 中可用的高级功能,例如事务功能。您可以编写自己的拦截器来使 CDI bean 事务化,但是现在,使用 EJB 更简单,直到 CDI 获得即将到来的事务性 CDI bean。如果您被困在 servlet 容器中并使用 CDI,那么手写事务或您自己的事务拦截器是没有 EJB 的唯一选择。
如果你需要@ViewScoped
在 CDI 中使用,你应该
- 使用seam-faces或MyFaces CODI模块。只需将其中一个添加到您的类路径中即可
@ViewScoped
在 CDI 中工作。MyFaces CODI 对@ViewScoped 的支持更加稳固
- 使用 MyFaces CODI's
@ViewAccessScoped
,它是 Apache 在 CDI 之上编写的扩展,只需下载它并使用@ViewAccessScoped
注释而不是@ViewScoped
.
- 使用 CDI
@ConversationScoped
并使其长期运行。请参阅此处了解更多信息。
- 使用Omnifaces @ViewScoped 注解
有些零件是从这里偷来的。