A következő címkéjű bejegyzések mutatása: EJB. Összes bejegyzés megjelenítése
A következő címkéjű bejegyzések mutatása: EJB. Összes bejegyzés megjelenítése

2012. november 4., vasárnap

Eclipselink beizzítása Jboss 7.1 környezetben

Valaha fejlesztettünk egy portált Liferay platformon. Mivel a fejlesztés elején a gyári Service Buildert elvetettük, teljesen kézenfekvő megoldás volt EJB/JPA párossal implementálni az üzleti és a perzisztens réteget. Első körben Hibernatere esett a választás, mert a Jboss alkalmazásszerver "natívan" támogatta, nem volt más dolgunk, mint deployolni az alkalmazást, és működött. Fejlesztés során nem is akadt semmi problémánk, ám az élesítés előtti utolsó hajrában szembesültünk azzal a ténnyel, hogy a perzisztens réteg teljesítménye a béka feneke alatt van, majd egy kis kutakodás után kikristályosodott, hogy a Hibernate nem bánik túl kedvezően az erőforrásokkal. Nincs mese, másodlagos cachet kell beüzemelni (Ehcachere esett a választás), de sajnos még ezzel sem hozta azt az alkalmazás, amit elvárnánk/megszoktunk. Ekkor jött az ötlet, hogy dobjuk ki a Hibernatet. Eclipselinkkel már volt tapasztalatunk, ráadásul nem is rossz, így megejtettük a váltást. Egyáltalán nem bántuk meg, a teljesítmény az egekbe szökött (pontos számadataink nincsenek, egyszerűen nem bírtuk elérni, hogy az adatbázisszerver CPU 10% fölé menjen, az addigi konstans 80% helyett). Eclipselinkre való átállás nem volt zökkenőmentes, nem pont ugyanúgy értelmezi a JPQL-t mint a Hibernate (egész pontosan az Eclipselink JPQL-t valósít meg, míg a Hibernate a saját HQL-jét használja), ezen felül a Jbossból is ki kellett gyomlálni pár Hibernate jart, úgy, hogy közben a Liferay, ami egyébként Hibernatre épül, még működőképes maradjon.

Az élesítés óta megjelent a Liferayből 2 újabb verzió, és szerettem volna frissen tartani a rendszert, de természetesen az új Liferayhez új Jboss is dukál. A frissítendő architektúra hibridsége miatt nem volt könnyű szülés a művelet, ennek lépéseit szeretném megosztani.

Az új Jboss architektúra teljesen modularizált, ezért egy 3rd party lib használata nem annyi, hogy bemásoljuk a megfelelő jar-t a classpathra, hanem modulként definiálnunk kell a rendszerben. Nincs ez másként az Eclipselinkkel sem. Modul létrehozásához annyit kell tennünk, hogy létrehozzuk a modules/org/eclipse/persistence/main könyvtárakat az alkalmazásszerverünkben, és a main könyvtárba bemásoljuk az eclipselink.jart, a konfigurációhoz pedig létre kell hoznunk ugyanitt egy module.xml-t, amiben a modult magát definiáljuk.


 
 
 
  
  
  
  
  
  
  
  
  
  
  
  
 

Az előző lépéshez hasonlóan egy Ant, és egy adatbázis modult is létre kell hozni (estemben egy PostgreSQLt).

    
        
    


    
        
    
    
        
        
        
    

Ezután nincs más dolgunk az alkalmazásszerveren, mint a standalone/configuration/standalone.xml-t szerkeszteni. Először is definiáljuk a szükséges DataSource-ot.
        
                
                    jdbc:postgresql://[hostname]:[port]/[database]
                    postgres
                    
                        14
                        20
                    
                    
                        postgres
                    
                
                
                    
                        org.postgresql.xa.PGXADataSource
                    
                
            
Lehet, hogy ez Liferay specifikus, de nálam volt Hibernate konfiguráció is ebben az xmlben, ami miatt timeoutolt a perzisztens réteg inicializálása, így azt egy kecses mozdulattal kitöröltem (egyelőre nem jelentkezett a hiányából fakadó probléma).

