从 Jakarta EE 10 开始,尤其是 Jakarta Concurrency 3.0 和 Jakarta Transactions 2.0,我们可以使用 CDI 来替代@StatelessEJB。
Jakarta Concurrency 3.0 提供了新的@Asynchronous注释以及CronTrigger替代 EJB@Asynchronous和 的帮助程序@Schedule。
Jakarta Transactions 2.0 提供了新的Transactional.TxType枚举来替代 EJB TransactionAttributeType。
如果您希望从 EJB 迁移到 CDI,或者只是需要了解事务性业务服务 bean 的“规范”CDI 方法,那么您可能会发现本指南很有帮助。
将 @Stateless EJB 迁移到 CDI
以下是 EJB 中平均无状态 bean 的样子(注意:为简洁起见,省略了方法参数和返回类型):
package com.example;
import jakarta.ejb.EJB; import jakarta.ejb.Stateless; import jakarta.ejb.TransactionAttribute; import jakarta.ejb.TransactionAttributeType; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext;
@Stateless public class StatelessBeanInEJB {
@PersistenceContext private EntityManager entityManager;
// The @TransactionAttribute(TransactionAttributeType.REQUIRED) annotation is optional; this is the default already. public void transactionalMethod() { // ... }
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public void nonTransactionalMethod() { // ... }
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void independentTransactionalMethod() { // ... }
@TransactionAttribute(TransactionAttributeType.SUPPORTS) public void optionalTransactionalMethod() { // ... } }
|
这里是 CDI 中的等效项,借助 Jakarta Transactions 2.0 中引入的Transactional.TxType枚举,并证明您可以完美地继续使用EntityManager相同的方式:package com.example;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.transaction.Transactional; import jakarta.transaction.Transactional.TxType;
@ApplicationScoped public class StatelessBeanInCDI {
@PersistenceContext private EntityManager entityManager;
@Transactional // The annotation value TxType.REQUIRED is optional; this is the default already. public void transactionalMethod() { // ... }
@Transactional(TxType.NOT_SUPPORTED) public void nonTransactionalMethod() { // ... }
@Transactional(TxType.REQUIRES_NEW) public void independentTransactionalMethod() { // ... }
@Transactional(TxType.SUPPORTS) public void optionalTransactionalMethod() { // ... } }
|
值得注意的是,@StatelessEJB 中还有一个特性:池化。CDI 中没有这样的等价物,而且实际上也不需要它,因为无状态 CDI bean 已被标记,@ApplicationScoped并且 CDI 实例不同步,而 EJB 实例已同步。如果您需要一个可能保存状态的业务服务 bean,因此您不得不标记它@RequestScoped,并且您想降低构建成本,那么您可以考虑为此使用OmniServices@Pooled实用程序库的注释。使用池的另一个原因是“节流”,这样后端访问在业务服务 bean 级别上就被 FIFO 队列所保护(尽管它也可以在 HTTP 服务器、JDBC 连接池和 DB 级别上被节流)。CDI 中也没有这样的等效项,但根据问题 136 ,即将推出的 Jakarta Concurrency 3.2 for Jakarta EE 12 可能会为此提供注释@MaxConcurrency,这可能是件好事。我的建议是,衡量就是了解。自 EJB(1998 年!)推出以来,硬件和 JVM(尤其是垃圾收集)功能取得了长足的进步,人们应该想知道,在业务服务级别进行节流和/或减少构造函数调用是否在当今仍然是绝对必要的。
还应注意的是,@Transactional还支持作为类级注释。因此,这为创建新的 CDI 注释提供了可能性,@Stereotype例如,@Service它基本上将@ApplicationScoped和合并@Transactional为单个注释,更接近于的行为@Stateless。
将 EJB @Asynchronous 迁移到 CDI
EJB 中的平均@Asynchronous方法如下所示:
package com.example;
import java.util.concurrent.Future;
import jakarta.ejb.AsyncResult; import jakarta.ejb.Asynchronous; import jakarta.ejb.EJB; import jakarta.ejb.Stateless; import jakarta.ejb.TransactionAttribute; import jakarta.ejb.TransactionAttributeType; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext;
@Stateless public class StatelessBeanInEJB {
@PersistenceContext private EntityManager entityManager;
@Asynchronous @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public Future<Void> asyncTransactionalMethod(YourEntity yourEntity) { // ... return new AsyncResult<>(null); }
@Asynchronous @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public Future<YourEntity> asyncNonTransactionalMethod() { // ... return new AsyncResult<>(yourEntity); } }
|
以下是 CDI 中的等效项,借助 Jakarta Concurrency 3.0 引入的@Asynchronous注释:package com.example;
import java.util.concurrent.CompletableFuture;
import jakarta.enterprise.concurrent.Asynchronous; import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.transaction.Transactional; import jakarta.transaction.Transactional.TxType;
@ApplicationScoped public class StatelessBeanInCDI {
@PersistenceContext private EntityManager entityManager;
@Asynchronous @Transactional(TxType.REQUIRES_NEW) public CompletableFuture<Void> asyncTransactionalMethod(YourEntity yourEntity) { // ... return Asynchronous.Result.complete(null); }
@Asynchronous @Transactional(TxType.NOT_SUPPORTED) public CompletableFuture<YourEntity> asyncNonTransactionalMethod() { // ... return Asynchronous.Result.complete(yourEntity); } }
|
导入注释时要小心 IDE 自动完成@Asynchronous!为了使其在 CDI 托管 bean 中工作,它需要来自jakarta.enterprise.concurrent包而不是jakarta.ejb包。将 @Startup EJB 迁移到 CDI
EJB 中的平均启动 bean 如下所示:
package com.example;
import jakarta.annotation.PostConstruct; import jakarta.ejb.Singleton; import jakarta.ejb.Startup;
@Startup @Singleton public class StartupBeanInEJB {
@PostConstruct public void init() { // ... } } 以下是 CDI 中的等效功能,借助 CDI 4.0 引入的Startup事件:
package com.example;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.event.Observes; import jakarta.enterprise.event.Startup;
@ApplicationScoped public class StartupBeanInCDI {
public void init(@Observes Startup startup) { // ... } }
|
您需要记住,@ApplicationScoped与 不同, 不是读写锁定的@Singleton,尽管许多人不需要它,只需@Singleton通过附加@ConcurrencyManagement(BEAN)注释解锁 。如果读写锁定实际上是一项技术要求,通常是为了避免业务服务级别的数据库死锁,那么您可能需要考虑为此使用OmniServices@Lock实用程序库的注释。注释的等效项也缺少 CDI,但目前有一个未解决的问题,将其包含在即将推出的 Jakarta Concurrency 3.2 for Jakarta EE 12 中:问题 135。@Lock如果启动 CDI bean 恰好是 WAR 而不是 JAR 的一部分,并且您恰好已经使用OmniFaces,那么您也可以使用它@Eager来代替@Observes Startup。
将 EJB @Schedule 迁移到 CDI
EJB 中的平均后台任务调度程序如下所示:
package com.example;
import jakarta.ejb.Schedule; import jakarta.ejb.Singleton;
@Singleton public class ScheduledTasksBeanInEJB {
@Schedule(hour="0", minute="0", second="0", persistent=false) public void someDailyJob() { // ... runs daily at midnight }
@Schedule(hour="*", minute="0", second="0", persistent=false) public void someHourlyJob() { // ... runs every hour of the day }
@Schedule(hour="*", minute="*/15", second="0", persistent=false) public void someQuarterlyJob() { // ... runs every 15th minute of the hour }
@Schedule(hour="*", minute="*", second="*/5", persistent=false) public void someFiveSecondelyJob() { // ... runs every 5th second of the minute } }
|
以下是 CDI 中的等效功能,借助 Jakarta Concurrency 3.0 引入的CronTrigger帮助程序:package com.example;
import java.time.ZoneId;
import jakarta.annotation.Resource; import jakarta.enterprise.concurrent.CronTrigger; import jakarta.enterprise.concurrent.ManagedScheduledExecutorService; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.event.Observes; import jakarta.enterprise.event.Startup;
@ApplicationScoped public class ScheduledTasksBeanInCDI {
@Resource private ManagedScheduledExecutorService scheduler;
public void init(@Observes Startup startup) { scheduler.schedule(this::someDailyJob, new CronTrigger(ZoneId.systemDefault()).hours("0").minutes("0").seconds("0")); scheduler.schedule(this::someHourlyJob, new CronTrigger(ZoneId.systemDefault()).hours("*").minutes("0").seconds("0")); scheduler.schedule(this::someQuarterlyJob, new CronTrigger(ZoneId.systemDefault()).hours("*").minutes("*/15").seconds("0")); scheduler.schedule(this::someFiveSecondelyJob, new CronTrigger(ZoneId.systemDefault()).hours("*").minutes("*").seconds("*/5")); }
public void someDailyJob() { // ... runs daily at midnight }
public void someHourlyJob() { // ... runs every hour of the day }
public void someQuarterlyJob() { // ... runs every 15th minute of the hour }
public void someFiveSecondelyJob() { // ... runs every 5th second of the minute } }
|
是的,它非常冗长。幸运的是,Jakarta Concurrency 3.1(Jakarta EE 11 的一部分)将对此进行改进。@Asynchronous(runAt = @Schedule(...))理论上,您可以简单地使用以下方法:package com.example;
import jakarta.enterprise.concurrent.Asynchronous; import jakarta.enterprise.concurrent.Schedule; import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped public class ScheduledTasksBeanInCDI {
@Asynchronous(runAt = @Schedule()) public void someDailyJob() { // ... runs daily at midnight }
@Asynchronous(runAt = @Schedule(hours = {})) public void someHourlyJob() { // ... runs every hour of the day }
@Asynchronous(runAt = @Schedule(hours = {}, minutes = { 0, 15, 30, 45 })) public void someQuarterlyJob() { // ... runs every 15th minute of the hour }
@Asynchronous(runAt = @Schedule(hours = {}, minutes = {}, seconds = { 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55 })) public void someFiveSecondelyJob() { // ... runs every 5th second of the minute } }
|
它还支持 cron 表达式字符串,遵循 API 规则CronTrigger:package com.example;
import jakarta.enterprise.concurrent.Asynchronous; import jakarta.enterprise.concurrent.Schedule; import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped public class ScheduledTasksBeanInCDI {
@Asynchronous(runAt = @Schedule(cron = "0 0 0 * * *")) public void someDailyJob() { // ... runs daily at midnight }
@Asynchronous(runAt = @Schedule(cron = "0 0 * * * *")) public void someHourlyJob() { // ... runs every hour of the day }
@Asynchronous(runAt = @Schedule(cron = "0 */15 * * * *")) public void someQuarterlyJob() { // ... runs every 15th minute of the hour }
@Asynchronous(runAt = @Schedule(cron = "*/5 * * * * *")) public void someFiveSecondelyJob() { // ... runs every 5th second of the minute } }
|
重申一下,您需要记住@ApplicationScoped,与 不同, 不是读写锁定的@Singleton。如果这是技术要求,那么您可能需要考虑使用OmniServices@Lock实用程序库的注释来实现这一点。@Stateful EJB 怎么样?
它从未在基于 Web 的应用程序中派上用场,它只在 CORBA/RMI 中有用,而如今 Web 服务已经完全淘汰了 CORBA/RMI,所以忘掉它吧。最好将其迁移到无状态@ApplicationScopedbean,必要时结合@SessionScoped甚至@ViewScoped托管 bean 来跟踪客户端的状态数据。