Spring Bean vs. EJB - Perbandingan Ciri

1. Gambaran keseluruhan

Selama bertahun-tahun, ekosistem Jawa telah berkembang dan berkembang pesat. Selama ini, Enterprise Java Beans and Spring adalah dua teknologi yang tidak hanya dapat bersaing tetapi saling belajar secara simbiotik.

Dalam tutorial ini, kita akan melihat sejarah dan perbezaan mereka. Sudah tentu, kita akan melihat beberapa contoh kod EJB dan yang setaraf dengannya di dunia Spring .

2. Sejarah Ringkas Teknologi

Sebagai permulaan, mari kita lihat sejarah kedua teknologi ini dan bagaimana mereka terus berkembang sejak bertahun-tahun.

2.1. Kacang Java Enterprise

Spesifikasi EJB adalah subset dari spesifikasi Java EE (atau J2EE, sekarang dikenal sebagai Jakarta EE) . Versi pertamanya keluar pada tahun 1999, dan ini adalah salah satu teknologi pertama yang dirancang untuk membuat pengembangan aplikasi perusahaan dari pelayan lebih mudah di Jawa.

Ini memikul beban bersamaan, keamanan, ketekunan, pemrosesan transaksi , dan banyak lagi pemaju Java . Spesifikasi tersebut menyerahkan ini dan keprihatinan perusahaan biasa lainnya ke wadah pelayan aplikasi pelaksana, yang menanganinya dengan lancar. Namun, penggunaan EJB seperti dulu agak membebankan kerana jumlah konfigurasi yang diperlukan. Lebih-lebih lagi, ia terbukti menjadi hambatan prestasi.

Tetapi sekarang, dengan penemuan anotasi, dan persaingan ketat dari Spring, EJB dalam versi 3.2 terbaru mereka jauh lebih mudah digunakan daripada versi debutnya. Enterprise Java Beans hari ini banyak meminjam dari suntikan ketergantungan Spring dan penggunaan POJO.

2.2. Musim bunga

Sementara EJB (dan Java EE pada umumnya) berjuang untuk memuaskan masyarakat Jawa, Spring Framework tiba seperti nafas segar. Pelepasan tonggak pertamanya dikeluarkan pada tahun 2004 dan menawarkan alternatif kepada model EJB dan bekas beratnya.

Terima kasih kepada Spring, aplikasi perusahaan Java kini dapat dijalankan pada kontainer IOC yang lebih ringan . Lebih-lebih lagi, ia juga menawarkan pergantungan ketergantungan, AOP, dan sokongan Hibernate di antara pelbagai ciri berguna lain. Dengan dukungan luar biasa dari masyarakat Java, Spring kini telah berkembang secara eksponensial dan dapat disebut sebagai kerangka aplikasi Java / JEE penuh.

Dalam avatar terbarunya, Spring 5.0 malah menyokong model pengaturcaraan reaktif. Satu lagi cabutan, Spring Boot, adalah pengubah permainan lengkap dengan pelayan tertanam dan konfigurasi automatik.

3. Prelude to the Feature Perbandingan

Sebelum beralih ke perbandingan ciri dengan sampel kod, mari kita buat beberapa asas.

3.1. Perbezaan Asas Antara Kedua

Pertama, perbezaan mendasar dan jelas adalah EJB adalah spesifikasi, sedangkan Spring adalah keseluruhan kerangka .

Spesifikasi ini dilaksanakan oleh banyak pelayan aplikasi seperti GlassFish, IBM WebSphere dan JBoss / WildFly. Ini bermaksud bahawa pilihan kita untuk menggunakan model EJB untuk pengembangan backend aplikasi kita tidak mencukupi. Kita juga perlu memilih pelayan aplikasi mana yang akan digunakan.

Secara teorinya, Enterprise Java Beans mudah alih di seluruh pelayan aplikasi, walaupun selalu ada prasyarat bahawa kita tidak boleh menggunakan pelanjutan khusus vendor jika interoperabilitas harus disimpan sebagai pilihan.

Kedua, Spring sebagai teknologi lebih dekat dengan Java EE daripada EJB dari segi portfolio penawarannya yang luas . Walaupun EJB hanya menentukan operasi backend, Spring, seperti Java EE, juga memiliki dukungan untuk pengembangan UI, RESTful API, dan pengaturcaraan Reaktif untuk menyebutkan beberapa.

