Spring Bean vs. EJB - usporedba značajki

1. Pregled

Tijekom godina, ekosustav Java se izuzetno razvio i narastao. Za to su vrijeme Enterprise Java Beans i Spring dvije tehnologije koje su se ne samo natjecale već simbiotski učile jedna od druge.

U ovom vodiču, pogledat ćemo njihovu povijest i razlike. Naravno, vidjet ćemo nekoliko primjera koda EJB-a i njihovih ekvivalenata u proljetnom svijetu.

2. Kratka povijest tehnologija

Za početak, zavirimo u povijest ove dvije tehnologije i kako su se one neprestano razvijale tijekom godina.

2.1. Poduzeće Java Beans

Specifikacija EJB podskup je specifikacije Java EE (ili J2EE, danas poznate kao Jakarta EE). Njegova prva verzija izašla je 1999. godine i bila je jedna od prvih tehnologija osmišljenih kako bi olakšala razvoj poslovnih aplikacija na poslužitelju u Javi.

To je prebacilo teret Java paralelnosti, sigurnosti, trajnosti i obrade transakcija za programere Jave, i više. Specifikacija je te i druge uobičajene zabrinutosti poduzeća predala spremnicima poslužitelja aplikacija koji su ih bez problema rješavali. Međutim, upotreba EJB-ova kakvi su bili bila je pomalo glomazna zbog količine potrebne konfiguracije. Štoviše, pokazalo se da je to usko grlo u izvedbi.

Ali sada, s izumom napomena i jakom konkurencijom od proljeća, EJB-ovi u njihovoj najnovijoj verziji 3.2 puno su jednostavniji za upotrebu od njihove prve verzije. Današnji Enterprise Java Beans uvelike se zadužuje za injekciju ovisnosti Springa i upotrebu POJO-a.

2.2. Proljeće

Dok su se EJB-ovi (i Java EE općenito) borili da zadovolje Java zajednicu, Spring Framework stigao je poput daška svježeg zraka. Prvo izdanje prekretnice objavljeno je 2004. godine i ponudilo je alternativu modelu EJB i njegovim teškim kontejnerima.

Zahvaljujući proljeću, Java korporativni programi sada bi se mogli izvoditi na lakšim IOC spremnicima. Štoviše, ponudio je i inverziju ovisnosti, AOP i podršku hibernacije među bezbroj drugih korisnih značajki. Uz ogromnu podršku Java zajednice, Spring je sada eksponencijalno narastao i može se nazvati potpunim Java / JEE aplikacijskim okvirom.

U svom najnovijem avataru, Spring 5.0 čak podržava model reaktivnog programiranja. Još jedan izdanak, Spring Boot, potpuni je izmjenjivač igara s ugrađenim poslužiteljima i automatskim konfiguracijama.

3. Preludij za usporedbu značajki

Prije nego što prijeđemo na usporedbu značajki s uzorcima koda, uspostavimo nekoliko osnova.

3.1. Osnovna razlika između njih dvoje

Prvo, osnovna i prividna razlika je u tome EJB je specifikacija, dok je proljeće čitav okvir.

Specifikaciju provode mnogi aplikacijski poslužitelji kao što su GlassFish, IBM WebSphere i JBoss / WildFly. To znači da naš izbor da koristimo EJB model za razvoj pozadine naše aplikacije nije dovoljan. Također trebamo odabrati koji će poslužitelj aplikacija koristiti.

Teoretski, Enterprise Java Beans prenosivi su na poslužitelje aplikacija, premda uvijek postoji preduvjet da ne bismo trebali upotrebljavati proširenja specifična za dobavljača ako se želi zadržati interoperabilnost.

Drugi, Proljeće kao tehnologija bliže je Javi EE nego EJB u smislu svog širokog portfelja ponuda. Iako EJB-ovi određuju samo pozadinske operacije, Spring, poput Java EE-a, također ima podršku za razvoj korisničkog sučelja, RESTful API-je i reaktivno programiranje da nabrojimo samo neke.

3.2. Korisna informacija

U sljedećim odjeljcima vidjet ćemo usporedbu dviju tehnologija s nekoliko praktičnih primjera. Budući da su EJB značajke podskup puno većeg proljetnog ekosustava, provest ćemo se prema njihovim vrstama i vidjeti njihove odgovarajuće proljetne ekvivalente.

Da biste najbolje razumjeli primjere, prvo razmotrite čitanje Java EE sesije graha, graha vođenog porukama, Spring Bean i Spring Bean Bilješke.