Miután az alkalmzásszerver konfigurációjával végeztünk, már csak az alkalmazásunkat kell egy kicsit reszelgetni. Ahol JNDI név szerint hivatkoztunk EJB Session Beanekre, ott a hivatkozott nevet célszerű átírni. A Jboss az EJB deployment során kiírja, hogy milyen neveken regisztrálta a beaneket, nekem nem mindegyik működött ??, de ez a forma bevált: global/[appname]/[serviceimplclassname].
A persistence.xml-ben is be kell állítanunk pár beállítást.
org.eclipse.persistence.jpa.PersistenceProvider
java:/jboss/datasources/LiferayPool

 
 


Utolsó lépésként az alkalmazásunkban be kell állítani, hogy milyen Jboss modulok a függőségei, ezt két féle módon is megtehetjük, vagy a META-INF/MANIFEST.MF állományban vesszük fel az alábbi sort:
Dependencies: org.eclipse.persistence
vagy létrehozunk ugyanitt egy jboss-deployment-structure.xml nevű konfigurációs állományt, az alábbi tartalommal:

  
    
      
    
  

Mindenkinek javaslom, hogy tegyen egy próbát az Eclipselinkkel, számos területen jobb, mint a Hibernate, nem csak teljesítményben és szabványkövetésben. Kedvenc tulajdonságom pl, hogy Hibernattel ellentétben a Session bezárása után is eléri még a DataSourcot (limitáltan), így a lustán inicializált relációkat nem kell kézzel betöltögetni még az üzleti rétegben, hanem a helyükre tett proxyn keresztül később is eléri azokat.

2010. március 23., kedd

Egységesített "Global JNDI name" kezelés EJB 3.1 környezetben

Az alkalmazás-szerver, amikor létrehozunk egy Session Bean-t, automatikusan regisztrálja azt a JNDI kontextusban. Az regisztrált Bean-ekre referenciát legegyszerűbben a függőség-injektálással szerezhetünk, a szerver leveszi a direkt név-feloldás terhét vállunkról. Az említett módszer egységesen működik minden alkalmazás-szerveren, viszont a megvalósítása már korántsem egységes. Minden gyártó saját elképzelése szerint oldotta meg a feladatot.
  • JBoss global JNDI name: ejbapp/BeanName/local
  • GlassFish global JNDI name: InterfaceClass
  • WebSphere Community Edition global JNDI name: ejbapp-ejb/BeanName/InterfaceClass
  • Oracle Application Server (OC4J) global JNDI name: BeanName
Mivel az alkalmazás-szerver elrejti előlünk a feloldás problémáját, egészen addig amíg van lehetőségünk a függőség-injektálásra támaszkodni, kódunk hordozható marad. Viszont abban a pillanatban, amikor pl. egy nem menedzselt objektumból, vagy egy unit-tesztelőből kényszerülünk direkt JNDI név-feloldásra, kénytelenek vagyunk gyártó specifikus kódot készíteni.
Egy egyszerű példa szemlélteti a problémát EJB 3 környezetben:
@Remote
public interface JNDITestBeanInterface {
    String test();
}
@Stateless(name="JNDITestBean")
public class JNDITestBean implements JNDITestBeanInterface {
    public String test() {
        return "ok";
    }
}
public class JNDITestBeanClient {
    public static void main(String[] args) throws Exception {
        Context context = new InitialContext();
        JNDITestBeanInterface testBean = (JNDITestBeanInterface) context.lookup("JNDITest/JNDITestBeanInterface/remote");
        System.out.print(testBean.test());
    }
}
Az EJB 3.1 specifikáció megoldást kínál, mégpedig úgy, hogy egységesíti a név-regisztráció módját, így azon alkalmazás-szerverek, melyek implementálni szeretnék a specifikációt kénytelenek követni is azt.


java:global[/<application-name>]/<module-name>/<bean-name>#<interface-name>
@Stateless
@LocalBean
public class JNDITestBean {
    public String test() {
        return "ok";
    }
}
public class JNDITestBeanClient {
    public static void main(String[] args) throws Exception {
        Context context = new InitialContext();
        JNDITestBean testBean = (JNDITestBean) context.lookup("java:global/JNDITest/JNDITest-ejb/JNDITestBean");
        System.out.print(testBean.test());
    }
}
Az "interface-name" elhagyható, amenniyben interface nélküli Bean-re szeretnénk referenciát szerezni, továbbá ha az egyszerűsített deployment technológiát használva a WAR-ba helyeztük az EJB réteget az "application-name" azaz az alkalmazás neve opcionálissá válik.

