Singleton Session Bean in Jakarta EE – 在Jakarta EE中的Singleton Session Bean

最后修改: 2018年 5月 20日

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

1. Overview

1.概述

Whenever a single instance of a Session Bean is required for a given use-case, we can use a Singleton Session Bean.

每当一个特定的用例需要一个会话Bean的单一实例时,我们可以使用一个单子会话Bean。

In this tutorial, we’re going to explore the this through an example, with a Jakarta EE application.

在本教程中,我们将通过一个例子,用一个Jakarta EE应用程序来探索这个问题。

2. Maven

2.Maven

First of all, we need to define required Maven dependencies in the pom.xml.

首先,我们需要在pom.xml中定义所需的Maven依赖项。

Let’s define the dependencies for the EJB APIs and Embedded EJB container for deployment of the EJB:

让我们定义EJB APIs和嵌入式EJB容器的依赖关系,以便部署EJB。

<dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-api</artifactId>
    <version>8.0</version>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>org.apache.openejb</groupId>
    <artifactId>tomee-embedded</artifactId>
    <version>1.7.5</version>
</dependency>

Latest versions can be found on Maven Central at JavaEE API and tomEE.

最新版本可在Maven中心的JavaEE APItomEE找到。

3. Types of Session Beans

3.会话Bean的类型

There are three types of Session Beans. Before we explore Singleton Session Beans, let’s see what is the difference between the lifecycles of the three types.

有三种类型的会话Bean。在我们探讨Singleton Session Beans之前,让我们看看这三种类型的生命周期有什么区别。

3.1. Stateful Session Beans

3.1.有状态的会话Bean

A Stateful Session Bean maintains the conversational state with the client it is communicating.

一个有状态的会话Bean维护着与它正在通信的客户的对话状态。

Each client creates a new instance of Stateful Bean and is not shared with other clients.

每个客户都会创建一个新的Stateful Bean实例,并且不与其他客户共享。

When the communication between the client and bean ends, the Session Bean also terminates.

当客户端和Bean之间的通信结束时,Session Bean也就终止了。

3.2. Stateless Session Beans

3.2.无状态会话Bean

A Stateless Session Bean doesn’t maintain any conversational state with the client. The bean contains the state specific to the client only till the duration of method invocation.

无状态会话Bean不与客户端保持任何对话状态。会话Bean仅在方法调用的过程中包含客户的特定状态。

Consecutive method invocations are independent unlike with the Stateful Session Bean.

与有状态会话Bean不同,连续的方法调用是独立的。

The container maintains a pool of Stateless Beans and these instances can be shared between multiple clients.

容器维护着一个无状态Bean的池子,这些实例可以在多个客户端之间共享。

3.3. Singleton Session Beans

3.3.单元会话Bean

A Singleton Session Bean maintains the state of the bean for the complete lifecycle of the application.

单子会话Bean在应用程序的整个生命周期中维护Bean的状态。

Singleton Session Beans are similar to Stateless Session Beans but only one instance of the Singleton Session Bean is created in the whole application and does not terminates until the application is shut down.

单子会话Bean与无状态会话Bean似,但在整个应用程序中只创建一个单子会话Bean的实例,在应用程序关闭之前不会终止。

The single instance of the bean is shared between multiple clients and can be concurrently accessed.

Bean的单个实例在多个客户端之间共享,可以并发访问。

4. Creating a Singleton Session Bean

4.创建一个Singleton会话Bean

Let’s start by creating an interface for it.

让我们先为它创建一个接口。

For this example, let’s use the javax.ejb.Local annotation to define the interface:

对于这个例子,让我们使用javax.ejb.Local注解来定义接口。

@Local
public interface CountryState {
   List<String> getStates(String country);
   void setStates(String country, List<String> states);
}

Using @Local means the bean is accessed within the same application. We also have the option to use javax.ejb.Remote annotation which allows us to call the EJB remotely.

使用@Local意味着Bean可以在同一个应用程序中被访问。我们也可以选择使用javax.ejb.Remote注解,它允许我们远程调用EJB。

Now, we’ll define the implementation EJB bean class. We mark the class as a Singleton Session Bean by using the annotation javax.ejb.Singleton.

现在,我们将定义实现EJB Bean类。我们通过使用注解javax.ejb.Singleton将该类标记为一个Singleton会话Bean。

