Spring Bean vs. EJB - En funktionssammenligning

1. Oversigt

I årenes løb har Java-økosystemet udviklet sig og vokset enormt. I løbet af denne tid er Enterprise Java Beans og Spring to teknologier, der ikke kun har konkurreret, men lært af hinanden symbiotisk.

I denne vejledning vi kigger på deres historie og forskelle. Selvfølgelig vil vi se nogle kodeeksempler på EJB og deres ækvivalenter i forårets verden.

2. En kort historie om teknologierne

Lad os starte med et hurtigt kig på historien om disse to teknologier, og hvordan de støt har udviklet sig gennem årene.

2.1. Enterprise Java Beans

EJB-specifikationen er en delmængde af Java EE (eller J2EE, nu kendt som Jakarta EE) -specifikationen. Dens første version kom ud i 1999, og det var en af ​​de første teknologier designet til at gøre det nemmere at udvikle applikationer på serversiden på Java.

Det påhviler Java-udviklernes byrde af samtidighed, sikkerhed, vedholdenhed, transaktionsbehandling, og mere. Specifikationen overleverede disse og andre almindelige virksomhedsproblemer til de implementerende applikationsservers containere, der håndterede dem problemfrit. At bruge EJB'er som de var var imidlertid en smule besværlig på grund af den nødvendige konfigurationsmængde. Desuden viste det sig at være en præstationsflaskehals.

Men nu, med opfindelsen af ​​annoteringer og hård konkurrence fra Spring, er EJB'er i deres seneste 3.2-version meget nemmere at bruge end deres debutversion. Enterprise Java Beans i dag låner stærkt fra Spring's afhængighedsindsprøjtning og brug af POJO'er.

2.2. Forår

Mens EJB'er (og Java EE generelt) kæmpede for at tilfredsstille Java-samfundet, ankom Spring Framework som et frisk pust. Dens første milepælsudgivelse kom ud i år 2004 og tilbød et alternativ til EJB-modellen og dens tunge containere.

Tak til foråret, Java-virksomhedsapplikationer kunne nu køres på IOC-containere med lettere vægt. Derudover tilbød det også afhængighedsinversion, AOP og dvale-support blandt utallige andre nyttige funktioner. Med enorm støtte fra Java-samfundet er Spring nu vokset eksponentielt og kan betegnes som en komplet Java / JEE-applikationsramme.

I sin seneste avatar understøtter Spring 5.0 endda den reaktive programmeringsmodel. En anden offshoot, Spring Boot, er en komplet game-changer med dens integrerede servere og automatiske konfigurationer.

3. Optakt til funktionssammenligningen

Inden vi springer til funktions sammenligningen med kodeeksempler, lad os etablere et par grundlæggende.

3.1. Grundlæggende forskel mellem de to

For det første er den grundlæggende og tilsyneladende forskel den EJB er en specifikation, mens foråret er en hel ramme.

Specifikationen er implementeret af mange applikationsservere såsom GlassFish, IBM WebSphere og JBoss / WildFly. Dette betyder, at vores valg om at bruge EJB-modellen til vores applikations backend-udvikling ikke er nok. Vi skal også vælge, hvilken applikationsserver vi skal bruge.

Teoretisk er Enterprise Java Beans bærbare på tværs af app-servere, selvom der altid er en forudsætning, at vi ikke skal bruge leverandørspecifikke udvidelser, hvis interoperabilitet skal holdes som en mulighed.

Sekund, Forår som teknologi er tættere på Java EE end EJB med hensyn til dets brede portefølje af tilbud. Mens EJB'er kun specificerer backend-operationer, har Spring ligesom Java EE også understøttelse af UI-udvikling, RESTful API'er og Reaktiv programmering for at nævne nogle få.

3.2. Brugbar information

I de følgende afsnit vil vi se sammenligningen af ​​de to teknologier med nogle praktiske eksempler. Da EJB-funktioner er en delmængde af det meget større Spring-økosystem, går vi efter deres typer og ser deres tilsvarende Spring-ækvivalenter.

For at forstå eksemplerne bedst kan du overveje først at læse om Java EE Session Beans, Message Driven Beans, Spring Bean og Spring Bean Annotations.