2010. március 9., kedd

JRebel, avagy hogyan tegyük egyszerűbbé az EEletet

Bár elkötelezett híve vagyok a nyílt forrású szoftverfejlesztésnek,a címben említett eszköz mellett mégsem bírtam elmenni csukott szemmel. Mentségükre legyen mondva, a fizetős terméküket bármely szabad szoftver fejlesztéséhez ingyen biztosítják. A JRebel projekt célja, hogy olyan megvalósításokkal támogassa meg a fejlesztőket, melyek nem része a JVM-nek, így hagyományos módon vagy sehogy, vagy igen körülményesen oldhatóak csak meg. A fejlesztők 5 fő pontot neveznek meg, melyek az alábbiak:
1. Új osztályok hozzáadása
A HotSwap protokoll csak meglévő osztályok frissítését tesz lehetővé futás-időben, mivel egy egyedi megfeleltetés van az osztály neve és tartalma között. Ahhoz, hogy új osztályt adhassunk hozzá, kell válasszunk egy class loadert, mivel minden osztály egy specializált loaderben töltődik be. Sajnos ezt a szolgáltatást nem támogatja a HotSwap.
2. Erő-forrás-állományok frissítése
Az osztályaink mellé gyakran teszünk erő-forrás-állományokat melyek leggyakrabban xml vagy properties fájlok. A HotSwap technológia nem támogatja ezen állományok dinamikus módosítását, azaz minden változtatást deployolni kell a szerverre. Ez elég nagy probléma, főleg ha a szerveren van már pl. feltöltött állomány, mert a deploy folyamat esetlegesen törli azokat.
3. Webes erő-források
Minden weben megjelenő alkalmazásban vannak képek, HTML, CSS, JS, stb állományok, melyek az előző ponthoz hasonlóan nem frissülnek automatikusan a web-konténerben.
4. Gyorsító-tárak
Enterprise környezet nagy előnye, hogy a konténer, legyen szó web-konténerről vagy alkalmazás-konténerről, gyorsító-tárrazza osztályainkat. Ez ez előny egyben egy hátrány is, hiszen új elemek, pl. metódusok hozzáadása után a tár törléséig az előző állapot érhető el. Megjegyzem a legtöbb IDE támogatja a "deploy on save" metódust, mely az osztály mentésekor automatikusan frissíti a szerveren az osztályt.
5. Menedzselt komponensek
Egy alkalmazásban számtalan un. menedzselt komponens (EJB-k, Spring bean-ek, stb.) vannak. Az ilyen osztályokra jellemző, hogy általában van függőség-injektálással a konténer által behelyezett osztály, némely állapota társítva van un. instance vagy instance pool-lal, a funkcionalitás megvalósítása különböző rétegeken történik. Frissítésénél nem elegendő magát az osztályt frissíteni, hanem a különböző rétegeket, injectált osztályokat is.
 