Koristit ćemo OpenJB kao naš ugrađeni spremnik za pokretanje EJB uzoraka. Za pokretanje većine primjera Spring, dovoljan je njegov IOC spremnik; za Spring JMS trebat će nam ugrađeni ApacheMQ posrednik.

Za testiranje svih naših uzoraka upotrijebit ćemo JUnit.

4. Singleton EJB == Proljeće Komponenta

Spremnik nam ponekad treba za stvaranje samo jedne instance graha. Na primjer, recimo da nam treba grah za brojanje broja posjetitelja naše web aplikacije. Ovaj grah treba stvoriti samo jednom tijekom pokretanja aplikacije.

Pogledajmo kako to postići koristeći Singleton Session EJB i Spring Komponenta.

4.1. Primjer pojedinačnog EJB-a

Prvo će nam trebati sučelje da odredimo da naš EJB ima mogućnost daljinskog rukovanja:

@Udaljeno javno sučelje CounterEJBRemote {int count (); Niz getName (); void setName (naziv niza); }

Sljedeći je korak definirati klasu implementacije s napomenom javax.ejb.Singleton, i viola! Naš singl je spreman:

@Singleton javna klasa CounterEJB implementira CounterEJBRemote {private int count = 1; privatni naziv niza; javni int count () {return count ++; } // dobivač i postavljač za ime} 

Ali prije nego što možemo testirati singleton (ili bilo koji drugi uzorak EJB koda), moramo inicijalizirati ejbContainer i dobiti kontekst:

@BeforeClass public void initializeContext () baca NamingException {ejbContainer = EJBContainer.createEJBContainer (); context = ejbContainer.getContext (); context.bind ("ubrizgati", ovo); } 

Pogledajmo sada test:

@Test javna praznina givenSingletonBean_whenCounterInvoked_thenCountIsIncremented () baca NamingException {int count = 0; CounterEJBRemote firstCounter = (CounterEJBRemote) context.lookup ("java: global / ejb-beans / CounterEJB"); firstCounter.setName ("first"); za (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; za (int i = 0; i <10; i ++) {count2 = secondCounter.count (); } assertEquals (20, count2); assertEquals ("first", secondCounter.getName ()); } 

Nekoliko stvari koje valja primijetiti u gornjem primjeru:

  • Za dobivanje koristimo pretragu JNDI brojačEJB iz kontejnera
  • računati2 pokupi od točke računati ostavio singleton na i zbraja 20
  • secondCounter zadržava ime koje smo postavili firstCounter

Posljednje dvije točke pokazuju značaj pojedinca. Budući da se koristi ista instanca graha svaki put kada se pogleda, ukupni broj je 20, a vrijednost postavljena za jedan ostaje ista za drugi.

4.2. Primjer jednog graha proljetnog graha

Ista se funkcionalnost može dobiti pomoću komponenata Spring.

Ovdje ne trebamo implementirati nijedno sučelje. Umjesto toga, mi ćemo dodati @Komponenta napomena:

@Component javna klasa CounterBean {// isti sadržaj kao u EJB}

Zapravo, komponente su prema zadanim postavkama jednokrevetne u proljeće.

Također moramo konfigurirati Spring da skenira komponente:

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

Slično kao što smo inicijalizirali EJB kontekst, sada ćemo postaviti proljetni kontekst:

@BeforeClass javna statička void init () {context = new AnnotationConfigApplicationContext (ApplicationConfig.class); } 

Sada da vidimo naše Komponenta u akciji:

@Test public void whenCounterInvoked_thenCountIsIncremented () baca NamingException {CounterBean firstCounter = context.getBean (CounterBean.class); firstCounter.setName ("first"); broj brojeva = 0; za (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; za (int i = 0; i <10; i ++) {count2 = secondCounter.count (); } assertEquals (20, count2); assertEquals ("first", secondCounter.getName ()); } 

Kao što vidimo, jedina razlika u odnosu na EJB-ove je kako dobivamo grah iz konteksta Spring kontejnera, umjesto JNDI pretraživanja.

5. Stateful EJB == Proljeće Komponenta s prototip Opseg

Ponekad recimo kad gradimo košaricu, trebamo da naš grah pamti svoje stanje dok se kreće naprijed-natrag između poziva metode.

U ovom nam slučaju treba naš spremnik za generiranje zasebnog graha za svaki poziv i spremanje stanja. Pogledajmo kako se to može postići našim dotičnim tehnologijama.

5.1. Primjer državnog EJB-a

Slično našem singleton EJB uzorku, potreban nam je javax.ejb.Udaljeno sučelje i njegova implementacija. Samo ovaj put, s naznakom javax.ejb.Državno:

@Stateful javna klasa ShoppingCartEJB implementira ShoppingCartEJBRemote {naziv privatnog niza; privatni popis shoppingCart; public void addItem (String item) {shoppingCart.add (item); } // konstruktor, getteri i postavljači}

Napišimo jednostavan test za postavljanje a Ime i dodajte stavke u a kolica za kupanje. Provjerit ćemo njegovu veličinu i naziv:

@Test javna praznina givenStatefulBean_whenBathingCartWithThreeItemsAdded_thenItemsSizeIsThree () baca NamingException {ShoppingCartEJBRemote bathingCart = (ShoppingCartEJBRemote) context.lookup ("java: global / ejB -JB / ShoppingCart / ShoppingCart / ShoppingCart / ShoppingCart / ShoppingCart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBart / ShoppingBartBB bathingCart.setName ("bathingCart"); bathingCart.addItem ("sapun"); bathingCart.addItem ("šampon"); bathingCart.addItem ("ulje"); assertEquals (3, bathingCart.getItems (). size ()); assertEquals ("bathingCart", bathingCart.getName ()); } 

Sada, da pokažemo da grah stvarno održava stanje u svim instancama, dodajmo još jedan shoppingCartEJB ovom testu:

ShoppingCartEJBRemote fruitCart = (ShoppingCartEJBRemote) context.lookup ("java: global / ejb-grah / ShoppingCartEJB"); fruitCart.addItem ("jabuke"); fruitCart.addItem ("naranče"); assertEquals (2, fruitCart.getItems (). size ()); assertNull (fruitCart.getName ()); 

Ovdje nismo postavili Ime i stoga je njegova vrijednost bila nula. Podsjetimo iz singleton testa da je naziv postavljen u jednom slučaju zadržan u drugom. To pokazuje da smo se odvojili ShoppingCartEJB instance iz spremišta graha s različitim stanjima instance.

5.2. Primjer državnog proljetnog graha

Da bismo dobili isti efekt s Springom, trebamo Komponenta s opsegom prototipa:

@Component @Scope (value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) javna klasa ShoppingCartBean {// isti sadržaj kao u EJB} 

To je to, samo se napomene razlikuju - ostatak koda ostaje isti.

Da bismo testirali svoj Stateful grah, možemo koristiti isti test kao što je opisano za EJB. Jedina je razlika opet u tome kako grah dobivamo iz spremnika:

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

6. EJB bez državljanstva! = Bilo što u proljeće

Ponekad, na primjer u API-ju za pretraživanje, niti nas je briga za stanje instance graha niti je li to jedno jedinjenje. Potrebni su nam samo rezultati pretraživanja koji mogu doći iz bilo kojeg primjerka graha za sve do čega nam je stalo.

6.1. Primjer EJB bez državljanstva

Za takve scenarije EJB ima varijantu bez državljanstva. Spremnik održava spremište primjera graha i bilo koji od njih vraća se na metodu pozivanja.

Način na koji ga definiramo jednak je kao i drugi EJB tipovi, s udaljenim sučeljem i implementacijom s javax.ejb.Bez države napomena:

@Statless javna klasa FinderEJB implementira FinderEJBRemote {abeceda privatne mape; javni FinderEJB () {abeceda = novi HashMap (); alphabet.put ("A", "Apple"); // ovdje dodamo više vrijednosti na mapi} public String search (String keyword) {return alphabet.get (keyword); }} 

Dodajmo još jedan jednostavan test da to vidimo na djelu:

@Test javna praznina givenStatelessBean_whenSearchForA_thenApple () baca NamingException {assertEquals ("Apple", alphabetFinder.search ("A")); } 

U gornjem primjeru, abeceda ubrizgava se kao polje u testnoj klasi pomoću bilješke javax.ejb.EJB:

@EJB private FinderEJBRemote alphabetFinder; 

Centralna ideja koja stoji iza EJB-a bez državljanstva je poboljšati izvedbu tako što će imati instancijski fond sličnih graha.

Međutim, Proljeće se ne slaže s ovom filozofijom i nudi samo single osobe bez državljanstva.

7. Grah vođen porukom == Spring JMS

Svi EJB-ovi do sada raspravljeni bili su sesije graha. Druga vrsta je ona vođena porukom. Kao što i samo ime govori, obično se koriste za asinkronu komunikaciju između dvaju sustava.

7.1. Primjer MDB-a

Da bismo stvorili Enterprise Java Bean na temelju poruka, moramo implementirati javax.jms.MessageListener sučelje definirajući svoj onMessage metodu i klasu označite kao javax.ejb.MessageDriven:

@MessageDriven (activationConfig = {@ActivationConfigProperty (propertyName = "odredište", propertyValue = "myQueue"), @ActivationConfigProperty (propertyName = "destinationType", propertyValue = "javax.jms.Queue") }B javna klasa RecieverMistenerM ReciverListenerMisteListenerMisteLMerDistListeMisteDMDD {RequestverMisteMisteMMD) {B> Resurs private ConnectionFactory connectionFactory; @Resource (name = "ackQueue") privatni red čekanja ackQueue; javna praznina onMessage (poruka poruke) {try {TextMessage textMessage = (TextMessage) poruka; Niz proizvođačPing = textMessage.getText (); if (proizvođačPing.equals ("marco")) {priznati ("polo"); }} catch (JMSException e) {baciti novi IllegalStateException (e); }}} 

Primijetite da također pružamo nekoliko konfiguracija za naš MDB:

      • odredišteTip kao Red
      • myQueue kao odredište naziv reda, koje sluša naš grah

U ovom primjeru, naš primatelj također daje potvrdu i u tom je smislu pošiljatelj sam po sebi. Šalje poruku drugom redu koji se zove ackQueue.

Sada da vidimo ovo na djelu s testom:

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

Ovdje poslali smo poruku myQueue, koji je primio naš @MessageDriven s bilješkama POJO. Ovaj POJO je zatim poslao potvrdu i naš je test primio odgovor kao a MessageConsumer.

7.2. Primjer proljetnog JMS-a

E, sad je vrijeme da učinimo istu stvar koristeći Proljeće!

Prvo, u tu svrhu trebamo dodati malo konfiguracije. Moramo bilježiti svoje ApplicationConfig razred od prije sa @EnableJms i dodajte nekoliko graha za postavljanje JmsListenerContainerFactory i JmsTemplate:

@EnableJms javna klasa ApplicationConfig {@Bean public DefaultJmsListenerContainerFactory jmsListenerContainerFactory () {DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory (); factory.setConnectionFactory (connectionFactory ()); tvornica za povratak; } @Bean public ConnectionFactory connectionFactory () {return new ActiveMQConnectionFactory ("tcp: // localhost: 61616"); } @Bean public JmsTemplate jmsTemplate () {JmsTemplate template = new JmsTemplate (connectionFactory ()); template.setConnectionFactory (connectionFactory ()); predložak za povratak; }} 

Dalje, trebamo a Proizvođač - jednostavno proljeće Komponenta - koji će slati poruke na myQueue i dobiti potvrdu od ackQueue:

@ Komponenta javne klase Producent {@Autowired private JmsTemplate jmsTemplate; javna praznina sendMessageToDefaultDestination (konačna niska poruka) {jmsTemplate.convertAndSend ("myQueue", poruka); } javni String receiveAck () {return (String) jmsTemplate.receiveAndConvert ("ackQueue"); }} 

Zatim, imamo a PrijamnikKomponenta s metodom označenom kao @JmsListener za primanje poruka asinkrono od myQueue:

@Component javni razred prijemnika {@Autowired private JmsTemplate jmsTemplate; @JmsListener (odredište = "myQueue") javna praznina receiveMessage (String msg) {sendAck (); } private void sendAck () {jmsTemplate.convertAndSend ("ackQueue", "polo"); }} 

Također djeluje kao pošiljatelj za potvrdu primitka poruke na ackQueue.

Kao što je naša praksa, provjerimo to testom:

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

U ovom smo testu poslali Marko do myQueue i primio polo kao priznanje od ackQueue, isto kao što smo radili s EJB-om.

Ovdje treba napomenuti jednu stvar Spring JMS može slati / primati poruke sinkrono i asinkrono.

8. Zaključak

U ovom uputstvu vidjeli smo pojedinačna usporedba Spring i Enterprise Java Beans-a. Razumjeli smo njihovu povijest i osnovne razlike.

Zatim smo se pozabavili jednostavnim primjerima kako bismo demonstrirali usporedbu proljetnog graha i EJB-a. Nepotrebno je reći, to je samo grebanje površine onoga za što su tehnologije sposobne, a ima još puno toga što treba istražiti.

Nadalje, ovo bi mogle biti konkurentske tehnologije, ali to ne znači da ne mogu supostojati. EJB-ove možemo lako integrirati u proljetni okvir.

Kao i uvijek, izvorni kod dostupan je na GitHub-u.