Singleton Session Bean di Jakarta EE

1. Gambaran keseluruhan

Setiap kali satu contoh Bean Sesi diperlukan untuk kes penggunaan tertentu, kita dapat menggunakan Bean Sesi Singleton.

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

2. Maven

Pertama sekali, kita perlu menentukan pergantungan Maven yang diperlukan dalam pom.xml .

Mari tentukan kebergantungan untuk EJB API dan wadah EJB Tertanam untuk penggunaan EJB:

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

Versi terbaru boleh didapati di Maven Central di JavaEE API dan tomEE.

3. Jenis Kacang Sesi

Terdapat tiga jenis Kacang Sesi. Sebelum kita meneroka Singleton Session Beans, mari kita lihat apakah perbezaan antara kitaran hayat ketiga jenis tersebut.

3.1. Kacang Sesi Negeri

Bean Session Stateful mengekalkan keadaan perbualan dengan pelanggan yang dia berkomunikasi.

Setiap pelanggan membuat contoh baru Stateful Bean dan tidak dikongsi dengan pelanggan lain.

Apabila komunikasi antara pelanggan dan kacang berakhir, Session Bean juga berakhir.

3.2. Kacang Sesi Tanpa Statik

Bean Session Tanpa Statistik tidak mengekalkan keadaan perbualan dengan pelanggan. Kacang berisi keadaan khusus untuk klien hanya sehingga jangka masa kaedah memohon.

Pemanggilan kaedah berturut-turut adalah bebas seperti dengan Stateful Session Bean.

Bekas ini menyimpan kumpulan Kacang Tanpa Stat dan kejadian ini dapat dikongsi antara beberapa pelanggan.

3.3. Kacang Sesi Singleton

Singleton Session Bean mengekalkan keadaan kacang untuk kitaran hayat aplikasi yang lengkap.

Singleton Session Beans mirip dengan Stateless Session Beans tetapi hanya satu contoh Singleton Session Bean dibuat di keseluruhan aplikasi dan tidak akan berakhir sehingga aplikasi ditutup.

Contoh tunggal kacang dikongsi antara beberapa pelanggan dan dapat diakses secara serentak.

4. Membuat Biji Sesi Singleton

Mari mulakan dengan membuat antara muka untuknya.

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

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

Menggunakan @Local bermaksud kacang diakses dalam aplikasi yang sama. Kami juga mempunyai pilihan untuk menggunakan anotasi javax.ejb.Remote yang membolehkan kami memanggil EJB dari jauh.

Sekarang, kami akan menentukan kelas kacang EJB pelaksanaan. Kami menandakan kelas sebagai Singleton Session Bean dengan menggunakan anotasi javax .ejb.Singleton .

Di samping itu, mari kita tandakan kacang dengan javax .ejb. Mulakan anotasi untuk memberitahu bekas EJB untuk memulakan kacang pada permulaan:

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

Ini dipanggil inisialisasi bersemangat. Sekiranya kita tidak menggunakan @Startup , bekas EJB menentukan kapan untuk memulakan kacang.

Kami juga dapat menentukan beberapa Biji Sesi untuk menginisialisasi data dan memuat kacang dalam urutan tertentu. Oleh itu, kami akan menggunakan, javax.ejb.DependsOn anotasi untuk menentukan kebergantungan kacang kami pada Session Beans yang lain.

Nilai untuk anotasi @DependsOn adalah susunan nama nama kelas Bean yang bergantung kepada Bean kami:

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

Kami akan menentukan kaedah inisialisasi () yang menginisialisasi kacang, dan menjadikannya kaedah panggilan balik kitaran hidup menggunakan javax.annotation.PostConstruct anotation .

Dengan anotasi ini, ia akan dipanggil oleh bekas semasa memberi contoh 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. Serentak

Seterusnya, kami akan merancang pengurusan bersamaan Singleton Session Bean. EJB menyediakan dua kaedah untuk melaksanakan akses serentak ke Singleton Session Bean: Concurrency yang dikendalikan Container, dan Concurrency yang dikendalikan oleh Bean.

Anotasi javax.ejb.ConcurrencyManagement menentukan dasar serentak untuk satu kaedah. Secara lalai, kontena EJB menggunakan konkurensi yang diuruskan oleh kontena.

The @ConcurrencyManagement anotasi mengambil javax.ejb.ConcurrencyManagementType nilai. Pilihannya adalah:

  • ConcurrencyManagementType.CONTAINER untuk serentak yang diurus kontena.
  • ConcurrencyManagementType.BEAN untuk serentak yang diuruskan kacang.

5.1. Kesesuaian Terurus Kontena

Ringkasnya, dalam konkuren yang diuruskan oleh kontena, wadah mengawal bagaimana akses pelanggan ke kaedah.

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.