Singleton Session Bean di Jakarta EE

1. Ikhtisar

Setiap kali satu contoh Session Bean diperlukan untuk kasus penggunaan tertentu, kita dapat menggunakan Singleton Session Bean.

Dalam tutorial ini, kita akan membahasnya melalui sebuah contoh, dengan aplikasi Jakarta EE.

2. Maven

Pertama-tama, kita perlu mendefinisikan dependensi Maven yang diperlukan di pom.xml .

Mari tentukan dependensi untuk EJB API dan container EJB Tertanam untuk penerapan EJB:

 javax javaee-api 8.0 provided   org.apache.openejb tomee-embedded 1.7.5 

Versi terbaru dapat ditemukan di Maven Central di JavaEE API dan tomEE.

3. Jenis Kacang Sesi

Ada tiga jenis Kacang Sesi. Sebelum kita menjelajahi Singleton Session Beans, mari kita lihat apa perbedaan antara siklus hidup ketiga jenis tersebut.

3.1. Kacang Sesi Stateful

A Stateful Session Bean mempertahankan status percakapan dengan klien yang dikomunikasikannya.

Setiap klien membuat instance baru dari Stateful Bean dan tidak dibagikan dengan klien lain.

Ketika komunikasi antara klien dan kacang berakhir, Sesi Bean juga berakhir.

3.2. Kacang Sesi Stateless

A Stateless Session Bean tidak mempertahankan status percakapan apa pun dengan klien. Kacang berisi status khusus untuk klien hanya sampai durasi pemanggilan metode.

Pemanggilan metode berurutan tidak tergantung tidak seperti Stateful Session Bean.

Kontainer memelihara kumpulan Kacang Tanpa Kewarganegaraan dan contoh ini dapat dibagikan di antara banyak klien.

3.3. Kacang Sesi Singleton

Singleton Session Bean mempertahankan status kacang untuk seluruh siklus hidup aplikasi.

Singleton Session Beans mirip dengan Stateless Session Beans tetapi hanya satu contoh Singleton Session Bean yang dibuat di seluruh aplikasi dan tidak berakhir hingga aplikasi ditutup.

Instance tunggal dari bean dibagikan di antara beberapa klien dan dapat diakses secara bersamaan.

4. Membuat Bean Sesi Singleton

Mari kita mulai dengan membuat antarmuka untuk itu.

Untuk contoh ini, mari gunakan anotasi javax.ejb.Local untuk menentukan antarmuka:

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

Menggunakan @Local berarti bean diakses dalam aplikasi yang sama. Kami juga memiliki opsi untuk menggunakan anotasi javax.ejb.Remote yang memungkinkan kami memanggil EJB dari jarak jauh.

Sekarang, kita akan mendefinisikan implementasi kelas kacang EJB. Kami menandai kelas sebagai Singleton Session Bean dengan menggunakan anotasi javax .ejb.Singleton .

Selain itu, mari kita juga menandai kacang dengan anotasi javax .ejb.Startup untuk memberi tahu wadah EJB untuk menginisialisasi kacang saat startup:

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

Ini disebut inisialisasi bersemangat. Jika kita tidak menggunakan @Startup , wadah EJB menentukan kapan harus menginisialisasi kacang.

Kami juga dapat menentukan beberapa Kacang Sesi untuk menginisialisasi data dan memuat kacang dalam urutan tertentu. Oleh karena itu, kita akan menggunakan anotasi javax.ejb.DependsOn untuk mendefinisikan ketergantungan kacang kita pada Kacang Sesi lainnya.

Nilai untuk anotasi @DependsOn adalah larik nama kelas Bean tempat Bean bergantung:

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

Kami akan mendefinisikan metode inisialisasi () yang menginisialisasi kacang, dan menjadikannya metode panggilan balik siklus hidup menggunakan anotasi javax.annotation.PostConstruct .

Dengan anotasi ini, itu akan dipanggil oleh wadah pada saat pembuatan kacang:

@PostConstruct public void initialize() { List states = new ArrayList(); states.add("Texas"); states.add("Alabama"); states.add("Alaska"); states.add("Arizona"); states.add("Arkansas"); countryStatesMap.put("UnitedStates", states); }

5. Konkurensi

Selanjutnya, kita akan merancang manajemen konkurensi Singleton Session Bean. EJB menyediakan dua metode untuk mengimplementasikan akses bersamaan ke Singleton Session Bean: konkurensi yang dikelola kontainer, dan konkurensi yang dikelola Bean.

Anotasi javax.ejb.ConcurrencyManagement menentukan kebijakan konkurensi untuk suatu metode. Secara default, penampung EJB menggunakan konkurensi yang dikelola penampung.

The @ConcurrencyManagement penjelasan mengambil javax.ejb.ConcurrencyManagementType nilai. Opsinya adalah:

  • ConcurrencyManagementType.CONTAINER untuk konkurensi yang dikelola container.
  • ConcurrencyManagementType.BEAN untuk konkurensi yang dikelola kacang.

5.1. Konkurensi yang Dikelola Penampung

Sederhananya, dalam konkurensi yang dikelola penampung, penampung mengontrol cara klien mengakses metode.

Mari gunakan anotasi @ConcurrencyManagement dengan nilai 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:

  • 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.

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

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

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.

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.

5.2. Bean-Managed Concurrency

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.

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.

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

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

@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:

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

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

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

6. Client

Now we can write the client to access our 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.

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

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:

@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 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 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:

@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 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 actualStates = countryStateBean.getStates("UnitedStates"); assertNotNull(actualStates); assertArrayEquals(expectedStates, actualStates.toArray()); }

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

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

7. Conclusion

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.

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.

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