In addition, let’s also mark the bean with the javax.ejb.Startup annotation to inform the EJB container to initialize the bean at the startup:

此外,让我们也用javax.ejb.Startup注解来标记Bean,以通知EJB容器在启动时初始化Bean。

@Singleton
@Startup
public class CountryStateContainerManagedBean implements CountryState {
    ...
}

This is called eager initialization. If we don’t use @Startup, the EJB container determines when to initialize the bean.

这被称为急切的初始化。如果我们不使用@Startup,EJB容器会决定何时初始化Bean。

We can also define multiple Session Beans to initialize the data and load the beans in the specific order. Therefore, we’ll use, javax.ejb.DependsOn annotation to define our bean’s dependency on other Session Beans.

我们也可以定义多个会话Bean来初始化数据,并按照特定的顺序加载Bean。因此,我们将使用javax.ejb.DependsOn注解来定义我们的Bean对其他会话Bean的依赖性。

The value for the @DependsOn annotation is an array of the names of Bean class names that our Bean depends on:

@DependsOn 注解的值是我们的 Bean 所依赖的 Bean 类名称的一个数组。

@Singleton 
@Startup 
@DependsOn({"DependentBean1", "DependentBean2"}) 
public class CountryStateCacheBean implements CountryState { 
    ...
}

We’ll define an initialize() method which initializes the bean, and makes it a lifecycle callback method using javax.annotation.PostConstruct annotation.

我们将定义一个initialize()方法来初始化Bean,并使用javax.annotation.PostConstruct注解使其成为生命周期的回调方法。

With this annotation, it’ll be called by the container upon instantiation of the bean:

有了这个注解,它将在Bean的实例化时被容器调用。

@PostConstruct
public void initialize() {

    List<String> states = new ArrayList<String>();
    states.add("Texas");
    states.add("Alabama");
    states.add("Alaska");
    states.add("Arizona");
    states.add("Arkansas");

    countryStatesMap.put("UnitedStates", states);
}

5. Concurrency

5.并发性

Next, we’ll design the concurrency management of Singleton Session Bean. EJB provides two methods for implementing concurrent access to the Singleton Session Bean: Container-managed concurrency, and Bean-managed concurrency.

接下来,我们将设计Singleton Session Bean的并发管理。EJB提供了两种方法来实现对Singleton Session Bean的并发访问。容器管理的并发性,和Bean管理的并发性。

The annotation javax.ejb.ConcurrencyManagement defines the concurrency policy for a method. By default, the EJB container uses container-managed concurrency.

注解javax.ejb.ConcurrencyManagement定义了一个方法的并发策略。默认情况下,EJB容器使用容器管理的并发性。

The @ConcurrencyManagement annotation takes a javax.ejb.ConcurrencyManagementType value. The options are:

@ConcurrencyManagement注解需要一个javax.ejb.ConcurrencyManagementType值。选项包括:。

  • ConcurrencyManagementType.CONTAINER for container-managed concurrency.
  • ConcurrencyManagementType.BEAN for bean-managed concurrency.

5.1. Container-Managed Concurrency

5.1.容器管理的并发性

Simply put, in container-managed concurrency, the container controls how clients’ access to methods.

简单地说,在容器管理的并发性中,容器控制客户对方法的访问方式。

Let’s use the @ConcurrencyManagement annotation with value javax.ejb.ConcurrencyManagementType.CONTAINER:

让我们使用@ConcurrencyManagementannotation,其值为javax.ejb.ConcurrencyManagementType.CONTAINER

@Singleton
@Startup
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
public class CountryStateContainerManagedBean implements CountryState {
    ...
}

To specify the access level to each of the singleton’s business methods, we’ll use javax.ejb.Lock annotation. javax.ejb.LockType contains the values for the @Lock annotation. javax.ejb.LockType defines two values:

为了指定每个单子的业务方法的访问级别,我们将使用javax.ejb.Lock注解。javax.ejb.LockType包含@Lock注解的值。 javax.ejb.LockType定义了两个值。

  • LockType.WRITE – This value provides an exclusive lock to the calling client and prevents all other clients from accessing all methods of the bean. Use this for methods that change the state of the singleton bean.
  • LockType.READThis value provides concurrent locks to multiple clients to access a method.
    Use this for methods which only read data from the bean.