3.2. Informasi berguna

Pada bahagian yang berikut, kita akan melihat perbandingan kedua-dua teknologi dengan beberapa contoh praktikal. Oleh kerana ciri EJB adalah sebahagian daripada ekosistem Spring yang jauh lebih besar, kami akan mengikut jenisnya dan melihat setara Spring yang sesuai.

Untuk memahami contohnya dengan sebaik-baiknya, pertimbangkan untuk membaca Java EE Session Beans, Message Driven Beans, Spring Bean, dan Spring Bean Annotations terlebih dahulu.

Kami akan menggunakan OpenJB sebagai wadah tertanam kami untuk menjalankan sampel EJB. Untuk menjalankan sebahagian besar contoh Spring, bekas IOCnya akan mencukupi; untuk Spring JMS, kami memerlukan broker ApacheMQ terbenam.

Untuk menguji semua sampel kami, kami akan menggunakan JUnit.

4. Singleton EJB == Komponen Musim Semi

Kadang-kadang kita memerlukan bekas untuk membuat satu contoh kacang sahaja. Sebagai contoh, katakan kita memerlukan kacang untuk mengira jumlah pengunjung aplikasi web kita. Kacang ini perlu dibuat hanya sekali semasa permulaan aplikasi .

Mari lihat bagaimana mencapainya dengan menggunakan Singleton Session EJB dan Spring Component .

4.1. Contoh Singleton EJB

Mula-mula kita memerlukan antara muka untuk menentukan bahawa EJB kita mempunyai keupayaan untuk ditangani dari jauh:

@Remote public interface CounterEJBRemote { int count(); String getName(); void setName(String name); }

Langkah seterusnya adalah menentukan kelas pelaksanaan dengan penjelasan javax.ejb.Singleton , dan viola! Singleton kami sudah siap:

@Singleton public class CounterEJB implements CounterEJBRemote { private int count = 1; private String name; public int count() { return count++; } // getter and setter for name } 

Tetapi sebelum kita dapat menguji singleton (atau contoh kod EJB lain), kita perlu menginisialisasi ejbContainer dan mendapatkan konteksnya :

@BeforeClass public void initializeContext() throws NamingException { ejbContainer = EJBContainer.createEJBContainer(); context = ejbContainer.getContext(); context.bind("inject", this); } 

Sekarang mari kita lihat ujiannya:

@Test public void givenSingletonBean_whenCounterInvoked_thenCountIsIncremented() throws NamingException { int count = 0; CounterEJBRemote firstCounter = (CounterEJBRemote) context.lookup("java:global/ejb-beans/CounterEJB"); firstCounter.setName("first"); for (int i = 0; i < 10; i++) { count = firstCounter.count(); } assertEquals(10, count); assertEquals("first", firstCounter.getName()); CounterEJBRemote secondCounter = (CounterEJBRemote) context.lookup("java:global/ejb-beans/CounterEJB"); int count2 = 0; for (int i = 0; i < 10; i++) { count2 = secondCounter.count(); } assertEquals(20, count2); assertEquals("first", secondCounter.getName()); } 

Beberapa perkara yang perlu diperhatikan dalam contoh di atas:

  • Kami menggunakan carian JNDI untuk mendapatkan counterEJB dari bekas
  • count2 memungut dari sudut kiraan meninggalkan lajang pada, dan menambah sehingga 20
  • secondCounter mengekalkan nama yang kami tetapkan untuk FirstCounter

The last two points demonstrate the significance of a singleton. Since the same bean instance is used each time it's looked up, the total count is 20 and the value set for one remains the same for the other.

4.2. Singleton Spring Bean Example

The same functionality can be obtained using Spring components.

We don't need to implement any interface here. Instead, we'll add the @Component annotation:

@Component public class CounterBean { // same content as in the EJB }

In fact, components are singletons by default in Spring.

We also need to configure Spring to scan for components:

@Configuration @ComponentScan(basePackages = "com.baeldung.ejbspringcomparison.spring") public class ApplicationConfig {} 

Similar to how we initialized the EJB context, we'll now set the Spring context:

@BeforeClass public static void init() { context = new AnnotationConfigApplicationContext(ApplicationConfig.class); } 

Now let's see our Component in action:

@Test public void whenCounterInvoked_thenCountIsIncremented() throws NamingException { CounterBean firstCounter = context.getBean(CounterBean.class); firstCounter.setName("first"); int count = 0; for (int i = 0; i < 10; i++) { count = firstCounter.count(); } assertEquals(10, count); assertEquals("first", firstCounter.getName()); CounterBean secondCounter = context.getBean(CounterBean.class); int count2 = 0; for (int i = 0; i < 10; i++) { count2 = secondCounter.count(); } assertEquals(20, count2); assertEquals("first", secondCounter.getName()); } 

As we can see, the only difference with respect to EJBs is how we are getting the bean from the Spring container's context, instead of JNDI lookup.

5. Stateful EJB == Spring Component with prototype Scope

At times, say when we are building a shopping cart, we need our bean to remember its state while going back and forth between method calls.

In this case, we need our container to generate a separate bean for each invocation and save the state. Let's see how this can be achieved with our technologies in question.

5.1. Stateful EJB Example

Similar to our singleton EJB sample, we need a javax.ejb.Remote interface and its implementation. Only this time, its annotated with javax.ejb.Stateful:

@Stateful public class ShoppingCartEJB implements ShoppingCartEJBRemote { private String name; private List shoppingCart; public void addItem(String item) { shoppingCart.add(item); } // constructor, getters and setters }

Let's write a simple test to set a name and add items to a bathingCart. We'll check its size and verify the name:

@Test public void givenStatefulBean_whenBathingCartWithThreeItemsAdded_thenItemsSizeIsThree() throws NamingException { ShoppingCartEJBRemote bathingCart = (ShoppingCartEJBRemote) context.lookup( "java:global/ejb-beans/ShoppingCartEJB"); bathingCart.setName("bathingCart"); bathingCart.addItem("soap"); bathingCart.addItem("shampoo"); bathingCart.addItem("oil"); assertEquals(3, bathingCart.getItems().size()); assertEquals("bathingCart", bathingCart.getName()); } 

Now, to demonstrate that the bean really maintains state across instances, let's add another shoppingCartEJB to this test:

ShoppingCartEJBRemote fruitCart = (ShoppingCartEJBRemote) context.lookup("java:global/ejb-beans/ShoppingCartEJB"); fruitCart.addItem("apples"); fruitCart.addItem("oranges"); assertEquals(2, fruitCart.getItems().size()); assertNull(fruitCart.getName()); 

Here we did not set the name and hence its value was null. Recall from the singleton test, that the name set in one instance was retained in another. This demonstrates that we got separate ShoppingCartEJB instances from the bean pool with different instance states.

5.2. Stateful Spring Bean Example

To get the same effect with Spring, we need a Component with a prototype scope:

@Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class ShoppingCartBean { // same contents as in the EJB } 

That's it, just the annotations differ – the rest of the code remains the same.

To test our Stateful bean, we can use the same test as described for EJBs. The only difference is again how we get the bean from the container:

ShoppingCartBean bathingCart = context.getBean(ShoppingCartBean.class); 

6. Stateless EJB != Anything in Spring

Sometimes, for example in a search API, we neither care about the instance state of a bean nor if it is a singleton. We just need the results of our search, which might be coming from any bean instance for all we care about.

6.1. Stateless EJB Example

For such scenarios, EJB has a stateless variant. The container maintains an instance pool of beans, and any of them is returned to the calling method.

The way we define it is the same as other EJB types, with a remote interface, and implementation with javax.ejb.Stateless annotation:

@Stateless public class FinderEJB implements FinderEJBRemote { private Map alphabet; public FinderEJB() { alphabet = new HashMap(); alphabet.put("A", "Apple"); // add more values in map here } public String search(String keyword) { return alphabet.get(keyword); } } 

Let's add another simple test to see this in action:

@Test public void givenStatelessBean_whenSearchForA_thenApple() throws NamingException { assertEquals("Apple", alphabetFinder.search("A")); } 

In the above example, alphabetFinder is injected as a field in the test class using the annotation javax.ejb.EJB:

@EJB private FinderEJBRemote alphabetFinder; 

The central idea behind Stateless EJBs is to enhance performance by having an instance pool of similar beans.

However, Spring does not subscribe to this philosophy and only offers singletons as stateless.

7. Message Driven Beans == Spring JMS

All EJBs discussed so far were session beans. Another kind is the message-driven one. As the name suggests, they are typically used for asynchronous communication between two systems.

7.1. MDB Example

To create a message-driven Enterprise Java Bean, we need to implement the javax.jms.MessageListener interface defining its onMessage method, and annotate the class as javax.ejb.MessageDriven:

@MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "destination", propertyValue = "myQueue"), @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue") }) public class RecieverMDB implements MessageListener { @Resource private ConnectionFactory connectionFactory; @Resource(name = "ackQueue") private Queue ackQueue; public void onMessage(Message message) { try { TextMessage textMessage = (TextMessage) message; String producerPing = textMessage.getText(); if (producerPing.equals("marco")) { acknowledge("polo"); } } catch (JMSException e) { throw new IllegalStateException(e); } } } 

Notice that we are also providing a couple of configurations for our MDB:

      • destinationType as Queue
      • myQueue as the destination queue name, to which our bean is listening

In this example, our receiver also produces an acknowledgment, and in that sense is a sender in itself. It sends a message to another queue called ackQueue.

Now let's see this in action with a test:

@Test public void givenMDB_whenMessageSent_thenAcknowledgementReceived() throws InterruptedException, JMSException, NamingException { Connection connection = connectionFactory.createConnection(); connection.start(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageProducer producer = session.createProducer(myQueue); producer.send(session.createTextMessage("marco")); MessageConsumer response = session.createConsumer(ackQueue); assertEquals("polo", ((TextMessage) response.receive(1000)).getText()); } 

Here we sent a message to myQueue, which was received by our @MessageDriven annotated POJO. This POJO then sent an acknowledgment and our test received the response as a MessageConsumer.

7.2. Spring JMS Example

Well, now it's time to do the same thing using Spring!

First, we'll need to add a bit of configuration for this purpose. We need to annotate our ApplicationConfig class from before with @EnableJms and add a few beans to setup JmsListenerContainerFactory and JmsTemplate:

@EnableJms public class ApplicationConfig { @Bean public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); factory.setConnectionFactory(connectionFactory()); return factory; } @Bean public ConnectionFactory connectionFactory() { return new ActiveMQConnectionFactory("tcp://localhost:61616"); } @Bean public JmsTemplate jmsTemplate() { JmsTemplate template = new JmsTemplate(connectionFactory()); template.setConnectionFactory(connectionFactory()); return template; } } 

Next, we need a Producer – a simple Spring Component – that will send messages to myQueue and receive an acknowledgment from ackQueue:

@Component public class Producer { @Autowired private JmsTemplate jmsTemplate; public void sendMessageToDefaultDestination(final String message) { jmsTemplate.convertAndSend("myQueue", message); } public String receiveAck() { return (String) jmsTemplate.receiveAndConvert("ackQueue"); } } 

Then, we have a ReceiverComponent with a method annotated as @JmsListener to receive messages asynchronously from myQueue:

@Component public class Receiver { @Autowired private JmsTemplate jmsTemplate; @JmsListener(destination = "myQueue") public void receiveMessage(String msg) { sendAck(); } private void sendAck() { jmsTemplate.convertAndSend("ackQueue", "polo"); } } 

It also acts as a sender for acknowledging message receipt at ackQueue.

As is our practice, let's verify this with a test:

@Test public void givenJMSBean_whenMessageSent_thenAcknowledgementReceived() throws NamingException { Producer producer = context.getBean(Producer.class); producer.sendMessageToDefaultDestination("marco"); assertEquals("polo", producer.receiveAck()); } 

In this test, we sent marco to myQueue and received polo as an acknowledgment from ackQueue, the same as what we did with the EJB.

One thing of note here is that Spring JMS can send/receive messages both synchronously and asynchronously.

8. Conclusion

In this tutorial, we saw a one-on-one comparison of Spring and Enterprise Java Beans. We understood their history and basic differences.

Kemudian kami memberikan contoh mudah untuk menunjukkan perbandingan Spring Beans dan EJB. Tidak perlu dikatakan, ia hanya menggaru permukaan teknologi yang mampu dilakukan, dan masih banyak lagi yang perlu diterokai lebih lanjut .

Tambahan pula, ini mungkin merupakan teknologi yang bersaing, tetapi itu tidak bermakna mereka tidak dapat hidup bersama. Kami dapat menggabungkan EJB dengan mudah dalam rangka Spring.

Seperti biasa, kod sumber tersedia di GitHub.