Vi bruger OpenJB som vores integrerede container til at køre EJB-prøverne. Til at køre de fleste af Spring-eksemplerne er dens IOC-container tilstrækkelig; til Spring JMS har vi brug for en integreret ApacheMQ-mægler.

For at teste alle vores prøver bruger vi JUnit.

4. Singleton EJB == Forår Komponent

Nogle gange har vi brug for containeren til kun at oprette en enkelt forekomst af en bønne. Lad os for eksempel sige, at vi har brug for en bønne til at tælle antallet af besøgende til vores webapplikation. Denne bønne skal kun oprettes en gang under opstart af applikationen.

Lad os se, hvordan man opnår dette ved hjælp af en Singleton Session EJB og en Spring Komponent.

4.1. Singleton EJB Eksempel

Vi har først brug for en grænseflade for at specificere, at vores EJB har evnen til at blive håndteret eksternt:

@Remote offentlig grænseflade CounterEJBRemote {int count (); String getName (); ugyldigt sætnavn (strengnavn); }

Det næste trin er at definere en implementeringsklasse med kommentaren javax.ejb.Singletonog viola! Vores singleton er klar:

@Singleton offentlig klasse CounterEJB implementerer CounterEJBRemote {private int count = 1; privat strengnavn; public int count () {return count ++; } // getter og setter for navn} 

Men før vi kan teste singleton (eller enhver anden EJB-kodeeksempel), skal vi initialisere ejbContainer og få den sammenhæng:

@BeforeClass offentlig ugyldighed initializeContext () kaster NamingException {ejbContainer = EJBContainer.createEJBContainer (); kontekst = ejbContainer.getContext (); context.bind ("indsprøjt", dette); } 

Lad os nu se på testen:

@Test offentlig ugyldighed givenSingletonBean_whenCounterInvoked_thenCountIsIncremented () kaster NamingException {int count = 0; CounterEJBRemote firstCounter = (CounterEJBRemote) context.lookup ("java: global / ejb-beans / CounterEJB"); firstCounter.setName ("første"); 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 ()); } 

Et par ting at bemærke i ovenstående eksempel:

  • Vi bruger JNDI-opslag for at få counterEJB fra containeren
  • tælle2 opfanger fra punktet tælle forlod singleton ved og tilføjer op til 20
  • secondCounter bevarer det navn, vi har valgt firstCounter

De sidste to punkter viser betydningen af ​​en singleton. Da den samme bønneinstans bruges hver gang den kigges op, er det samlede antal 20, og værdien, der er indstillet for den ene, forbliver den samme for den anden.

4.2. Singleton Spring Bean Eksempel

Den samme funktionalitet kan opnås ved hjælp af fjederkomponenter.

Vi behøver ikke implementere nogen grænseflade her. I stedet tilføjer vi @Komponent kommentar:

@Component public class CounterBean {// samme indhold som i EJB}

Faktisk er komponenter som standard singletoner i foråret.

Vi skal også konfigurere Spring til at scanne efter komponenter:

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

Svarende til hvordan vi initialiserede EJB-konteksten, indstiller vi nu forårskonteksten:

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

Lad os nu se vores Komponent i aktion:

@Test offentlig ugyldig nårCounterInvoked_thenCountIsIncremented () kaster NamingException {CounterBean firstCounter = context.getBean (CounterBean.class); firstCounter.setName ("første"); int-antal = 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 ()); } 

Som vi kan se, er den eneste forskel i forhold til EJB'er, hvordan vi får bønnen fra Spring container-sammenhængen i stedet for JNDI-opslag.

5. Stateful EJB == Spring Komponent med prototype Anvendelsesområde

Sig til tider når vi bygger en indkøbskurv, vi har brug for vores bønne for at huske dens tilstand, mens vi går frem og tilbage mellem metodekald.

I dette tilfælde har vi brug for vores container til at generere en separat bønne til hver påkaldelse og gemme staten. Lad os se, hvordan dette kan opnås med vores pågældende teknologier.

5.1. Stateful EJB Eksempel