Mindezen problémák feloldására a JRebel egy sajátságos metodikát ajánl, amit egy egyszerű, mégis hétköznapi példán mutatnék be. Jelenleg a 2.2.1-es verzió a legfrissebb, amit a letöltés oldalról szerezhetünk meg, 30 napos próbára. A telepítés grafikusan történik pár lépésben. A telepítő felajánlja, hogy befejezés után futtatja is a varázslót, amely megjegyzem példásan kivitelezett alkalmazás, minek segítségével pofon egyszerűvé válik a konfigurálás. A varázslót "UNIX like" környezetben a bin/jrebel-config.sh script futtatásával "varázsolhatjuk" elő.
  • Nincs más dolgunk mint első lépésben a megfelelő IDE-t kiválasztani, én Netbeans-el próbálkoztam, de támogatott az Eclipse, IntelliJ is.
  • Második lépésben felajánlja a varázsló az IDE-knek megfelelő plugint, Netbeans esetén a beépített pluginek közül a legegyszerűbb beszerezni.
  • Következő, sorban a harmadik lépés, hogy alkalmazásunkat felkészítsük a JRebel használatára. A segédlet mindent érthetően elmagyaráz, EJB projekt esetén a források gyökerébe, web projekt esetén a WEB-INF/classes mappában kell egy-egy rebel.xml állományt elhelyeznünk, és az xml-ben specifikálni, hogy mely osztályokat és/vagy erő-forrásokat szeretnénk ha a JRebel kezelné. Itt térnék ki kicsit a működésre. A JRebel egy újabb rétegként rátelepszik az alkalmzásunkra, és a betöltendő osztályokat vagy erő-forrásokat a rebel.xml alapján közvetlenül a fejlesztőkörnyezetből tölti be. Biztos mindenkinek volt már problémája a dinamikusan fordított web-oldalakkal, amikor is a WEB-INF/classes-ban elhelyezett translate.properties-t minden apró változtatás után deployolni kellett. Mivel ez egy gyakori eset, ebből a problémából indultam ki.
<?xml version="1.0" encoding="UTF-8"?>
<application
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.zeroturnaround.com"
xsi:schemaLocation="http://www.zeroturnaround.com http://www.zeroturnaround.com/alderaan/rebel-2_0.xsd">
<web>
<link target="/"><dir name="~/NetBeansProjects/webapp/webapp-war/web"/></link>
</web>
</application>
  • Negyedik lépéséként választanunk kell Java verziót, operációs rendszert, és konténert, utóbbiból eléggé széles a paletta, én Glassfish 3-at használok, ezért azt választottam. Glassfish esetén az adminisztrációs felületen a JVM Setting / JVM Options alatt 2 paramétert kell megadnunk a JVM-nek, mégpedig a -noverify és -javaagent:/utvonal/jrebel.jar.
  • Ötödik lépésben az ügynök, na jó az agent beállításait kell megadnunk. Az advanced gombra kattintva állíthatjuk be pl. azt, ha nem akarunk a rebel.xml-ben abszolút eléréseket megadni, ennek módját részletesen tárgyalja a varázsló. A beállító-panelt "UNIX like" környezetben a bin/agent-settings.sh script futtatásával indíthatjuk.
  • Nincs más dolgunk, mint újraindítani az alkalmazás-szervert, deployolni a rebel.xml-el megfejelt alkalmazásunkat, és máris élvezhetjük a produktumot.
Bár a JRebel fizetős eszköz, a honlapján elhelyezett kalkulátor segítségével gyorsan kiszámolható megéri-e az árát, viszont a pénzben nem mérhető könnyebbséget még a kalkulátor sem tudja számításba venni.

2010. március 2., kedd

EJB 3.1 Lite

A Java EE 6 megjelenése számos újdonságot hozott, már itt is volt szó róla, ezek közül egy érdekesség az un. web-profilok létrehozása. Mint EE fejlesztő gyakran megfordult már a fejemben, hogy vajon tényleg mindig szükség van a teljes enterprise környezetre, hogy vajon nem-e ágyúval verébre amit csinálok, hiszen a szolgáltatások tárházának töredék részét használom egy-egy projektben. Az érem másik oldalán viszont mindig ott van az egyszerű fejlesztés, az átlátható logika (amit egy kicsit az architektúra is kikényszerít), valamint az élesen elkülönített szerep-körök. Ezt a problémát gondolták orvosolni az EE 6 fejlesztői a web-profilok segítségével, mégpedig úgy, hogy az alkalmazásunkhoz igazíthatjuk a felhasznált és szállított rétegeket. A szintén újdonságként megjelenő egyszerűsített deployment technológiának köszönhetően, melynek lényege, hogy az ejb-réteg behelyezhető a web-rétegbe megspórolva a két különálló projektre, némi megkötéssel egy web-konténer, is képes futtatni enterprise alkalmazásunkat. Az EJB Lite technológia nem más, mint egy előre csomagolt web-profil, az alábbi kritériumokkal:
  • Stateless, Stateful, és Singleton session beaneket használhatunk
  • csak lokális EJB hívásokat végezhetünk
  • nem támogatja az aszinkron hívásokat
  • támogatja a deklaratív és programozott tranzakciókat