With this in mind, we’ll define the setStates() method with @Lock(LockType.WRITE) annotation, to prevent simultaneous update of the state by clients.

考虑到这一点,我们将用@Lock(LockType.WRITE)/em>注解来定义setStates()/em>方法,以防止客户端同时更新状态。

To allow clients to read the data concurrently, we’ll annotate getStates() with @Lock(LockType.READ):

为了允许客户端同时读取数据,我们将用@Lock(LockType.READ)来注释getStates()

@Singleton 
@Startup 
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER) 
public class CountryStateContainerManagedBean implements CountryState { 

    private final Map<String, List<String> countryStatesMap = new HashMap<>();

    @Lock(LockType.READ) 
    public List<String> getStates(String country) { 
        return countryStatesMap.get(country);
    }

    @Lock(LockType.WRITE)
    public void setStates(String country, List<String> states) {
        countryStatesMap.put(country, states);
    }
}

To stop the methods execute for a long time and blocking the other clients indefinitely, we’ll use the javax.ejb.AccessTimeout annotation to timeout long-waiting calls.

为了阻止这些方法长时间执行并无限期地阻塞其他客户端,我们将使用javax.ejb.AccessTimeout注解来超时处理长期等待的调用。

Use the @AccessTimeout annotation to define the number of milliseconds method times-out. After the timeout, the container throws a javax.ejb.ConcurrentAccessTimeoutException and the method execution terminates.

使用@AccessTimeout注解来定义方法超时的毫秒数。超时后,容器会抛出一个javax.ejb.ConcurrentAccessTimeoutException,方法的执行就会终止。

5.2. Bean-Managed Concurrency

5.2.Bean管理的并发性

In Bean managed concurrency, the container doesn’t control simultaneous access of Singleton Session Bean by clients. The developer is required to implement concurrency by themselves.

在 Bean 管理的并发性中,容器不控制客户对 Singleton Session Bean 的同时访问。开发者需要自己来实现并发性。

Unless concurrency is implemented by the developer, all methods are accessible to all clients simultaneously. Java provides the synchronization and volatile primitives for implementing concurrency.

除非开发者实现了并发性,否则所有的方法都可以被所有的客户同时访问。Java提供了synchronizationvolatile基元来实现并发性。

To find out more about concurrency read about java.util.concurrent here and Atomic Variables here.

要了解有关并发的更多信息,请阅读java.util.concurrent 和原子变量

For bean-managed concurrency, let’s define the @ConcurrencyManagement annotation with the javax.ejb.ConcurrencyManagementType.BEAN value for the Singleton Session Bean class:

对于Bean管理的并发,让我们为Singleton Session Bean类定义带有javax.ejb.ConcurrencyManagementType.BEAN值的@ConcurrencyManagement注释。

@Singleton 
@Startup 
@ConcurrencyManagement(ConcurrencyManagementType.BEAN) 
public class CountryStateBeanManagedBean implements CountryState { 
   ... 
}

Next, we’ll write the setStates() method which changes the state of the bean using synchronized keyword:

接下来,我们将编写setStates()方法,使用synchronized关键字改变Bean的状态。

public synchronized void setStates(String country, List<String> states) {
    countryStatesMap.put(country, states);
}

The synchronized keyword makes the method accessible by only one thread at a time.

synchronized关键字使该方法一次只能由一个线程访问。

The getStates() method doesn’t change the state of the Bean and so it doesn’t need to use the synchronized keyword.

getStates()方法并不改变Bean的状态,因此它不需要使用synchronized关键字。

6. Client

6.客户

Now we can write the client to access our Singleton Session Bean.

现在我们可以编写客户端来访问我们的Singleton Session Bean。

We can deploy the Session Bean on application container servers like JBoss, Glassfish etc. To keep things simple, we will use the javax.ejb.embedded.EJBContainer class. EJBContainer runs in the same JVM as the client and provides most of the services of an enterprise bean container.

我们可以在JBoss、Glassfish等应用容器服务器上部署会话Bean。为了保持简单,我们将使用javax.ejb.embedded.EJBContainer类。EJBContainer在与客户端相同的JVM中运行,并提供企业Bean容器的大部分服务。

First, we’ll create an instance of EJBContainer. This container instance will search and initialize all the EJB modules present in the classpath:

首先,我们将创建一个EJBContainer的实例。这个容器实例将搜索并初始化classpath中的所有EJB模块。

public class CountryStateCacheBeanTest {

    private EJBContainer ejbContainer = null;

    private Context context = null;

    @Before
    public void init() {
        ejbContainer = EJBContainer.createEJBContainer();
        context = ejbContainer.getContext();
    }
}

Next, we’ll get the javax.naming.Context object from the initialized container object. Using the Context instance, we can get the reference to CountryStateContainerManagedBean and call the methods:

接下来,我们将从初始化的容器对象中获得javax.naming.Context对象。使用Context实例,我们可以获得对CountryStateContainerManagedBean的引用并调用这些方法。

@Test
public void whenCallGetStatesFromContainerManagedBean_ReturnsStatesForCountry() throws Exception {

    String[] expectedStates = {"Texas", "Alabama", "Alaska", "Arizona", "Arkansas"};

    CountryState countryStateBean = (CountryState) context
      .lookup("java:global/singleton-ejb-bean/CountryStateContainerManagedBean");
    List<String> actualStates = countryStateBean.getStates("UnitedStates");

    assertNotNull(actualStates);
    assertArrayEquals(expectedStates, actualStates.toArray());
}

@Test
public void whenCallSetStatesFromContainerManagedBean_SetsStatesForCountry() throws Exception {

    String[] expectedStates = { "California", "Florida", "Hawaii", "Pennsylvania", "Michigan" };
 
    CountryState countryStateBean = (CountryState) context
      .lookup("java:global/singleton-ejb-bean/CountryStateContainerManagedBean");
    countryStateBean.setStates(
      "UnitedStates", Arrays.asList(expectedStates));
 
    List<String> actualStates = countryStateBean.getStates("UnitedStates");
    assertNotNull(actualStates);
    assertArrayEquals(expectedStates, actualStates.toArray());
}

Similarly, we can use the Context instance to get the reference for Bean-Managed Singleton Bean and call the respective methods:

同样地,我们可以使用Context实例来获取Bean-Managed Singleton Bean的引用,并调用相应的方法。

@Test
public void whenCallGetStatesFromBeanManagedBean_ReturnsStatesForCountry() throws Exception {

    String[] expectedStates = { "Texas", "Alabama", "Alaska", "Arizona", "Arkansas" };

    CountryState countryStateBean = (CountryState) context
      .lookup("java:global/singleton-ejb-bean/CountryStateBeanManagedBean");
    List<String> actualStates = countryStateBean.getStates("UnitedStates");

    assertNotNull(actualStates);
    assertArrayEquals(expectedStates, actualStates.toArray());
}

@Test
public void whenCallSetStatesFromBeanManagedBean_SetsStatesForCountry() throws Exception {

    String[] expectedStates = { "California", "Florida", "Hawaii", "Pennsylvania", "Michigan" };

    CountryState countryStateBean = (CountryState) context
      .lookup("java:global/singleton-ejb-bean/CountryStateBeanManagedBean");
    countryStateBean.setStates("UnitedStates", Arrays.asList(expectedStates));

    List<String> actualStates = countryStateBean.getStates("UnitedStates");
    assertNotNull(actualStates);
    assertArrayEquals(expectedStates, actualStates.toArray());
}

End our tests by closing the EJBContainer in the close() method:

通过在close()方法中关闭EJBContainer来结束我们的测试。

@After
public void close() {
    if (ejbContainer != null) {
        ejbContainer.close();
    }
}

7. Conclusion

7.结论

Singleton Session Beans are just as flexible and powerful as any standard Session Bean but allow us to apply a Singleton pattern to share state across our application’s clients.

Singleton会话Bean与任何标准的会话Bean一样灵活和强大,但允许我们应用Singleton模式在我们的应用程序的客户端之间共享状态。

Concurrency management of the Singleton Bean could be easily implemented using Container-Managed Concurrency where the container takes care of concurrent access by multiple clients, or you could also implement your own custom concurrency management using Bean-Managed Concurrency.

Singleton Bean的并发管理可以使用容器管理的并发来轻松实现,其中容器负责处理多个客户端的并发访问,或者你也可以使用Bean-Managed Concurrency来实现你自己的自定义并发管理。

The source code of this tutorial can be found over on GitHub.

本教程的源代码可以在GitHub上找到over