I lighed med vores singleton EJB-prøve har vi brug for en javax.ejb. fjernbetjening interface og dets implementering. Kun denne gang er det kommenteret med javax.ejb.Stateful:

@Stateful offentlig klasse ShoppingCartEJB implementerer ShoppingCartEJBRemote {privat strengnavn; private Liste shoppingCart; public void addItem (String item) {shoppingCart.add (item); } // konstruktør, getters og setters}

Lad os skrive en simpel test for at indstille en navn og tilføj emner til en badevogn. Vi kontrollerer dens størrelse og verificerer navnet:

@Test offentlig ugyldighed givetStatefulBean_whenBathingCartWithThreeItemsAdded_thenItemsSizeIsThree () smider NamingException {ShoppingCartEJBRemote bathingCart = (ShoppingCartEJBRemote) context.lookup ("java: global / ejb-beans / ShoppingCartEJB; bathingCart.setName ("bathingCart"); bathingCart.addItem ("sæbe"); bathingCart.addItem ("shampoo"); bathingCart.addItem ("olie"); assertEquals (3, bathingCart.getItems (). størrelse ()); assertEquals ("bathingCart", bathingCart.getName ()); } 

For at demonstrere, at bønnen virkelig opretholder tilstand på tværs af tilfælde, lad os tilføje endnu en shoppingCartEJB til denne test:

ShoppingCartEJBRemote fruitCart = (ShoppingCartEJBRemote) context.lookup ("java: global / ejb-beans / ShoppingCartEJB"); fruitCart.addItem ("æbler"); fruitCart.addItem ("appelsiner"); assertEquals (2, fruitCart.getItems (). størrelse ()); assertNull (fruitCart.getName ()); 

Her indstillede vi ikke navn og dermed var dens værdi nul. Husk fra singleton-testen, at navnet i en instans blev bevaret i en anden. Dette viser, at vi blev adskilt ShoppingCartEJB forekomster fra bønnepuljen med forskellige forekomsttilstande.

5.2. Stateful Spring Bean Eksempel

For at få den samme effekt med Spring, har vi brug for en Komponent med en prototype rækkevidde:

@Component @Scope (værdi = ConfigurableBeanFactory.SCOPE_PROTOTYPE) offentlig klasse ShoppingCartBean {// samme indhold som i EJB} 

Det er det, bare kommentarerne adskiller sig - resten af ​​koden forbliver den samme.

For at teste vores Stateful bønne kan vi bruge den samme test som beskrevet for EJB'er. Den eneste forskel er igen, hvordan vi får bønnen fra containeren:

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

6. Statsløs EJB! = Alt om foråret

Nogle gange, for eksempel i en søgning-API, vi bryr os ikke om forekomsten af ​​en bønne, eller om det er en singleton. Vi har bare brug for resultaterne af vores søgning, som muligvis kommer fra enhver bønneinstans for alt, hvad vi holder af.

6.1. Statsløs EJB-eksempel

For sådanne scenarier har EJB en statsløs variant. Beholderen opretholder en forekomstpool af bønner, og enhver af dem returneres til kaldemetoden.

Den måde, vi definerer det på, er den samme som andre EJB-typer med en ekstern grænseflade og implementering med javax.ejb.Stateless kommentar:

@Stateless offentlig klasse FinderEJB implementerer FinderEJBRemote {privat kortalfabet; public FinderEJB () {alphabet = new HashMap (); alphabet.put ("A", "Apple"); // tilføj flere værdier på kortet her} offentlig String-søgning (String-nøgleord) {returner alphabet.get (nøgleord); }} 

Lad os tilføje endnu en simpel test for at se dette i aktion:

@Test offentlig ugyldighed givenStatelessBean_whenSearchForA_thenApple () kaster NamingException {assertEquals ("Apple", alphabetFinder.search ("A")); } 

I ovenstående eksempel alphabetFinder injiceres som et felt i testklassen ved hjælp af kommentaren javax.ejb.EJB:

@EJB privat FinderEJBRemote alphabetFinder; 

Den centrale idé bag statsløse EJB'er er at forbedre ydeevnen ved at have en forekomst af lignende bønner.

Imidlertid, Foråret abonnerer ikke på denne filosofi og tilbyder kun singler som statsløse.

7. Message Driven Beans == Spring JMS

Alle hidtil diskuterede EJB'er var sessionbønner. En anden slags er den budskabsstyrede. Som navnet antyder, de bruges typisk til asynkron kommunikation mellem to systemer.

7.1. MDB-eksempel

For at oprette en meddelelsesdrevet Enterprise Java Bean skal vi implementere javax.jms.MessageListener interface, der definerer dens onMessage metode og kommentere klassen som javax.ejb.MessageDriven:

@MessageDriven (aktiveringConfig = {@ActivationConfigProperty (propertyName = "destination", propertyValue = "myQueue"), @ActivationConfigProperty (propertyName = "destinationType", propertyValue = "javax.jms.Queue")}) public class RecieverMD Ressource privat ConnectionFactory connectionFactory; @Resource (name = "ackQueue") privat kø ackQueue; public void onMessage (Message message) {prøv {TextMessage textMessage = (TextMessage) meddelelse; String producerPing = textMessage.getText (); hvis (producerPing.equals ("marco")) {anerkender ("polo"); }} fange (JMSException e) {smide ny IllegalStateException (e); }}} 

Bemærk, at vi også leverer et par konfigurationer til vores MDB:

      • destinationType som
      • myQueue som den bestemmelsessted kønavn, som vores bønne lytter til

I dette eksempel er vores modtager producerer også en anerkendelse og er i den forstand en afsender i sig selv. Den sender en besked til en anden kaldet kø ackQueue.

Lad os nu se dette i aktion med en test:

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

Her vi sendte en besked til myQueue, som blev modtaget af vores @MessageDriven kommenteret POJO. Denne POJO sendte derefter en kvittering, og vores test modtog svaret som en MessageConsumer.

7.2. Spring JMS-eksempel

Nå, nu er det tid til at gøre det samme ved hjælp af Spring!

Først skal vi tilføje en smule konfiguration til dette formål. Vi er nødt til at kommentere vores ApplicationConfig klasse fra før med @EnableJms og tilføj et par bønner til opsætningen JmsListenerContainerFactory og JmsTemplate:

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

Dernæst har vi brug for en Producent - en simpel forår Komponent - der vil sende beskeder til myQueue og modtage en bekræftelse fra ackQueue:

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

Så har vi en ModtagerKomponent med en metode, der er kommenteret som @JmsListener at modtage beskeder asynkront fra myQueue:

@ Komponent offentlig klassemodtager {@Autowired privat JmsTemplate jmsTemplate; @JmsListener (destination = "myQueue") public void receiveMessage (String msg) {sendAck (); } privat ugyldigt sendAck () {jmsTemplate.convertAndSend ("ackQueue", "polo"); }} 

Det fungerer også som afsender for at bekræfte modtagelse af meddelelse kl ackQueue.

Som det er vores praksis, lad os kontrollere dette med en test:

@Test offentligt ugyldigt givetJMSBean_whenMessageSent_thenAcknowledgementReceived () kaster NamingException {Producer producer = context.getBean (Producer.class); producer.sendMessageToDefaultDestination ("marco"); assertEquals ("polo", producer.receiveAck ()); } 

I denne test sendte vi marco til myQueue og modtaget polo som en anerkendelse fra ackQueue, det samme som hvad vi gjorde med EJB.

En ting at bemærke her er, at Spring JMS kan sende / modtage beskeder både synkront og asynkront.

8. Konklusion

I denne vejledning så vi en en-til-en sammenligning af Spring og Enterprise Java Beans. Vi forstod deres historie og grundlæggende forskelle.

Derefter beskæftigede vi os med enkle eksempler for at demonstrere sammenligningen af ​​forårsbønner og EJB'er. Det er overflødigt at sige, det skraber blot overfladen af, hvad teknologierne er i stand til, og der er meget mere, der skal udforskes yderligere.

Desuden kan det være konkurrerende teknologier, men det betyder ikke, at de ikke kan eksistere sammen. Vi kan let integrere EJB'er i foråret.

Som altid er kildekoden tilgængelig på GitHub.