A csomag egy annyira "lebutított" profil, hogy használatához web-konténer sem kell, ennek köszönhetően bármely hagyományos alkalmazásunkban használható, mindössze a javax.ebj és glassfish-embedded-static-shell könyvtár-gyüjteményt (jar) kell importálnunk.
import javax.ejb.Stateless;

@Stateless
public class TestBean {
public String test() {
   return "ok";
}
}
import java.util.Properties;
import javax.ejb.embeddable.EJBContainer;
import javax.naming.Context;
import javax.naming.NamingException;

public class Main {
public static void main(String[] args) throws NamingException {
   Properties properties = new Properties();
   properties.put("org.glassfish.ejb.embedded.glassfish.installation.root", "");
   EJBContainer container = EJBContainer.createEJBContainer(properties);
   Context namingContext = container.getContext();

   TestBean testBean = (TestBean) namingContext.lookup("java:global/classes/TestBean");

   System.out.println(testBean.test());
}
}
Az alkalmazást futtatva az alábbi kimenetet kapjuk:
Mar 3, 2010 8:47:28 AM org.glassfish.ejb.embedded.EJBContainerProviderImpl getValidFile
SEVERE: ejb.embedded.location_not_exists
Mar 3, 2010 8:47:31 AM com.sun.enterprise.v3.server.AppServerStartup run
INFO: GlassFish v3 (74.2) startup time : Embedded(2591ms) startup services(492ms) total(3083ms)
Mar 3, 2010 8:47:31 AM org.glassfish.admin.mbeanserver.JMXStartupService$JMXConnectorsStarterThread run
INFO: JMXStartupService: JMXConnector system is disabled, skipping.
Mar 3, 2010 8:47:31 AM com.sun.enterprise.transaction.JavaEETransactionManagerSimplified initDelegates
INFO: Using com.sun.enterprise.transaction.jts.JavaEETransactionManagerJTSDelegate as the delegate
Mar 3, 2010 8:47:31 AM AppServerStartup run
INFO: [Thread[GlassFish Kernel Main Thread,5,main]] started
Mar 3, 2010 8:47:32 AM com.sun.appserv.connectors.internal.api.ConnectorsUtil extractRar
INFO: could not find RAR [ __ds_jdbc_ra.rar ] in the archive, skipping .rar extraction
Mar 3, 2010 8:47:32 AM com.sun.appserv.connectors.internal.api.ConnectorsUtil extractRar
INFO: could not find RAR [ __cp_jdbc_ra.rar ] in the archive, skipping .rar extraction
Mar 3, 2010 8:47:32 AM com.sun.appserv.connectors.internal.api.ConnectorsUtil extractRar
INFO: could not find RAR [ __xa_jdbc_ra.rar ] in the archive, skipping .rar extraction
Mar 3, 2010 8:47:32 AM com.sun.appserv.connectors.internal.api.ConnectorsUtil extractRar
INFO: could not find RAR [ __dm_jdbc_ra.rar ] in the archive, skipping .rar extraction
Mar 3, 2010 8:47:32 AM com.sun.enterprise.security.SecurityLifecycle 
INFO: security.secmgroff
Mar 3, 2010 8:47:32 AM com.sun.enterprise.security.ssl.SSLUtils checkCertificateDates
SEVERE: java_security.expired_certificate
Mar 3, 2010 8:47:32 AM com.sun.enterprise.security.SecurityLifecycle onInitialization
INFO: Security startup service called
Mar 3, 2010 8:47:32 AM com.sun.enterprise.security.PolicyLoader loadPolicy
INFO: policy.loading
Mar 3, 2010 8:47:33 AM com.sun.enterprise.security.auth.realm.Realm doInstantiate
INFO: Realm admin-realm of classtype com.sun.enterprise.security.auth.realm.file.FileRealm successfully created.
Mar 3, 2010 8:47:33 AM com.sun.enterprise.security.auth.realm.Realm doInstantiate
INFO: Realm file of classtype com.sun.enterprise.security.auth.realm.file.FileRealm successfully created.
Mar 3, 2010 8:47:33 AM com.sun.enterprise.security.auth.realm.Realm doInstantiate
INFO: Realm certificate of classtype com.sun.enterprise.security.auth.realm.certificate.CertificateRealm successfully created.
Mar 3, 2010 8:47:33 AM com.sun.enterprise.security.SecurityLifecycle onInitialization
INFO: Security service(s) started successfully....
Mar 3, 2010 8:47:33 AM com.sun.ejb.containers.BaseContainer initializeHome
INFO: Portable JNDI names for EJB TestBean : [java:global/classes/TestBean, java:global/classes/TestBean!ejblite.TestBean]
ok
Bár nem hiszem, hogy a technológia leggyakoribb alkalmazási területét sikerült megragadnom, mégis ezen egyszerű példából kiindulva csak a fejlesztő kreativitásán múlik, hogy milyen környezetbe ágyazva alkalmazza az EJB réteget.
Aki részletesebben szeretne tájékozódni a témában, megteheti Roberto Chinnici blogjában, vagy a TheServerSide portálon. Az egyszerűsített deploymentről ebben a cikkben található részletesebb leírás.

