The Difference Between CDI and EJB Singleton – CDI和EJB Singleton的区别

最后修改: 2019年 5月 20日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll take a closer look at two types of singletons available in Jakarta EE. We’ll explain and demonstrate the differences and see the usages suitable for each one.

在本教程中,我们将仔细研究Jakarta EE中可用的两种类型的singletons。我们将解释和演示它们的区别,并看看适合每一种类型的用途。

First, let’s see what singletons are all about before getting into the details.

首先,在讨论细节之前,让我们看看单体是什么一回事。

2. Singleton Design Pattern

2.Singleton设计模式

Recall that a common way to implement Singleton Pattern is with a static instance and private constructor:

回顾一下,实现Singleton模式的一种常见方式是使用静态实例和私有构造函数。

public final class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}

But, alas, this isn’t really object-oriented. And it has some multi-threading issues.

但是,可惜的是,这并不是真正的面向对象。而且它有一些多线程问题

CDI and EJB containers give us an object-oriented alternative, though.

虽然CDI和EJB容器给了我们一个面向对象的选择。

3. CDI Singleton

3.CDI单体

With CDI (Contexts and Dependency Injection), we can easily create singletons using the @Singleton annotation. This annotation is a part of the javax.inject package. It instructs the container to instantiate the singleton once and passes its reference to other objects during the injection.

通过CDI(Contexts and Dependency Injection),我们可以使用@Singleton注解轻松创建单子。这个注解是javax.injection包的一部分。它指示容器将单子实例化一次,并在注入期间将其引用传递给其他对象。

As we can see, singleton implementation with CDI is very simple:

我们可以看到,用CDI实现单子是非常简单的。

@Singleton
public class CarServiceSingleton {
    // ...
}

Our class simulates a car service shop. We have a lot of instances of various Cars, but they all use the same shop for servicing. Therefore, Singleton is a good fit.

我们的班级模拟了一个汽车维修店。我们有很多不同的Cars的实例,但它们都使用同一个商店进行维修。因此,Singleton是一个很好的选择。

We can verify it is the same instance with a simple JUnit test that asks the context for the class twice. Note that we’ve got a getBean helper method here for readability:

我们可以通过一个简单的JUnit测试来验证它是否是同一个实例,该测试会询问该类的上下文。注意,我们在这里有一个getBean辅助方法,以便于阅读。

@Test
public void givenASingleton_whenGetBeanIsCalledTwice_thenTheSameInstanceIsReturned() {       
    CarServiceSingleton one = getBean(CarServiceSingleton.class);
    CarServiceSingleton two = getBean(CarServiceSingleton.class);
    assertTrue(one == two);
}

Because of the @Singleton annotation, the container will return the same reference both times. If we try this with a plain managed bean, however, the container will provide a different instance each time.

由于@Singleton注解的存在,容器将两次返回相同的引用。然而,如果我们用普通的托管 Bean 来尝试这样做,那么容器每次都将提供一个不同的实例。

And while this works the same for either javax.inject.Singleton or javax.ejb.Singleton, there’s a key difference between these two.

虽然这对javax.inject.Singletonjavax.ejb.Singleton都有同样的作用,这两者之间有一个关键的区别。

4. EJB Singleton

4.EJB Singleton

To create an EJB singleton we use the @Singleton annotation from the javax.ejb package. This way we create a Singleton Session Bean.

为了创建EJB单子,我们使用@Singleton注解,来自javax.ejb包。这样我们就创建了一个Singleton Session Bean

We can test this implementation the same way we tested the CDI implementation in the previous example, and the result will be the same. EJB singletons, as expected, provide the single instance of the class.

我们可以用前面例子中测试CDI实现的方法来测试这个实现,结果是一样的。EJB的单子,正如预期的那样,提供了类的单一实例。

However, EJB Singletons also provide additional functionality in the form of container-managed concurrency control.

然而,EJB Singletons还以容器管理的并发控制的形式提供额外的功能。

When we use this type of implementation, the EJB container ensures that every public method of the class is accessed by a single thread at a time. If multiple threads try to access the same method, only one thread gets to use it while others wait for their turn.

当我们使用这种类型的实现时,EJB容器确保类的每个公共方法一次只能由一个线程访问。如果多个线程试图访问同一个方法,只有一个线程可以使用它,而其他线程则在等待轮到自己。

We can verify this behavior with a simple test. We’ll introduce a service queue simulation for our singleton classes:

我们可以通过一个简单的测试来验证这一行为。我们将为我们的单子类引入一个服务队列模拟。

private static int serviceQueue;

public int service(Car car) {
    serviceQueue++;
    Thread.sleep(100);
    car.setServiced(true); 
    serviceQueue--;
    return serviceQueue;
}

serviceQueue is implemented as a plain static integer which increases when a car “enters” the service and decreased when it “leaves”. If proper locking is provided by the container, this variable should be equal to zero before and after the service, and equal to one during the service.

serviceQueue被实现为一个普通的静态整数,当汽车 “进入 “服务时它会增加,当汽车 “离开 “时它会减少。如果容器提供了适当的锁定,这个变量在服务前后都应该等于0,在服务期间等于1。

We can check that behavior with a simple test:

我们可以通过一个简单的测试来检查这一行为。

@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;
}

This test starts 10 parallel threads. Each thread instantiates a car and tries to service it. After the service, it asserts that the value of the serviceQueue is back to zero.

这个测试启动了10个平行线程。每个线程实例化一辆汽车,并尝试为其提供服务。服务结束后,它断言serviceQueue的值回到了零。

If we, for instance, execute a similar test on the CDI singleton, our test will fail.

例如,如果我们在CDI单体上执行一个类似的测试,我们的测试就会失败。

5. Conclusion

5.结论

In this article, we went through two types of singleton implementations available in Jakarta EE. We saw their advantages and disadvantages and we also demonstrated how and when to use each one.

在这篇文章中,我们浏览了Jakarta EE中的两种类型的单子实现。我们看到了它们的优点和缺点,我们还演示了如何以及何时使用每一种。

And, as always, the complete source code is available over on GitHub.

而且,像往常一样,完整的源代码可以在GitHub上获得