Spring Bean vs. EJB - Een vergelijking van functies

1. Overzicht

Door de jaren heen is het Java-ecosysteem enorm geëvolueerd en gegroeid. Gedurende deze tijd zijn Enterprise Java Beans en Spring twee technologieën die niet alleen hebben geconcurreerd, maar ook symbiotisch van elkaar hebben geleerd.

In deze tutorial we zullen hun geschiedenis en verschillen bekijken. Natuurlijk zullen we enkele codevoorbeelden van EJB en hun equivalenten zien in de Spring-wereld.

2. Een korte geschiedenis van de technologieën

Laten we om te beginnen een korte blik werpen op de geschiedenis van deze twee technologieën en hoe ze zich in de loop der jaren gestaag hebben ontwikkeld.

2.1. Enterprise Java-bonen

De EJB-specificatie is een subset van de Java EE (of J2EE, nu bekend als Jakarta EE) specificatie. De eerste versie kwam uit in 1999 en het was een van de eerste technologieën die was ontworpen om de ontwikkeling van bedrijfsapplicaties aan de serverzijde in Java gemakkelijker te maken.

Het droeg de last van de Java-ontwikkelaars op het gebied van gelijktijdigheid, beveiliging, volharding en transactieverwerking, en meer. De specificatie droeg deze en andere veel voorkomende zorgen over aan de containers van de implementerende applicatieservers, die ze naadloos afhandelden. Het gebruik van EJB's zoals ze waren, was echter een beetje omslachtig vanwege de hoeveelheid vereiste configuratie. Bovendien bleek het een bottleneck op het gebied van prestaties te zijn.

Maar nu, met de uitvinding van annotaties en stevige concurrentie van Spring, zijn EJB's in hun nieuwste 3.2-versie veel eenvoudiger te gebruiken dan hun debuutversie. De Enterprise Java Beans van vandaag lenen veel van Spring's afhankelijkheidsinjectie en het gebruik van POJO's.

2.2. Voorjaar

Terwijl EJB's (en Java EE in het algemeen) moeite hadden om de Java-gemeenschap tevreden te stellen, kwam Spring Framework als een verademing. De eerste mijlpaalrelease kwam uit in het jaar 2004 en bood een alternatief voor het EJB-model en zijn zware containers.

Dankzij Spring, Java-bedrijfstoepassingen kunnen nu worden uitgevoerd op lichtere IOC-containers. Bovendien bood het ook afhankelijkheidsinversie, AOP en Hibernate-ondersteuning naast talloze andere handige functies. Met enorme steun van de Java-gemeenschap is Spring nu exponentieel gegroeid en kan het worden aangeduid als een volledig Java / JEE-toepassingsraamwerk.

In zijn nieuwste avatar ondersteunt Spring 5.0 zelfs het reactieve programmeermodel. Een andere uitloper, Spring Boot, is een complete game-wisselaar met zijn embedded servers en automatische configuraties.

3. Inleiding tot de vergelijking van de functies

Laten we, voordat we naar de feature-vergelijking met codevoorbeelden springen, een paar basisprincipes vaststellen.

3.1. Fundamenteel verschil tussen de twee

Ten eerste is het fundamentele en schijnbare verschil dat EJB is een specificatie, terwijl Spring een volledig raamwerk is.

De specificatie wordt geïmplementeerd door veel applicatieservers zoals GlassFish, IBM WebSphere en JBoss / WildFly. Dit betekent dat onze keuze om het EJB-model te gebruiken voor de backend-ontwikkeling van onze applicatie niet voldoende is. We moeten ook kiezen welke applicatieserver we willen gebruiken.

Theoretisch zijn Enterprise Java Beans draagbaar over app-servers, hoewel er altijd de voorwaarde is dat we geen leverancierspecifieke extensies gebruiken als de interoperabiliteit als een optie behouden moet blijven.

Tweede, Lente omdat technologie dichter bij Java EE staat dan EJB in termen van zijn brede portfolio van aanbiedingen. Hoewel EJB's alleen backend-bewerkingen specificeren, heeft Spring, net als Java EE, ook ondersteuning voor UI-ontwikkeling, RESTful API's en reactief programmeren om er maar een paar te noemen.

3.2. Bruikbare informatie

In de secties die volgen, zullen we de vergelijking van de twee technologieën zien met enkele praktische voorbeelden. Omdat EJB-functies een subset zijn van het veel grotere Spring-ecosysteem, gaan we naar hun typen en zien we hun overeenkomstige Spring-equivalenten.

Om de voorbeelden zo goed mogelijk te begrijpen, kunt u overwegen eerst de aantekeningen Java EE Session Beans, Message Driven Beans, Spring Bean en Spring Bean te lezen.