2010. február 6., szombat

@Aszinkron EJB >=3.1 hívás

Az EJB 3.1 megjelenése számos újítást és egyszerűsítést hozott a rendszerbe, a fejlesztők, mint ahogy a 2.x-ről 3.0-ra váltáskor is, igyekeztek még kényelmesebbé tenni az enterprise alkalmazások készítését. Mivel mi Glassfish alkalmazás-szervert használunk, és a kiadást követő napokban kezdtünk bele egy új projektbe, úgy határoztunk, hogy a friss és ropogós EJB-t használjuk (eddig nem bántuk meg) az implementáláshoz. Az új EJB-t tanulmányozva szembeötlött egy lehetőség, ami mellett nem tudtam szó nélkül elmenni. Úgy néz ki, hogy nem csak nekem jutott eszembe, hogy az alkalmazást, amit fejlesztek párhuzamosítsam, hanem az EJB fejlesztőinek is, ezért megalkották az aszinkron EJB hívás fogalmát. A dolog igen egyszerű, a hagyományokhoz híven @Annotációval kell megjelölni azokat a SessionBean metódusokat, amiket nem a hagyományos módon szeretnénk kiajánlani.
package sample;

import javax.ejb.Asynchronous;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;

@LocalBean
@Stateless
public class TestBean {

    @Asynchronous
    public void asyncMethod() {
     //some functionality
    }
}
A metódus meghívása a megszokott módon történik, viszont az abban elhelyezett utasítások egy új szálon fognak futni. Ez eddig egyszerű, de mi történik, ha szeretnénk, hogy a hívás valamilyen visszatérési értéket is prezentáljon? Ezt úgy tudjuk megtenni, hogy visszatérési értékként a java.util.concurrent.Future<V> interfészt jelöljük meg, és annak valamely implementációját adjuk vissza.
package sample;

import java.util.concurrent.Future;
import javax.ejb.AsyncResult;
import javax.ejb.Asynchronous;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;

@LocalBean
@Stateless
public class TestBean {

    @Asynchronous
    public Future<String> asyncMethod() {
        try { Thread.sleep(1000);
        } catch (Exception ex) {}
        return new AsyncResult<String>("finish");
    }
}
A dolog érdekessége a hívó oldalon történik, hiszen valahogy kezelni kell tudni, hogy az aszinkron hívás éppen milyen állapotban van.
Future<String> asyncResponse = testBean.asyncMethod();
//some functionality
while (!asyncResponse.isDone()) {
    try { Thread.sleep(100);
    } catch (Exception ex) {}
}

if (!asyncResponse.isCancelled()) {
    try {
        String response = asyncResponse.get();
    } catch (InterruptedException e) {
    } catch (ExecutionException e) {}
}
Láthatjuk mi módon tudjuk "összeszinkronizálni" a hívásokat a szülő szálban. Természetesen meg is lehet szakítani a kérést, a Future cancel metódusával, illetve a rendelkezésünkre állnak a hagyományos Thread notify, notifyAll, és wait metódusai.