Spring Bean vs. EJB - Ein Funktionsvergleich

1. Übersicht

Im Laufe der Jahre hat sich das Java-Ökosystem enorm weiterentwickelt und ist enorm gewachsen. In dieser Zeit sind Enterprise Java Beans und Spring zwei Technologien, die nicht nur konkurriert haben, sondern auch symbiotisch voneinander gelernt haben.

In diesem Tutorial werden wir uns ihre Geschichte und Unterschiede ansehen. Natürlich werden wir einige Codebeispiele von EJB und ihren Entsprechungen in der Frühlingswelt sehen .

2. Eine kurze Geschichte der Technologien

Lassen Sie uns zunächst einen kurzen Blick auf die Geschichte dieser beiden Technologien werfen und wie sie sich im Laufe der Jahre stetig entwickelt haben.

2.1. Enterprise Java Beans

Die EJB-Spezifikation ist eine Teilmenge der Java EE-Spezifikation (oder J2EE, jetzt als Jakarta EE bekannt) . Die erste Version wurde 1999 veröffentlicht und war eine der ersten Technologien, die die serverseitige Entwicklung von Unternehmensanwendungen in Java vereinfachen sollten.

Es übernahm die Last der Java-Entwickler für Parallelität, Sicherheit, Persistenz, Transaktionsverarbeitung und mehr. Die Spezifikation übergab diese und andere allgemeine Unternehmensanliegen an die Container der implementierenden Anwendungsserver, die diese nahtlos handhabten. Die Verwendung von EJBs war jedoch aufgrund des erforderlichen Konfigurationsaufwands etwas umständlich. Darüber hinaus erwies es sich als Leistungsengpass.

Mit der Erfindung von Anmerkungen und der starken Konkurrenz durch Spring sind EJBs in ihrer neuesten Version 3.2 jetzt viel einfacher zu verwenden als ihre Debütversion. Die heutigen Enterprise Java Beans sind stark von Spring's Abhängigkeitsinjektion und der Verwendung von POJOs abhängig.

2.2. Frühling

Während EJBs (und Java EE im Allgemeinen) Schwierigkeiten hatten, die Java-Community zufrieden zu stellen, kam Spring Framework wie ein Hauch frischer Luft. Die erste Meilensteinveröffentlichung erschien im Jahr 2004 und bot eine Alternative zum EJB-Modell und seinen Schwergewichtscontainern.

Dank Spring konnten Java-Unternehmensanwendungen jetzt auf leichteren IOC-Containern ausgeführt werden . Darüber hinaus bot es unter unzähligen anderen nützlichen Funktionen Unterstützung für Abhängigkeitsinversion, AOP und Ruhezustand. Mit der enormen Unterstützung der Java-Community ist Spring nun exponentiell gewachsen und kann als vollständiges Java / JEE-Anwendungsframework bezeichnet werden.

In seinem neuesten Avatar unterstützt Spring 5.0 sogar das reaktive Programmiermodell. Ein weiterer Ableger, Spring Boot, ist mit seinen eingebetteten Servern und automatischen Konfigurationen ein kompletter Game-Changer.

3. Vorspiel zum Funktionsvergleich

Bevor wir zum Funktionsvergleich mit Codebeispielen springen, wollen wir einige Grundlagen festlegen.

3.1. Grundlegender Unterschied zwischen den beiden

Erstens besteht der grundlegende und offensichtliche Unterschied darin, dass EJB eine Spezifikation ist, während Spring ein ganzer Rahmen ist .

Die Spezifikation wird von vielen Anwendungsservern wie GlassFish, IBM WebSphere und JBoss / WildFly implementiert. Dies bedeutet, dass unsere Entscheidung, das EJB-Modell für die Backend-Entwicklung unserer Anwendung zu verwenden, nicht ausreicht. Wir müssen auch auswählen, welcher Anwendungsserver verwendet werden soll.

Theoretisch sind Enterprise Java Beans über App-Server hinweg portierbar, obwohl immer die Voraussetzung besteht, dass keine herstellerspezifischen Erweiterungen verwendet werden sollten, wenn die Interoperabilität als Option beibehalten werden soll.

Zweitens ist Spring as Technology in Bezug auf sein breites Angebotsportfolio näher an Java EE als an EJB . Während EJBs nur Backend-Operationen angeben, unterstützt Spring wie Java EE auch die UI-Entwicklung, RESTful-APIs und die reaktive Programmierung, um nur einige zu nennen.

3.2. Nützliche Informationen

In den folgenden Abschnitten sehen wir den Vergleich der beiden Technologien mit einigen praktischen Beispielen. Da EJB-Funktionen eine Teilmenge des viel größeren Spring-Ökosystems sind, werden wir nach ihren Typen suchen und die entsprechenden Spring-Äquivalente anzeigen.

Um die Beispiele am besten zu verstehen, sollten Sie zuerst Java EE Session Beans, Message Driven Beans, Spring Bean und Spring Bean Annotations lesen.

Wir werden OpenJB als eingebetteten Container verwenden, um die EJB-Beispiele auszuführen. Für die Ausführung der meisten Spring-Beispiele reicht der IOC-Container aus. Für Spring JMS benötigen wir einen eingebetteten ApacheMQ-Broker.

Um alle unsere Beispiele zu testen, verwenden wir JUnit.

4. Singleton EJB == Federkomponente

Manchmal benötigen wir den Container, um nur eine einzelne Instanz einer Bean zu erstellen. Angenommen, wir benötigen eine Bean, um die Anzahl der Besucher unserer Webanwendung zu zählen. Diese Bean muss beim Start der Anwendung nur einmal erstellt werden .

Mal sehen, wie dies mit einem Singleton Session EJB und einer Spring Component erreicht wird .

4.1. Singleton EJB Beispiel

Wir benötigen zunächst eine Schnittstelle, um anzugeben, dass unser EJB remote gehandhabt werden kann:

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

Der nächste Schritt besteht darin , eine Implementierungsklasse mit der Anmerkung javax.ejb.Singleton und viola! Zu definieren. Unser Singleton ist fertig:

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

Bevor wir jedoch den Singleton (oder ein anderes EJB-Codebeispiel) testen können, müssen wir den ejbContainer initialisieren und den Kontext abrufen :

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

Schauen wir uns nun den Test an:

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

Einige Dinge, die im obigen Beispiel zu beachten sind:

  • Wir verwenden die JNDI-Suche, um counterEJB aus dem Container abzurufen
  • COUNT2 nimmt vom Punkt Zählung an die Singleton links und fügt bis 20
  • secondCounter behält den Namen bei, den wir für firstCounter festgelegt haben

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.

Dann haben wir uns mit einfachen Beispielen befasst, um den Vergleich von Spring Beans und EJBs zu demonstrieren. Es ist unnötig zu erwähnen, dass nur die Oberfläche der Technologien zerkratzt wird, und es gibt noch viel mehr zu erforschen .

Darüber hinaus könnten dies konkurrierende Technologien sein, aber das bedeutet nicht, dass sie nicht nebeneinander existieren können. Wir können EJBs problemlos in das Spring-Framework integrieren.

Wie immer ist der Quellcode über GitHub verfügbar.