We gebruiken OpenJB als onze embedded container om de EJB-samples uit te voeren. Voor het uitvoeren van de meeste Spring-voorbeelden is de IOC-container voldoende; voor Spring JMS hebben we een ingebedde ApacheMQ-broker nodig.

Om al onze voorbeelden te testen, gebruiken we JUnit.

4. Singleton EJB == Lente Component

Soms hebben we de container nodig om slechts één exemplaar van een boon te maken. Stel dat we bijvoorbeeld een boon nodig hebben om het aantal bezoekers aan onze webapplicatie te tellen. Deze bean hoeft slechts één keer te worden gemaakt tijdens het opstarten van de applicatie.

Laten we eens kijken hoe we dit kunnen bereiken met behulp van een Singleton Session EJB en een Spring Component.

4.1. Singleton EJB-voorbeeld

We hebben eerst een interface nodig om te specificeren dat onze EJB op afstand kan worden afgehandeld:

@Remote openbare interface CounterEJBRemote {int count (); String getName (); void setName (String naam); }

De volgende stap is om definieer een implementatieklasse met de annotatie javax.ejb.Singleton, en altviool! Onze singleton is klaar:

@Singleton openbare klasse CounterEJB implementeert CounterEJBRemote {private int count = 1; private String naam; public int count () {return count ++; } // getter en setter voor naam} 

Maar voordat we de singleton (of een ander EJB-codevoorbeeld) kunnen testen, moeten we de ejbContainer en verkrijg het context:

@BeforeClass public void initializeContext () gooit NamingException {ejbContainer = EJBContainer.createEJBContainer (); context = ejbContainer.getContext (); context.bind ("injecteren", dit); } 

Laten we nu eens kijken naar de test:

@Test openbare leegte gegevenSingletonBean_whenCounterInvoked_thenCountIsIncremented () gooit NamingException {int count = 0; CounterEJBRemote firstCounter = (CounterEJBRemote) context.lookup ("java: global / ejb-beans / CounterEJB"); firstCounter.setName ("eerste"); voor (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; voor (int i = 0; i <10; i ++) {count2 = secondCounter.count (); } assertEquals (20, count2); assertEquals ("eerste", secondCounter.getName ()); } 

Een paar dingen om op te merken in het bovenstaande voorbeeld:

  • We gebruiken de JNDI-lookup om tegenEJB uit de container
  • count2 pikt vanaf het punt op tellen liet de singleton op, en telt op 20
  • secondCounter behoudt de naam die we hebben ingesteld firstCounter

De laatste twee punten demonstreren de betekenis van een singleton. Omdat dezelfde bean-instantie elke keer dat het wordt opgezocht wordt gebruikt, is het totale aantal 20 en blijft de waarde die voor de ene is ingesteld, hetzelfde voor de andere.

4.2. Singleton Spring Bean Voorbeeld

Dezelfde functionaliteit kan worden verkregen met behulp van veercomponenten.

We hoeven hier geen interface te implementeren. In plaats daarvan voegen we de @Component annotatie:

@Component openbare klasse CounterBean {// dezelfde inhoud als in de EJB}

In feite zijn componenten standaard eenlingen in het voorjaar.

We moeten Spring ook configureren om te scannen op componenten:

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

Net zoals we de EJB-context hebben geïnitialiseerd, stellen we nu de Spring-context in:

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

Laten we nu eens kijken naar onze Component in actie:

@Test openbare leegte whenCounterInvoked_thenCountIsIncremented () gooit NamingException {CounterBean firstCounter = context.getBean (CounterBean.class); firstCounter.setName ("eerste"); int count = 0; voor (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; voor (int i = 0; i <10; i ++) {count2 = secondCounter.count (); } assertEquals (20, count2); assertEquals ("eerste", secondCounter.getName ()); } 

Zoals we kunnen zien, is het enige verschil met betrekking tot EJB's hoe we de boon uit de context van de Spring-container halen, in plaats van JNDI-lookup.

5. Stateful EJB == Spring Component met voorlopig ontwerp Reikwijdte

Soms, bijvoorbeeld wanneer we een winkelwagentje bouwen, we hebben onze boon nodig om zijn toestand te onthouden terwijl we heen en weer gaan tussen methodeaanroepen.

In dit geval hebben we onze container nodig om voor elke aanroep een aparte bean te genereren en de staat op te slaan. Laten we eens kijken hoe dit kan worden bereikt met onze technologieën in kwestie.

5.1. Stateful EJB-voorbeeld

Net als bij ons singleton EJB-monster, hebben we een javax.ejb.Remote interface en de implementatie ervan. Alleen deze keer is het geannoteerd met javax.ejb.Stateful:

@Stateful openbare klasse ShoppingCartEJB implementeert ShoppingCartEJBRemote {private String-naam; privélijst shoppingCart; public void addItem (String-item) {shoppingCart.add (item); } // constructor, getters en setters}

Laten we een eenvoudige test schrijven om een naam en voeg items toe aan een badenCart. We zullen de grootte controleren en de naam verifiëren:

@Test openbare leegte gegevenStatefulBean_whenBathingCartWithThreeItemsAdded_thenItemsSizeIsThree () gooit NamingException {ShoppingCartEJBRemote badenCart = (ShoppingCartEJBRemote) context.lookup ("java: global / ejb-beans / ShoppingCartEJB"); badenCart.setName ("badenCart"); badenCart.addItem ("soap"); badenCart.addItem ("shampoo"); badenCart.addItem ("olie"); assertEquals (3, badenCart.getItems (). size ()); assertEquals ("badenCart", badenCart.getName ()); } 

Om aan te tonen dat de bean echt de status behoudt in alle instanties, laten we nog een shoppingCartEJB aan deze test toevoegen:

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

Hier hebben we de naam en daarom was de waarde ervan nul. Bedenk uit de singleton-test dat de naam die in het ene exemplaar was ingesteld, in het andere werd behouden. Dit toont aan dat we uit elkaar zijn geraakt WinkelenWinkelwagenEJB instanties uit de bean-pool met verschillende instantiestatussen.

5.2. Voorbeeld van een Stateful Spring Bean

Om hetzelfde effect met Spring te krijgen, hebben we een Component met een prototype scope:

@Component @Scope (waarde = ConfigurableBeanFactory.SCOPE_PROTOTYPE) openbare klasse ShoppingCartBean {// dezelfde inhoud als in de EJB} 

Dat is alles, alleen de annotaties verschillen - de rest van de code blijft hetzelfde.

Om onze Stateful bean te testen, kunnen we dezelfde test gebruiken als beschreven voor EJB's. Het enige verschil is weer hoe we de boon uit de container halen:

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

6. Staatloze EJB! = Alles in het voorjaar

Soms, bijvoorbeeld in een zoek-API, we geven niet om de instantiestatus van een boon, noch of het een singleton is. We hebben alleen de resultaten van onze zoekopdracht nodig, die van elke bean-instantie kunnen komen voor alles waar we om geven.

6.1. Staatloos EJB-voorbeeld

Voor dergelijke scenario's heeft EJB een staatloze variant. De container houdt een instantie-pool van bonen bij, en elk daarvan wordt teruggestuurd naar de aanroepende methode.

De manier waarop we het definiëren is hetzelfde als bij andere EJB-typen, met een externe interface en implementatie met javax.ejb.Stateless annotatie:

@Stateless openbare klasse FinderEJB implementeert FinderEJBRemote {privékaartalfabet; openbare FinderEJB () {alfabet = nieuwe HashMap (); alfabet.put ("A", "Apple"); // voeg hier meer waarden in de kaart toe} public String search (String trefwoord) {return alphabet.get (trefwoord); }} 

Laten we nog een eenvoudige test toevoegen om dit in actie te zien:

@Test openbare leegte gegevenStatelessBean_whenSearchForA_thenApple () gooit NamingException {assertEquals ("Apple", alphabetFinder.search ("A")); } 

In het bovenstaande voorbeeld alfabetFinder wordt als veld in de testklasse geïnjecteerd met behulp van de annotatie javax.ejb.EJB:

@EJB privé FinderEJBRemote alphabetFinder; 

Het centrale idee achter Stateless EJB's is om de prestaties te verbeteren door een instantie-pool van vergelijkbare bonen te hebben.

Echter, Spring onderschrijft deze filosofie niet en biedt alleen singletons als staatloos aan.

7. Bericht Driven Beans == Spring JMS

Alle tot dusver besproken EJB's waren sessiebonen. Een andere soort is de berichtgestuurde versie. Zoals de naam al doet vermoeden, ze worden doorgaans gebruikt voor asynchrone communicatie tussen twee systemen.

7.1. MDB-voorbeeld

Om een ​​berichtgestuurde Enterprise Java Bean te maken, moeten we het javax.jms.MessageListener interface die zijn onMessage methode en annoteer de klasse als javax.ejb.MessageDriven:

@MessageDriven (ActivationConfig = {@ActivationConfigProperty (propertyName = "destination", propertyValue = "myQueue"), @ActivationConfigProperty (propertyName = "destinationType", propertyValue = "javax.jms.Queue")}) public class MessageListener {@ implementeert Resource privé ConnectionFactory connectionFactory; @Resource (name = "ackQueue") privé wachtrij ackQueue; public void onMessage (berichtbericht) {probeer {TextMessage textMessage = (TextMessage) bericht; String producerPing = textMessage.getText (); if (producerPing.equals ("marco")) {erkennen ("polo"); }} catch (JMSException e) {throw nieuwe IllegalStateException (e); }}} 

Merk op dat we ook een aantal configuraties bieden voor onze MDB:

      • bestemmingstype net zo Wachtrij
      • myQueue als de bestemming wachtrijnaam, waarnaar onze boon luistert

In dit voorbeeld is onze ontvanger produceert ook een bevestiging, en is in die zin een afzender op zich. Het stuurt een bericht naar een andere wachtrij met de naam ackQueue.

Laten we dit nu in actie zien met een test:

@Test openbare ongeldige gegevenMDB_whenMessageSent_thenA bevestigingReceived () gooit InterruptedException, JMSException, NamingException {Connection connection = connectionFactory.createConnection (); connection.start (); Sessiesessie = connection.createSession (false, Session.AUTO_ACKNOWLEDGE); MessageProducer producer = session.createProducer (myQueue); producer.send (session.createTextMessage ("marco")); MessageConsumer antwoord = session.createConsumer (ackQueue); assertEquals ("polo", ((TextMessage) response.receive (1000)). getText ()); } 

Hier we hebben een bericht gestuurd naar myQueue, die werd ontvangen door ons @MessageDriven geannoteerd POJO. Deze POJO stuurde vervolgens een bevestiging en onze test ontving het antwoord als een MessageConsumer.

7.2. Spring JMS-voorbeeld

Nou, nu is het tijd om hetzelfde te doen met Spring!

Hiervoor moeten we eerst een beetje configuratie toevoegen. We moeten onze ApplicationConfig les van vroeger met @BuienRadarNL en voeg een paar bonen toe aan de setup JmsListenerContainerFactory en JmsTemplate:

@EnableJms openbare klasse ApplicationConfig {@Bean openbaar DefaultJmsListenerContainerFactory jmsListenerContainerFactory () {DefaultJmsListenerContainerFactory factory = nieuw DefaultJmsListenerContainerFactory (); factory.setConnectionFactory (connectionFactory ()); terugkeer fabriek; } @Bean public ConnectionFactory connectionFactory () {retourneer nieuwe ActiveMQConnectionFactory ("tcp: // localhost: 61616"); } @Bean openbare JmsTemplate jmsTemplate () {JmsTemplate-sjabloon = nieuwe JmsTemplate (connectionFactory ()); template.setConnectionFactory (connectionFactory ()); retour sjabloon; }} 

Vervolgens hebben we een Producent - een simpele lente Component - dat zal berichten sturen naar myQueue en ontvang een bevestiging van ackQueue:

@Component public class Producer {@Autowired private JmsTemplate jmsTemplate; public void sendMessageToDefaultDestination (laatste String-bericht) {jmsTemplate.convertAndSend ("myQueue", bericht); } openbare String ontvangstAck () {terugkeer (String) jmsTemplate.receiveAndConvert ("ackQueue"); }} 

Dan hebben we een OntvangerComponent met een methode geannoteerd als @JmsListener om berichten asynchroon te ontvangen van myQueue:

@Component public class Receiver {@Autowired private JmsTemplate jmsTemplate; @JmsListener (bestemming = "myQueue") openbare ongeldige ontvangst ontvangen (string msg) {sendAck (); } private void sendAck () {jmsTemplate.convertAndSend ("ackQueue", "polo"); }} 

Het fungeert ook als afzender voor ontvangstbevestiging van het bericht op ackQueue.

Zoals onze praktijk is, laten we dit verifiëren met een test:

@Test openbare ongeldig gegeven JMSBean_whenMessageSent_thenA bevestigingReceived () gooit NamingException {Producer producer = context.getBean (Producer.class); producer.sendMessageToDefaultDestination ("marco"); assertEquals ("polo", producer.receiveAck ()); } 

In deze test hebben we verzonden Marco naar myQueue en ontvangen polo als erkenning van ackQueue, hetzelfde als wat we deden met de EJB.

Een ding dat hier moet worden opgemerkt, is dat Spring JMS kan berichten zowel synchroon als asynchroon verzenden / ontvangen.

8. Conclusie

In deze tutorial zagen we een een-op-een vergelijking van Spring en Enterprise Java Beans. We begrepen hun geschiedenis en fundamentele verschillen.

Vervolgens hebben we eenvoudige voorbeelden behandeld om de vergelijking van Spring Beans en EJB's te demonstreren. Onnodig te zeggen, het krabt slechts aan de oppervlakte van waartoe de technologieën in staat zijn, en er valt nog veel meer te onderzoeken.

Bovendien kunnen dit concurrerende technologieën zijn, maar dat betekent niet dat ze niet naast elkaar kunnen bestaan. We kunnen EJB's gemakkelijk integreren in het Spring-framework.

Zoals altijd is de broncode beschikbaar op GitHub.