1. 概述
本文将深入探讨 Jakarta EE 中提供的两种 Singleton 实现:CDI Singleton 与 EJB Singleton。我们将通过示例代码说明它们的差异,并指出在何种场景下更适合使用哪一种。
2. Singleton 设计模式回顾
我们都知道,传统的 Singleton 模式通常是通过私有构造器和静态实例实现的:
public final class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
这种方式虽然简单,但并不是面向对象的做法,而且在多线程环境下还可能存在并发问题。
幸运的是,CDI 和 EJB 容器为我们提供了更优雅、更符合现代 Java EE 风格的 Singleton 实现方式。
3. CDI Singleton
使用 CDI(Contexts and Dependency Injection) 创建 Singleton 非常简单,只需使用 @Singleton
注解,它属于 javax.inject
包。
@Singleton
public class CarServiceSingleton {
// ...
}
我们模拟了一个汽车服务中心类。在这个场景中,有很多不同的 Car
实例,但它们都使用同一个服务中心。Singleton 模式非常适合这种场景。
我们可以通过一个简单的 JUnit 测试来验证是否返回的是同一个实例:
@Test
public void givenASingleton_whenGetBeanIsCalledTwice_thenTheSameInstanceIsReturned() {
CarServiceSingleton one = getBean(CarServiceSingleton.class);
CarServiceSingleton two = getBean(CarServiceSingleton.class);
assertTrue(one == two);
}
✅ 测试通过,说明容器确实返回了同一个引用。
⚠️ 注意: 如果不加 @Singleton
注解,容器每次都会创建一个新的实例。
虽然 javax.inject.Singleton
和 javax.ejb.Singleton
都能实现单例,但它们的行为和特性有显著差异。
4. EJB Singleton
使用 EJB 创建 Singleton 时,我们需要使用 @Singleton
注解,这个注解属于 javax.ejb
包。
@Singleton
public class CarServiceEjbSingleton {
private static int serviceQueue;
public int service(Car car) {
serviceQueue++;
Thread.sleep(100);
car.setServiced(true);
serviceQueue--;
return serviceQueue;
}
}
我们可以通过与 CDI Singleton 类似的测试方式验证其行为,结果也会是同一个实例。
❌ 但 EJB Singleton 的优势在于它支持容器管理的并发控制。
当多个线程同时访问 EJB Singleton 的公共方法时,EJB 容器会自动确保每次只有一个线程可以执行该方法。其余线程将排队等待。
我们可以用下面的测试来验证这种行为:
@Test
public void whenEjb_thenLockingIsProvided() {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int serviceQueue = carServiceEjbSingleton.service(new Car("Speedster xyz"));
assertEquals(0, serviceQueue);
}
}).start();
}
return;
}
✅ 测试通过,说明 EJB 容器确实提供了线程同步机制。
⚠️ 如果我们将相同的测试应用于 CDI Singleton,测试将失败。
5. 总结
本文我们介绍了两种在 Jakarta EE 中常用的 Singleton 实现方式:
类型 | 来源包 | 是否支持并发控制 | 适用场景 |
---|---|---|---|
CDI Singleton | javax.inject |
❌ 不支持 | 简单的共享实例、无并发要求的场景 |
EJB Singleton | javax.ejb |
✅ 支持 | 需要并发控制、事务管理等高级功能的场景 |
✅ 选择建议:
- 如果只是共享一个实例,不需要并发控制,用 CDI Singleton 简单粗暴。
- 如果需要线程安全、事务支持等企业级功能,推荐使用 EJB Singleton。
完整示例代码可在 GitHub 上找到。