neděle 31. prosince 2006

Java 6 – co vězí za zrychlením (mikro benchmark)

V článku Java 6 – co vězí za zrychlením jsme se prokousali teorií a nyní je na čase se podívat na reálný dopad synchronizačních optimalizací.

Abstrakt

Celý benchmark byl koncipován na měření vlivu nové optimalizační techniky JVM zvané lock elision čili vypuštění zbytečných synchronizačních zámků. Pro srovnání výkonu byl zároveň proveden stejný benchmark proti Jave 5 a to včetně zapnutí techniky biased lockingu, která měla snížit synchronizační režii.

Benchmark

Následuje verze benchamrku se zapnutou synchornizací. Pokud se testovalo bez synchronizace, byla použita stejná verze s tím rozdílem, že synchronizační blok byl zakomentován.

public class TestSynOptimizations {
    public static void main(final String args[]) {
        for (int i = 0; i < 10; i++) {
            long startTime = System.nanoTime();
            for (int j = 0; j < 30000; j++) {
                compute();
            }
            long delta = System.nanoTime() - startTime;
            System.out.println("Total time [ns]:" + delta);
        }
    }

    public static void compute() {
        Object lock = new Object();
        int sum = 0;
        for (int j = 0; j < 5; j++) {
            synchronized (lock) {
                sum = sum + 1;
            }
        }
    }
}

Kód metody compute je koncipován tak, aby umožnil HotSpotu provedení odstranění zámku. Tím pádem by se měl setřít rozdíl mezi synchronizovanou nesynchornizovanou verzí.

Měřící prostředí

  • HW notebook Dell D610
  • CPU Intel Pentium M 760 (2.00Ghz w/ 533Mhz FSB)
  • RAM 2048 MB (dual channel)
  • OS Microsoft Windows XP Professional SP 2

Měření proběhlo na Jave 1.6.0-b105 a 1.5.0_10-b03 v HotSpot módu server a vyhrazená velikost heapu byla nastavena v rozmezí 256MB - 128MB (eliminace vlivu GC). Pro každou verzi Javy byl benchmark zkompilován příslušnou verzí kompilátoru. Benchmark byl spouštěn v následujících verzích a konfiguracích JVM (sync a nosync rozlišuje jestli byla v kódu benchmarku použita synchronizace).

  • Java 6.0, sync
  • Java 6.0, sync,-XX:+DoEscapeAnalysis
  • Java 1.5.0_10, sync, -XX:+UseBiasedLocking
  • Java 1.5.0_10, sync
  • Java 6.0, nosync
  • Java 1.5.0_10,nosync

Poznámka ke konfiguraci JVM

  • -XX:+DoEscapeAnalysis zapíná (defaultně vypnuto) od Javy 6 analýzu nutnou k provedení odstranění zámku.
  • -XX:+UseBiasedLocking zapína biased locking (dostupné i v Jave 5 od aktualizace 06)

Naměřené hodnoty

Následující tabulka obsahuje výsledky měření po jednotlivých měřících iteracích. Iterace je průchod tělem nejvyššího cyklu v metodě main. Poslední dva řádky tabulky obsahuje průměrné hodnoty, s tím rozdílem, že poslední řádek nemá do průměru započtené první dvě iterace, které jsou skreslující díky práci HotSpotu.

Výsledky měření
Plná velikost obrázku (cca. 37KB).

Naměřené výsledky v grafu

Výsledky měření
Plná velikost obrázku (cca. 22KB).

První graf je výrazně zkreslený optimalizací HotSpotu, která probíhala během prvních dvouch iteracích, proto má mnohem přesnější vypovídající hodontu graf číslo dva. Z výsledků jasně vyplývá, že odstranění zbytečných zámků v Jave 6 funguje. Rychlost synchronizované verze se zapnutou escape analýzou je totiž stejná s rychlostí nesynchronizované verze. Zajímavý je vliv biased lockingu v Jave 5, který rychlost posouvá na stejnou úroveň synchronizované verze Javy 6.0. Z tohoto výsledku se dá vyvodit, že v Jave 6.0 je biased locking zapnutý defaultně.

Bohužel nemám po ruce žádné konkrétní čísla dopadu těchto synchronizačních optimalizací na reálné aplikace. Schválně říkám reálné, protože vypovídající hodnota tohoto mikro benchmarku slouží spíše k ověření teoretických předpokladů. Nicméně i podle těchto výsledků bych si troufl říci, že pro většinu aplikací přináší Java 6 v podstatě zdarma solidní výkonnostní nárůst.

Tabulku s naměřenými hodnotami a grafy můžete také najít v PDF dokumentu, který je přece jenom vhodnějším médiem pro prezentaci naměřených dat.

Aktualizace: v článku Lži, větší lži a mikrobenchmarky:) uvádí Jarda Kameník na pravou míru jak je to s měřením nesynchronizované verze.

Java 6 – co vězí za zrychlením

O tom, že je Java 6, co do výkonu, opět dále, nemůže být pochyb, to dokazují například testy Davida Dagastine. Mě osobně trochu výkonnost Javy 6 nedala spát, takže jsem se jal hledat informace, co za tím vším je. Posledních několik dní jsem tak strávil, krom studia potřebné teorie, měřením výkonu nových synchronizačních technik JVM.

Type Checking Verifier

Začnu tím, co jsem nijak netestoval, nicméně vliv na výkon to má zcela určitě. Každá JVM má v sobě verifikátor byte kódu, který dělá dvě základní věci, typovou kontrolu a odvození typu. Proč odvození typu? Byte kód samozřejmě obsahuje typové informace (parametry metod, návratové typy), ale neobsahuje je pro lokální proměnné metod. Verifikátor je tak zodpovědný za jejich odvození.

Právě odvození typů je úkol, který je pomalý, paměťově náročný a celkem komplikovaný. Od Javy 6.0 byl verifikátor upraven tak, že odvození typů se přesunulo z odpovědnosti JVM na kompilátor. Děje se tedy ve chvíli kompilace do byte kódu. Díky tomuto rozdělení došlo k dvojnásobnému zrychlení práce verifikátoru.

Nový verifikátor, Type Checking Verifier, může samozřejmě běžet ve původním módu s odvozením typů, pokud byte kód neobsahuje potřebné typové informace a nebo tyto informace nejsou korektní. Pokud tedy testujete svůj kód pod Javou 6, nezapomeňte jej také zkompilovat kompilátorem z Javy 6, jinak se vliv nového verifikátoru neprojeví.

Zdroje

Optimalizace v oblasti synchronizace - teorie

Velký kus práce byl ovšem udělán na HotSpotu resp. JITu (kompilátor do nativního kódu). To se projevuje především v oblasti synchronizovaného přístupu. Synchonizovaným přístupem je myšleno použití klíčového slova Javy synchronized. Ve spojení s objektem, přes který se synchronizuje, dochází k takzvanému vzájemnému vyloučení (mutual exclusion). Nad kritickou sekcí, ohraničenou blokem synchronized a daným objektem, operuje vždy pouze a jedině jedno vlákno.

Objekt (Monitor), nad kterým se synchronizuje, obsahuje takzvaný zámek. Z hlediska JVM spadají zámky do dvou kategorií contended a uncontended (přeložit by se to dalo jako soupeřivé a nesoupeřivé, ale zůstanu u anglické terminologie).

contended
Jedná se o zámky, u kterých velice často dochází k souběhu vláken a k jejich "soupeření". Těmto zámkům lze také říkat horké. V aplikacích jsou to typicky zámky nad částmi kódu, které jsou hojně souběžně přistupované. Typicky například pool databázových připojení.
uncontended
Jedná se o zámky, na kterých nedochází k souběhu takřka vůbec. Tedy nedochází k tomu, že při pokusu o získání zámku, drží zámek jiné vlákno.

Valná většina zámků je podle dostupných informací uncontended. JVM má dvě rozdílné metody, pro získání contended a uncontended zámků. Pro získání contended zámku používá JVM takzvaný "fast path" způsob a pro získání uncontended zámku takzvaný "slow path" způsob. Rozdíl mezi "fast path" a "slow path" je v tom, že při "fast path" stačí použít dvě instrukce pro získání nebo uvolnění zámku (jednoduchý příznak owner, který nabývá hodnot null nebo not null). Oproti tomu "slow path" je mnohem složitější, protože se musí postarat o obsluhu (uspání/vzbuzení) vláken.

Jak jsem si řekli, většina zámků je uncontended, proto je důležitá rychlost "fast path" způsobu. "Fast path" sice používá jednu nebo dvě instrukce, ovšem instrukce atomické vzhledem k paměti, díky tomu dochází k CAS zpoždění (CAS latency). Proto jakékoliv zrychlení v oblasti uncontended zámků se počítá.

Java 6 má tři nové způsoby jak urychlit práci s uncontended zámky a tím pádem zrychlit běh většiny programů. Protože jak si ukážeme dále, uncontended zámků máte v programu mnoho a ani o tom možná nevíte.

Odstranění zámků (Lock elision)

V jednoduchosti je síla, mnoho uncontended zámků totiž ani nemusí být nutných, protože se synchronizovaný objekt nedostane mimo viditelnost daného vlákna (thread stack). Řekněme, že máme následující kód.

     
public String doSomething() {
    Object lock = new Object();
    synchronized(lock){
      //do something
      return result;           
    }
}    
    
    

Blok je sice synchronizovaný, ale synchronizace se děje přes objekt, který má každé vlákno vlastní. Navíc jsou všechny proměnné lokální a proto je synchronizace zbytečná, vlákna se nemohou vzájemně ovlivnit. Pokud se ptáte, proč by někdo takovouto zbytečnou synchronizaci používal, tak odpovím reálným kódem.

     
public String doSomething() {
    StringBuffer s = new StringBuffer();
    s.append("foo");
    s.append("hoo");
    s.append("bar");
    return s.toString();
}    
    
    

Tento kód je z hlediska synchronizace ekvivalentem předchozího kódu. Hodně core Java tříd je thread safe, tedy obsahují nutnou synchronizaci, aby bylo jejich použití ve vícevláknovém prostředí bezpečné. Právě synchronizace StringBufferu je v tomto případě zbytečnou režií, která nemá v tomto konkrétním případě smysl. Opět zopakuji, objekt s bude mít každé vlákno vlastní, čili vzájemně se nemohou ovlivnit.

JVM od verze 6 umí detekovat, že reference na objekt s nikdy neopustí metodu doSomething. Tím pádem může JVM za běhu zbytečnou synchronizaci odstranit. K tomu, aby JVM rozhodla jestli může synchronizaci odstranit, dělá analýzu útěku. Tenhle termín jsem si nevymyslel, protože jsem asi tři dny v kuse sledoval Prison break, ale protože v originále se to nazývá Escape analysis.

Analýza útěku umožňuje JVM nejenom odstranit zbytečnou synchronizaci, ale i úžasnou optimalizaci v podobě stack alokace objektů. Alokace objektů na heapu je totiž mnohem pomalejší než alokace přímo na stacku. Java všechny objekty alokuje na heapu, pouze primitivní datové typy a reference na objekty leží na stacku.

Díky analýze útěku může JVM optimalizovat vytváření objektů, které neopustí lokální rozsah, že je bude alokovat na stacku. Záměrně používám slovo může, i když Java umí analýzu útěku a umí jí využít pro eliminaci zbytečných synchronizací, stack alokací bohužel zatím neumí.

Lock coarsening

Lock coarsening by se dal volně přeložit jako zhrubnutí zámku. Jde o snížení režie na zamykání a odemykání v případě, že je možné dané části uzavřít do jednoho zámku. Pro ilustraci si pomůžeme lehkou modifikací předchozího příkladu.


public void doSomething(StringBuffer s) {
    s.append("foo");
    s.append("hoo");
    s.append("bar");
}    
    

Každé volání metody append způsobí zamčení a odemčení zámku na objektu s. JVM může kód optimalizovat tak, že nebude zamykat a odemykat pro každé volání metody append, ale uvnitř metody doSomething udělá jeden větší zámek přes všechny tři volání. Tím pádem se sníží náklady na synchronizační režii.

Lock coarsening může mít jeden vedlejší efekt. Sice se sníží synchronizační režie, ale může se zvýšit sekvenční část programu a tím pádem se zmenší část, kterou je možné vykonávat paralelně, pokud se z uncontended zámku stane contended zámek. To může mít neblahý vliv na paralelismus viz Amdahluv zákon (sekce paralelizace).

Lock biasing

Lock biasing je technika, která opět využívá faktu, že zámek je většinou držen pouze jedním vláknem. Lock biasing je v podstatě rezervování zámku. Když vlákno poprvé obdrží zámek, dojde k nastavení speciálního flagu. Při další žádosti o zámek, se nejdříve zkontroluje hodnota flagu společně s identifikátorem daného vláknu a pokud je thread vlastníkem, flag se nastaví. Tím je zámek aktivní.

Tomuto způsobu zamykání se také říká "ultra fast path", protože eliminuje CAS latency "fast path" způsobu. Lock biasing je v Jave 6 stndardně zapnutý, v Jave 5 (od verze 5.0_06) lze toto chování aktivovat parametrem -XX:+UseBiasedLocking. Vliv biased locku se projeví především na víceprocesorových systémech, kde by se jinak musela použít lock verze CAS instrukce, která by způsobila serializaci zpracování.

Reálný dopad na výkon

Pro otestování těchto vlastností jsem si napsal mikro benchmark, kterým jsem ověřil dopad odstranění zámků (Lock elision) na výkon. No nebudu vás napínat, dopadlo to přesně podle teorie. Díky runtime odstranění zámků byl výsledek stejný jako v případě, že tam zámky (synchornizace) nebyly. Pokud se o výsledky zajímáte, můžete pokračovat na detailní popis testu a naměřené hodnoty mikro benchmarku.

Zdroje

neděle 24. prosince 2006

Vánoční dárky, které vám definitivně změnily život

Každé vánoce přemýšlím o tom jaký dárek, který jsem kdy dostal, změnil můj život. Musím definitivně říci, že můj život změnil dárek, který přistál pod vánočním stromkem léta páně 1991. Byl to úžasný a nepředstavitelný pocit rozbalit v jedenácti letech první osobní mikropočítač Didaktik M (Československý klon ZX Spectrum).

Uvnitř tohoto nebušeného počítače tikal v taktu 4MHz procesor Z80A a k dispozici byla paměť s kapacitou závratných 64KB. Jako monitor tenkrát sloužila televize a místo disku se používaly obyčejné magnetofonové kazety. Na nich člověk většinou míval rodinné stříbro v podobě skvostných her, které šli ovládat přes joystick. Ano, tenkrát bylo celkem normální hry ovládat většinu her joystickem.

K hrám, na které nikdy nezapomenu, patřila úchvatná textovka Franty Fuky jménem Podraz III. Kde jsem se vtělesnil do role počítačového hackera Tima Colemana. Dodnes netuším jestli číslovka tři byla analogií k Lucasovým Hvězdným válkám, které taky startovaly dílem tři nebo dílem Fukovi výstředností. Každopádně něco jako Podraz I a II nikdy nevznikly a nebo jsem o nich nikdy neslyšel.

Krom jiných zvláštností měl Didaktik tepelně neizolované trafíčko, na kterém jsme si s kamarády mohli během hraní přihřívat čaj. Také joysticky nebyly moc kvalitní, takže jsem jich docela dost odrovnal. Jako největší zabiják joysticku se ukázalo hraní desetiboje (Dacathlon) a to především disciplína běh na 1500 metrů, kdy bylo potřeba vyvinout největší frekvenci horizontálním kmitáním "kniplu".

Při hraní her my v hlavě šťouralo, jak jsou udělané. Tím jsem se dostal k jazyku Basic (akronym pro Beginner's All-purpose Symbolic Instruction Code), který byl přímo vestavěný v Didaktiku. No a tak jsem se vlastně poprvé dostal k řemeslu jménem programování, se kterým se protloukám již pěkných pár let.

Jsem skoro přesvědčen, že kdybych oněch vánoc roku 91 dostal něco jiného, například autodráhu, nebyl bych dnes tím čím jsem, tedy softwárovým vývojářem. Každé vánoce si na ten Didaktik vzpomenu a děkuji za tu prozíravost rodičům. A kdybych měl jmenovat ještě jednu věc, která mě ovlivnila, tak to byla stavebnice Lego. Kdeže ty loňské sněhy jsou, od jisté doby totiž dostávám obligátní fusekle a spoďáry.

neděle 17. prosince 2006

Eclipse a drobné maličkosti: Ant builder

Musím nepokrytě přiznat, že kdybych znal dříve níže popisovanou vlastnost Eclipse, ušetřil bych si spoustu času a nervů. Problém, kterému jste vystaveni, ať už používáte jakékoliv IDE, se jmenuje dostatečně flexibilní buildování projektu. Vývojová struktura projektu bývá totiž diametrálně odlišná od výsledku buildu např. WARu. Můžeme sice použít Ant, ale jeho manuální spouštění není efektivní. V tomto tipu si ukážeme integraci volitelného Ant skrtiptu jako dalšího projektového builderu, který se bude spouštět automaticky při uložení souboru.

Vezměme si malou modelovou web aplikaci, kde náš projekt obsahuje adresář pro zdrojové soubory, JSP a konfigurační soubory.

Vzorový projekt

Projekt s touto strukturou bychom chtěli buildovat například do WARu. Za tímto účelem si připravíme jednoduchý buildovací Ant skript, který umístíme do kořenového adresáře našeho projektu. Buildovacím adresářem bude přímo deploy adresář naší aplikace v cílovém serveru.

<?xml version="1.0"?>
<project name="Ant builder" basedir="." default="build">
  <property name="build.path" value="c:\tools\tomcat\webapps\test.war" />

  <target name="build" >
    <!-- copy classes -->
    <copy todir="${build.path}/WEB-INF/classes" preservelastmodified="true">
      <fileset dir="bin">
        <include name="**/*.*"/>
      </fileset>
    </copy>

    <!-- copy jsps -->
    <copy todir="${build.path}" preservelastmodified="true">
      <fileset dir="jsp">
        <include name="**/*.*"/>
      </fileset>
    </copy>

    <!-- copy configs -->
    <copy todir="${build.path}/WEB-INF" preservelastmodified="true">
      <fileset dir="conf">
        <include name="**/*.*"/>
      </fileset>
    </copy>
  </target>

  <!-- clean build path -->
  <target name="clean" >
    <delete dir="${build.path}" failonerror="no"/>
  </target>
</project>

Nyní tento buildovací soubor nastavíme jako nový builder našeho projektu. Project >> Properties >> Builders >> New

Vytvoření nového builderu

V dalším dialogu Choose configuration type zvolíme Ant build.

Tím se dostaneme na okno vlastností našeho nového builderu. Na záložce Main nastavíme Buildfile na námi zvolený ant skipt.

Nastavení buildovacího skriptu

Další záložkou, kterou nás zajíma, představuje Targets. Zde si můžeme zvolit, jaké se budou spuštět Ant targets z našeho buildovacího skriptu v závislosti na Eclipse událostech (After Clean, Manual Build, Auto build a During Clean). Jako výchozí target je zvolen default označený v zvoleném Ant skriptu.

Ve výše uvedeném skriptu je to target build, což plně vyhovuje. My ovšem ještě potřebujeme nastavit target (Set targets) pro událost Auto Build a During Clean. Především události Auto Build je velice důležité přiřadit target, protože tato událost nastane ve chvíli uložení jakéhokoliv souboru v projektu.

Nastavení targets na jednotlivé události

To je všechno co potřebujeme. Nyní se s každým uložením souboru v projektu automaticky spustí námi definovaný target pod událostí Auto Build.

Optimalizační tipy

Jak jste si jistě všimli, náš builder má mnohem více záložek. Z pohledu optimalizace jde celkem zajímavě modifikovat chování na záložkách JRE a Build options.

  • Pokud máte vícero projektu, pro které máte nadefinovaný Ant builder a tyto projekty jsou na sobě závislé, pak je potřeba na záložce JRE nastavit Separate JRE místo Run in the same JRE as the workspace. Pokud jsem tak neměl nastaveno, dělal neplechu paralelní clean projektů.
  • Pokud vás otravuje log výpis do konzole při každém buildu, odškrtněte Allocate Console na záložce Build options
  • Záložka Build options rovněž nabízí možnost nechat pouštět build na pozadí. Pro aktivování zaškrtněte Launch in background
  • Jestli Vám nevyhovuje, že se Ant spouští při uložení jakéhokoliv souboru v projektu pak opět na záložce Build options zaškrtněte Specify working set of relevant resources a tlačítkem Specify resources si vyberte adresáře/soubory při jejichž uložení se zavolá událost Auto Build, spouštějící definovaný Ant target.

Poznámka: pokud seAnt nevolá, zkontrolujte zaškrtnutí volby v menu Project >> Build Automatically

čtvrtek 14. prosince 2006

Podcast volume#3 konečně je venku

Vánoce, vánoce přicházejí a s nimi v pořadí třetí Java podcast tentokrát na téma Java 6 SE, Vývoj webových aplikací. Vzkaz tohoto podcastu je nad slunce jasnější: Místo vánočních koled a Mrazíka doma pouštějte Javu 6 a JSF ;-).

středa 13. prosince 2006

Seam 1.1 - nejenom EJB 3.0

Gavin King se asi chytil za nos, protože nová verze Seamunezávisí na EJB 3.0. V předchozích verzích šel sice Seam spustit například v Tomcatu, ale bylo nutné rozběhnout JBoss Micro Container. To s verzí 1.1 již není potřeba.

Mezi další novinky patří možnost, vygenerovat kostru Seam aplikace z existující databáze. Další novinkou je replikace stavu statefull komponent, funguje to i na POJO objekty. Právě replikace je velice zajímavá, protože se nereplikují celé objekty (grafy objektů), ale pouze vzniklé rozdíly. Pokud si chcete replikace POJO objektů vyzkoušet, začněte s konfigurací clusteru podle návodu JBoss cluster krok za krokem.

Další novinky najdete v článku What's New in Seam 1.1, kde je i spousta dalších zajímavých linků. Seam se mi začíná líbit čím dál tím víc. Pokud máte se Seamem reálnou zkušenost podělte se o ní v diskuzi. Určitě to zajímá velké množství čtenářů.

pondělí 11. prosince 2006

Java SE 6 je venku

Java Standard Edition 6 byla oficiálně uvolněna. Sun v rámci této verze nabízí bezplatnou nelimitovanou 60 denní podporu určenou k migraci skrze Sun Developer Expert Assistance. Krom veřejně proklamovaných vlastností nabízí i tato verze výkonostní posun v rozmezí 5% až 24% (TheServerSide) oproti předchozí verzi.

Následující seznam zahrnuje nové či inovované oblasti v rámci Java 6 SE.

  • Security Features and Enhancements
  • Integrated Web Services
  • Scripting Language Support
  • Enhanced Management and Serviceability
  • Increased Developer Productivity (JDBC 4.0, Java Platform Debug Architecture (JPDA) & JVM Tool Interface)
  • Improved User Experience (Swing performance, Vista support)

Mnohem detailnější popis najdete ve následujících článcích.

Přiblížení vizuálního vzhledu mezi Swing a OS ilustruje následující obrázek dialogu pro výběr souboru či adresáře (obrázek z diskuse k článku Around the Web: Java 6.0 Released).

Swingová komponenta

OS komponenta

Mě udělalo největší radost fixnutí IO chyby, která způsobovala, že nebylo možmé na Windows používat delší cestu než 255 znaků a vylepšení debug rozhraní JVM. Naopak jsem opět zatřel slzu, protože než bude Javu 6.0 adoptována, uteče mnoho vody v rodné Otavě.

Související články

Dagblog, související články :

neděle 10. prosince 2006

Web frameworky v Jave

Tento článek by se také mohl jmenovat po stopách MVC aneb nutné minimum, pokud nechcete udělat při vývoji webových aplikací v Jave chybu. Začneme hodně ze široka, Java není .NET, proto máte opravdu obrovský výběr frameworku pro tvorbu webových aplikací. Všechny frameworky, která znám a budu o nich mluvit, spojuje jedna věc. Tou věcí je jejich architektura, všechny je spojuje návrhový vzor MVC. Pokud jste seznámeni s konceptem MVC, můžete následující stať přeskočit.

Od špagetového kódu k MVC

Když byla poprvé představena technologie Java Server Pages (JSP, (1)) byla hodně podobná PHP či ASP a umožňovala používat Javu v prostředí webu pro dynamické vytváření stránek. Základem je takzvaný skriptlet (uvozený mezi <% %>), uvnitř kterého bylo možné používat přímo Javu ke generování HTML. To by nebylo na škodu, kdyby nedocházelo k tomu, že vývojáři začali všechen kód aplikace dávat přímo do JSP stránek. Výsledek pak vypadal pak přibližně následovně

 
<% 
 Connection con = null;
 String sql = "select name from users";
 PreparedStatement ps =  con.prepareStatement(sql);
 ResultSet rs = ps.executeQuery();
 out.println("<table>");
 while(rs.next()) {   
     out.println("<tr>"); 
       out.println("<td>");
         out.println(rs.getString(1));
       out.println("</td>");
     out.println("</tr>");
 }
 out.println("<table>");
%>  

 

Problém s uvedeným kódem byl v několika rovinách. Míchala se prezentační a aplikační logika. Kód který měl nějakou business funkci byl promíchán s kódem, který vytvářel jeho zobrazení. Takto vytvořený kód se nedal, a nebo velice obtížně, znovupoužít v jiné stránce a nebylo jej možné testovat. Výsledkem byl kód, který byl promíchaný jako klubko špaget.

Další evoluční krok bylo zavedení nového rozšíření a to takzvaných custom tagů (JSP 1.1), které bylo možno sdružovat do tag library (knihoven). Tag má dvě části, jednak vlastni zápis podobný HTML elementům a jednak Java třídu (2), která mu odpovídá. Díky tomu umožňuje zapouzdřit Java kód do znovupoužitelné podoby, jedno zda pro tvorbu dynamického obsahu nebo pro volání aplikační logiky.

V návaznosti na JSP 1.1 se někdy kolem roku 2000 začaly objevovat první web frameworky, které byly postavené na návrhovém vzoru Model View Controller (zkráceně MVC). Asi nejznámějším frameworkem, který tuto vlnu představoval a jenž se stal de facto standardem, byl Struts.

Návrhový vzor MVC slouží k oddělení aplikační a prezentační logiky. Za tímto účelem není použit jenom v rámci webových aplikací, ale například i v desktopových aplikacích (3).

Model View Controller

MVC má tři části, Model, View a Controller. Každá část má svojí specifickou a přesně určenou roli.

Controller
hraje roli prostředníka mezi Model a View.
Model
představuje entity (data), nad kterými pracuje View
View
transformuje Model do prezentační formy

Díky rozdělení rolí, má každá část svojí odpovědnost a nedochází k jejich vzájemnému promíchání. Jednotlivé části mezi sebou nemají těsnou vazbu, Model poskytovaný nezávislou aplikační logikou je možné využívat beze změny v N View. Pro objasnění, na následujícím obrázku je znázorněná jedna z možných realizací MVC v prostředí JSP a Servletů.

Realizace MVC v prostředí Servletů a JSP

Příchozí požadavek je delegován na Servlet, který představuje Contoller. Servlet (Controller) zavolá aplikační logiku, ze které získá Model (4) a ty zaregistruje do scope viditelného z JSP např. request attribute. Následně provede přesměrování na JSP, které si ze scope vyzvedne data (Model) a ten zobrazí.

Současná architektura web frameworků

Následující text není pro volbu webového frameworku tolik důležitý, slouží pouze jako nahlédnutí pod kapotu. Pokud Vás nezajímají nuance mezi enterprise návrhovými vzory, můžete tuto kapitolu s klidem přeskočit.

Existuje několik způsobů jak MVC v prostředí webu implementovat. V dnešní době jsou dva hlavní přístupy (5), které odpovídají následujícím dvěma návrhovým vzorům

Klasickými představiteli implementace Front Controller jsou frameworky Struts 1,2, Spring MVC. Naopak na vzoru Dispatcher view jsou postaveny všechny implementace standardu JSF a jeho nadstaveb jako Shale či Seam.

Rozdíl mezi webovým frameworkem postaveným na Front Controller a nebo Dispatcher view je z pohledu aplikace nad ním postavené v okamžiku volání aplikační logiky. V případě Front Controlleru se aplikační logika volá před předáním zpracování do View, kdežto v případě Dispatcher view se volání aplikační logiky děje zprostředkovaně uvnitř View viz návrhový vzor View Helper.

Rozdíl je vidět na následujícím sekvenčním diagramu. Front Controller jsem nahradil za specializovanější Service To Worker.

Sekvenční diagram Dispatcher view a Service to Worker
Plná velikost (cca. 25KB)

Určitě stojí minimálně za zmíňku, že Service To Worker i Dispatcher view jsou kompozicí vzorů Front Controlleru a View helper. Rozdíl je pouze ve větší čí menší míře odpověnosti na Controlleru resp. View.

Jak rozlišit webové frameworky

Pokud pomineme vnitřní architekturu jednotlivých frameworků, od které je aplikace odstíněna, hraje nejdůležitější roli úroveň abstrakce nad HTTP protokolem. Čím větší úroveň abstrakce, tím méně se musí jednotlivé vrstvy zajímat o samotný HTTP protokol. Pěkně to ilustruje následující obrázek (Kito D. Mann, Java Server Faces in Action str. 20).

Abtrakce poskytovaná webovými frameworky
Plná velikost (cca. 81KB)

Jak je vidět, Java Server Faces nabízejí vyšší míru abstrakce oproti Struts (Spring MVC) a to v oblasti tvorby UI komponent a událostního modelu. Na stejné úrovni jako JSF je například framework Tapestry. O těchto frameworcích můžeme mluvit jako o komponentově orientovaných. Naproti tomu o Struts like frameworcích mluvíme jako o požadavkem řízených. Pokud vybíráme webový framework, měli bychom si zodpovědět několik zásadních otázek.

Komponentově orientovaný či požadavkem orientovaný framework?

Budoucnost patří rozhodně komponentově orientovaným frameworkům, protože jsou navržené tak, aby umožňovaly vývojáři pracovat na úrovni UI komponent. Díky tomu je možné vytvářet nástroje, které mají podporu UI komponent zabudovanou. Na druhou stranu existuje obrovské množství vývojářů, kteří ovládají Struts se všemi jejich best practises. Pokud nemáme čas potřebný na adoptování JSF, proč nesáhnout po Struts nebo Spring MVC.

Standardizovaný či proptietární?

Výhoda standardizovaných řešení je v míře jejich podpory v nástrojích. Především jednáli se o Java standard jako v případě JSF. Pěkným příkladem je Java Studion Creator od firmy Sun, který lze považovat za první opravdový RAD nástroj pro tvorbu webových aplikací v Jave. Oproti tomu open source proprietární řešení většinou nabízejí lepší možnost úpravy v případech, že něco nevyhovuje. Jejich další výhodou je nezávislost na úrovni implementace standardu.

Bude se používat JSP?

V některých případech není JSP zvoleno jako technologie pro tvorbu dynamického obsahu. Bude umožňovat webový framework jednoduše použít například XSLT? Velice dobře je to podchyceno v JSF, které nejsou závislé na JSP. V případě JSF je dokonce otázka JSP ano či ne celkem zásadní. Především z toho důvodu, že nelze rozumě mixovat JSTL tagy a JSF tagy. Životní cyklus JSF komponent je odlišný od životního cyklu JSP.

Pokud mělo v něčem Tapestry obrovskou výhodu, pak to byl zápis. V případě Tapestry se totiž zapisuje rovnou HTML kód rozšířený o Tapestry tagy. Při zpracování se pak obsah tagů nahradí odpovídajícími hodnotami. V JSF lze JSP nahradit technologií Facelets a podle některých je to více než doporučované.

Podporuje jednoduchou integraci s bean kontejnerem?

Aplikační logika bývá spravována kontejnery typu Spring, EJB apod. Bylo by pro to dobré, aby byly objekty s aplikační logikou jednoduše přístupné webovému frameworku resp. odpovídajícím částem Controller nebo View Helper. Například Seam umožňuje přímé vystavení business logiky realizované pomocí session bean jako JSF backing bean (6).

Nadstandard

V případě JSF nabízejí různé implementace různá rozšíření nad rámec specifikace. Je tedy dobré zvážit jestli se svázat s konkrétní implementací a jejím rozšířením (v mnoha případech velice potřebným) a nebo být nezávislý na použité JSF implementaci.

Závěr

Velkou výhodou Javy je široký výběr nabízených web frameworků. Někdo to sice považuje za navýhodu, protože není úplně jednoduché se v tom množství nabízených řešení vyznat. Cílem tohoto článku bylo zdůraznit, že není důležité konkrétní jméno, ale vlastnosti frameworku, které požadujete. Doufám tedy, že se to povedlo. Tomuto tématu, bychom se měli věnovat v příštím podcastu.

Poznámky

  • [1] JSP je podmnožina technologie Servlet, která je mnohem abstraktnější pracuje přímo nad komunikačním protoklem (HTTP, FTP) a nad ním poskytuje základní API. Každá JSP stránka je vždy před prvním požadavku zkompilována na servlet a použita.
  • [2] Musí implementovat přímo nebo nepřímo javax.servlet.jsp.tagext.Tag
  • [3] Příkladem z Javy je technologie Swing.
  • [4] Model představuje nejen vlastní data, může sloužit ja rozhraní k aplikační logice
  • [5] prezentace The State Of Web Frameworks (PDF) - Craig R. McClanahan
  • [6] Backing beana je v případě JSF takzvaný View Helper, který zprostředkovává data z aplikační logiky.

Související články

Zmiňované frameworky

sobota 2. prosince 2006

Blog jako informační nástroj

Co je víc, podstata informace a nebo její využití?

Blog, a to ten odborný, je zcela jistě fenomén poslední doby. V mnoha firmách se blogování stalo jakousi součásti vnitrofiremní kultůry. Blog není jenom nástroj efektivního využívání informací. Naopak, může se pro firmu stát kompromitujícím prvkem v konkurenčním boji. Kde leží ona hranice? To jsou otázky, které si musí dříve nebo později uvědomit každý kdo se rozhodne sdílet informace.

Jak obecně řešíš sdílení znalostí s okolním světem? Přišel jsi na nějaké jednoduché pravidlo co zveřejnit a co ne? Některé z publikovaných článků by se daly považovat třeba z pohledu firmy za "nezveřejnitelné", protože leckdy by právě tento druh informací mohl způsobit konkurenční výhodu. Co myslíš - kde je ta hranice "tohle zveřejnit a tohle už ne"?

To nejjednodušší pravidlo je nezveřejňovat informace, které nejsou všeobecné známé. Jenže je ještě vůbec něco co není známe? Mnohem důležitější než informace sama je její aplikování a to by mělo zůstat vždy uvnitř firmy. Pokud napíšu článek o návrhu architektury aplikace, ale nezveřejním jakým způsobem to řeší daná firma, tak zveřejnění této informace nikoho nekompromituje.

Analogii je podobná jako v případě softwarový patent vs. autorské právo. Softwarový patent chrání informaci jako takovou beze zbytku. Oproti tomu autorské právo chrání to, jak je s danou informací naloženo, ne informaci samotnou. Z tohoto pohledu existuje i určitá exkluzivita informace. A potom je to čistě na svědomí a vědomí daného člověka.

Z pohledu globální komunity vývojáři drží spolu, ale v podstatě toto spojení nikdy nemůže být těsné, pokud jejich firmy mezi sebou soupeří (produktem, databází znalostí, interními procesy...)

To je podle mě omyl, určitě na úrovni softwarového průmyslu. To v čem spolu skutečně firmy soupeří je aplikování informací nikoliv jejich podstata, samozřejmě až na vzácné výjimky potvrzující toto pravidlo. Prostý fakt, že to takto funguje poskytuje sama praxe. Naopak, těch informací, které jsou ze své podstaty tajné je nepoměrně malé množství. Firmy se mezi sebou liší pouze v efektivnosti jejich využívání a aplikování.

Pouhé vlastnictví informace nezaručuje v globálním světě vůbec nic. Konkurenční výhoda je informaci umět aplikovat a to je know how, které by si firma měla chránit a nemělo by se zveřejňovat.

Kdybych měl vlastní firmu, tak bych naopak své zaměstnance podporoval v publikování vlastních názorů zakládajících se na veřejných informacích, bez ohledu na to jestli je firma využívá či nikoliv. Tím že veřejně vyjádřím svůj názor, mám možnost veřejné konfrontace. Díky konfrontaci mám zpětnou vazbu, kterou využiji k efektivnějšímu aplikování té samé informace, která byla na počátku.

Patří všechny informace všech zaměstnanců pouze a jenom jejich zaměstnavateli?

pátek 1. prosince 2006

JBoss cluster krok za krokem

JBoss logoChtěli by jste otestovat svojí webovou aplikaci v clusteru a nebo si prostě jenom takový cluster vyzkoušet. Pokud ano, můžete pokračovat čtením tohoto průvodce Jak vytvořit JBoss cluster krok za krokem. Nemělo by Vám to zabrat podle mých odhadů více jak patnáct minut.

Níže popsaným způsobem můžete cluster rozběhnout na jednom počítači. Není tedy potřeba více počítačů, samozřejmě pokud budete postupovat analogicky, tak je možný postup aplikovat i pro scénář kdy jsou jednotlivé uzly clusteru oddělené. Přesto má za určitých podmínek i cluster v rámci jednoho počítače opodstatnění. Například OS Windows limituje maximální množství paměti přidělené jednomu procesu, toto omezení se dá obejít právě díky rozběhnutí clusteru v rámci jednoho počítače.

Instalace JBosse

První věc, kterou musíme udělat je nainstalovat JBoss. Doporučuji stáhnout a použít instalátor. Při výběru instalační skupiny zvolte skupinu all tak budete mít JBoss s podporou clusteru. Všechny ostatní hodnoty necháme na defaultních hodnotách. Instalační složku JBosse budeme dále v textu označovat jako JBOSS_HOME.

Vytvoření uzlů clusteru

Po instalaci si vytvoříme cluster, který bude obsahovat dva nódy.

v adresáři JBOSS_HOME\server se nalézá adresář default. Ten dvakrát v tom samém adresáři zkopírujeme jednou pod jménem node1 a node2. Tím pádem budeme mít v JBOSS_HOME\server celkem tři adresáře default, node1 a node2.

V souborech JBOSS_HOME\server\node1\conf\jboss-service.xml a JBOSS_HOME\server\node2\conf\jboss-service.xml odkomentujeme následující beanu.

<mbean code="org.jboss.services.binding.ServiceBindingManager" name="jboss.system:service=ServiceBindingManager">

Navíc v tom samém konfiguračním souboru druhého uzlu změníme

<attribute name="ServerName">ports-01</attribute>

na

<attribute name="ServerName">ports-02</attribute>

Odkomentováním této beany jsme určili, že se nepoužijí defaultní porty, protože by nám dva JBoss server neběžely vedle sebe na jednom počítači. Místo těchto defaultních portů se použijí porty nadefinované v souboru ${jboss.home.url}/docs/examples/binding-manager/sample-bindings.xml. V tomto souboru jsou jsou pak pod klíčem ports-01 a ports-02 nadefinovány různé sady portů. Proto jsme v konfiguračním souboru druhého uzlu nastavili, aby použil jinou sadu.

Spuštěcí skripty

Nyní si vytvoříme spouštěcí skripty, které nám nastartují jeden či druhý node.

V adresáři JBOSS_HOME\bin vytvoříme soubor node1.bat a vložíme do něj run -g dagi -u 228.1.2.199 -c node1.

V adresáři JBOSS_HOME\bin vytvoříme soubor node2.bat a vložíme do něj run -g dagi -u 228.1.2.199 -c node2

Uživatelé Linux samozřejmě *.sh.

Parametr g resp. určuje jméno partition. Partition defiuje rodinu uzlů, které jsou sdruženy v daném clusteru. Proto naše dva uzly mají stejné jméno partition neboť patří do stejného clusteru. Parametr u definuje UDP multicast adresu, na které si budou jednotlivé nódy v clusteru dorozumívat.

Multicast adresu si můžete zvolit jako jsem to udělal já (228.1.2.199), ale musí být pro všechny uzly vašeho clusteru stejná. Každopádně, pokud jste v rámci sítě a někdo z kolegů si bude také zkoušet rozjet JBoss v clusteru a nastaví si stejné jméno partition a stejnou multicast adresu, tak se připojí do Vašeho clusteru. Z tohoto důvodu si zvolte jméno partition a multicast adresu s rozmyslem.

Pro vyzkoušení konfigurace clusteru nejdříve spustíme node1.bat a až nastartuje tak node2.bat. Můžeme to udělat i opačně protože na pořadí v případě JBoss clusteru nezáleží, všechny uzly jsou si rovny. Není zde žádný řídící uzel, který by se musel nahazovat. V logu uzlu, který jsem spustili by mělo být po startu druhého uzlu něco podobného následujícímu.

       
22:48:52,250 INFO  [dagi] New cluster view for partition dagi (id: 1, delta: 1)
: [10.0.0.3:1199, 10.0.0.3:1299]
22:48:52,250 INFO  [dagi] I am (10.0.0.3:1199) received membershipChanged event:
22:48:52,250 INFO  [dagi] Dead members: 0 ([])
22:48:52,250 INFO  [dagi] New Members : 1 ([10.0.0.3:1299])
22:48:52,250 INFO  [dagi] All Members : 2 ([10.0.0.3:1199, 10.0.0.3:1299])       
       
    

Nastavení loadvbalanceru

Takže máme již nastavený cluster, ale před ten musíme posadit load balancer, který bude rozdělovat jednotlivé příchozí HTTP požadavky na uzly našeho clusteru. Následuje obrázek z je originální JBoss dokumentace a ilustruje funkci load balanceru v případě, že jeden z uzlů clusteru odejde.

The load balancer architecture for clustering

Protože JBoss používá jako HTTP server Tomcat, který podporuje mod_jk je možné jako load balancer použí HTTP server Apache.

Takže pusťmě se do toho!

Instalace

Doporučuji Vám sthánout kompatibilní verze mod_jk a Apache. Ja jsem stáhnul mod_jk-apache-2.2.3 a instalátor Apache 2.2.3. Při instalaci samotného Apache serveru není potřeba nic měnit, můžete použí defaultní hodnoty. Jenom si dejte pozor, že nemáte obsazený defaultní port 80 pro HTTP komunikaci např. jiným serverem (IIS) či jiným programem. Instalační adresář Apache je odkazován jako APACHE_HOME.

Po instalaci nakopírujte stahnutý mod_jk-apache-X.Y.Z.so do adresáře APACHE_HOME\modules a přejmenujte na mod_jk.so.

Apache konfigurace

Přidejte následující řádek Include conf/mod-jk.conf na konec souboru APACHE_HOME\conf\httpd.conf.

Vytvořte soubor APACHE_HOME\conf\mod-jk.conf a vložte do něj následující obsah.

     
# Load mod_jk module
# Specify the filename of the mod_jk lib
LoadModule jk_module modules/mod_jk.so
 
# Where to find workers.properties
JkWorkersFile conf/workers.properties

# Where to put jk logs
JkLogFile logs/mod_jk.log
 
# Set the jk log level [debug/error/info]
JkLogLevel info 
 
# Select the log format
JkLogStampFormat  "[%a %b %d %H:%M:%S %Y]"
 
# JkOptions indicates to send SSK KEY SIZE
JkOptions +ForwardKeySize +ForwardURICompat -ForwardDirectories
 
# JkRequestLogFormat
JkRequestLogFormat "%w %V %T"
               
# Mount your applications
JkMount /application/* loadbalancer
 
# You can use external file for mount points.
# It will be checked for updates each 60 seconds.
# The format of the file is: /url=worker
# /examples/*=loadbalancer
JkMountFile conf/uriworkermap.properties               

# Add shared memory.
# This directive is present with 1.2.10 and
# later versions of mod_jk, and is needed for
# for load balancing to work properly
JkShmFile logs/jk.shm 
              
# Add jkstatus for managing runtime data
<Location /jkstatus/>
    JkMount status
    Order deny,allow
    Deny from all
    Allow from 127.0.0.1
</Location>         
     
    

Vytvořte soubor APACHE_HOME\conf\uriworkermap.properties a vložte do něj následující obsah.

    
# Simple worker configuration file

# Mount the Servlet context to the ajp13 worker
/jmx-console=loadbalancer
/jmx-console/*=loadbalancer
/web-console=loadbalancer
/web-console/*=loadbalancer

/test/*=loadbalancer
/test=loadbalancer    
    
    

Tím je řečeno, které sub cesty budou směrovány load balancerem. Jinýmy slovy jakýkoliv HTTP požadavek na http://hostname/jmx-console nebo http://hostname/web-console a nebo http://hostname/test doručí na jeden z uzlů v našem clusteru. Aplikace jmx-console a web-console jsou v JBossu standardně. Pro naše budoucí testovací účely jsme připravil ješte mapování aplikace test. Pokud si to budete chtít vyzkoušet nějakou aplikaci, pak stačí aby byla v JBossu deploynutá v kontextu test případně si samozřejmě můžete namapovat vlastní existující aplikaci. Stačí pouze uvést její kontext.

Dále (už se blížíme do finále) je potřeba vytvořit soubor APACHE_HOME\conf\workers.properties a do něj vložit následující obsah.


# Define list of workers that will be used
# for mapping requests
worker.list=loadbalancer,status

# Define Node1
# modify the host as your host IP or DNS name.
worker.node1.port=8109
worker.node1.host=localhost 
worker.node1.type=ajp13
worker.node1.lbfactor=1
worker.node1.cachesize=10

# Define Node2
# modify the host as your host IP or DNS name.
worker.node2.port=8209
worker.node2.host=localhost
worker.node2.type=ajp13
worker.node2.lbfactor=1
worker.node2.cachesize=10

# Load-balancing behaviour
worker.loadbalancer.type=lb
worker.loadbalancer.balance_workers=node1,node2
worker.loadbalancer.sticky_session=1
#worker.list=loadbalancer

# Status worker for managing load balancer
worker.status.type=status    
    
    

Pozn: všiměte si hodnoty worker.node1.port a worker.node2.port. To jsou porty, na kterých poslouchá JK konektor JBosse resp. Tomcatu.

Konfigurace Tomcatu uvnitř JBosse

Poslední věc, kterou musíme udělat je nastavení Tomcatu a jeho Mod_JK.

V souboru JBOSS_HOME\server\node1\deploy\jbossweb-tomcat55.sar\server.xml přidejte do elementu Engine atribut jvmRoute="node1". Ten potom vypadá následovně

   <Engine name="jboss.web" defaultHost="localhost" jvmRoute="node1">
   

V souboru JBOSS_HOME\server\node2\deploy\jbossweb-tomcat55.sar\server.xml přidejte do elementu Engine atribut jvmRoute="node2". Ten potom vypadá následovně

   <Engine name="jboss.web" defaultHost="localhost" jvmRoute="node2">
   

V souboru JBOSS_HOME/server/node1/deploy/jbossweb-tomcat55.sar/META-INF/jboss-service.xml a JBOSS_HOME/server/node2/deploy/jbossweb-tomcat55.sar/META-INF/jboss-service.xml změňte obsah elementu attribute s atributem name a hodnotou UseJK na true.

   
<attribute name="UseJK">true</attribute>

A to je vše, nyní máme cluster připravený včetně loadbalanceru.

V případě nejakých nejasností doporučuji konzultovat postup s JBoss originální dokumentací. Konkrétně sekce 16.5. HTTP Services v rámci kapitoly 16. clustering.

Zkouška clusteru

  • nastartujte Apache
  • nahoďte uzel 1
  • nahoďte uzel 2
  • otevřete prohlížeč a zadejte adresu http://localhost/web-console/. Pokud Vám Apache poslouchá na jiném portě než 80, tak jej musíte zadat. Webová konzole je defaultni chráněná jménem a heslem, které jste zadali v jednom z instalačních kroků JBosse.
  • Na stránce webové konzole, je v pravém rámu informace Running config: 'XXX'. Pokud několikrát za sebou zadáte adresu http://localhost/web-console/ a mezi každým požadavkem si smažete cookies a nebo použijete jinou instanci prohlížeče tak by jste se měli čas od času dostat na jiný uzel. Tedy bude se měnit nápis Running config: 'Node1' za Running config: 'Node2' a naopak.

Pokud se Vám za žádných okolností nepovede, že prohlížeč nepujde alespoň někdy (po vymazání cookies či puštěním jiné instance prohlížeče) na jiný uzel, pak je něco špatně. V opačném případě gratuluji, máte úspěšně nastavený a fungující JBoss cluster. O tom proč jsme mazali cookies aneb sticky session a jak udělat aplikaci, která bude automaticky replikovat HttpSession zase příště.

Tenhle článek by nikdy nevzniknul nebýt Gala Mardera VP R&D z firmy InterBit.

Thank you very much, you are the real Java champion!

neděle 26. listopadu 2006

Java trpí nedostatkem hostingů

Ze všech stran se na nás valí připravované novinky v JDK6 a JDK7, které by měly zvýšit efektivitu, zkvalitnit a zjednodušit vývoj v Jave. Jenže problém Javy leží trochu někde jinde než ve vývojové fázi. Problematickou fází, alespoň pro určitou část vývojářů, je hosting Java aplikací. Jedním z mýtů o Jave je, že není tak efektivní jako PHP. K šíření tohoto nesmyslu nezanedbatelnou měrou pomáhá fakt, že není jednoduše možné hostovat webové aplikace v Jave takřka kdekoliv jako v případě PHP.

Jenom v České republice existuje velké množství freehostingu pro PHP aplikace a ještě větší množství placených hostingu a ve světě je tomu stejně. Oproti tomu freehostingy Javy by se v celosvětovém měřítku dali spočítat na prstech jedné ruky. V placeném hostingu je sice situace lepší, ale to k popularizace Javy pro web samozřejmě nestačí.

K čemu je Jave skvělá variabilita middleware produktů, když to základní, tedy aplikaci někde hostovat (pokud možno zdarma) chybí. Samozřejmě je pro vývojáře mnohem lepší, když může svou aplikaci nasadit takřka kdekoliv, než přemýšlet kde by to vlastně vůbec šlo a za kolik. V PHP i v Jave si doma uplácáte na koleně aplikaci za pár dní, jenže narozdíl od té v PHP můžete tu v Jave strčit do šuplíku pokud si nezaplatíte hosting a nebo si nerozjedete vlastni server.

Jenže ono se to lehce řekne udělat hosting Java aplikací, ale hůře se to provádí. Java jako platforma pro web je v současném modelu, alespoň podle mého názoru, velice obtížně hostovatelná. Největší problém je izolace jednotlivých web aplikací uvnitř JVM. Ač je snadné zamezit naschválnému kódu alá System.exit(0) díky SecurityManageru, zůstává několik dost závažných problémů.

sdílený paměťový prostor
paměťová náročnost či případně memory leak jedné webové aplikace složí všechny zbylé uvnitř VM. Paměťový prostor VM je shora striktně omezen maximální hodnotou, která je zadaná při startu VM a která nemůže být při běhu přesáhnutá.
velká paměťová náročnost
díky paměťovému modelu a jeho správě Garbage Collectorem (uklízeč, sběrač neplatných objektů) je Java server mnohem více paměťově náročnější než klický http server. S každým HTTP požadavkem vzniká velké množství nových objektů, kterou jsou po jeho skončení k ničemu a čekají až je GC odstraní. To samo o sobě není problém, ale GC musí být velice citlivě nastaven tak, aby nepracoval na hranici zaplnění celého heapu a nedocházelo k jeho častému volání, které by vyústilo ve značné zpomalení běhu serveru.

Od verze 1.4 je možné významně ovlivnit chování GC. Paměťová náročnost není z hlediska správy jedné aplikace, kterou máme pevně v ruce problém. Jenže v případě veřejného serveru máme množství paralelně bežících aplikací uvnitř jedné VM, které se mohou velice nepříjemně ovlivňovat.
DOS náchylnost
Je možné snadno dosáhnout DOS (Denial of Service) útoku relativně nezávadným kódem. Dva příklady. Prvním z nich je nekonečný cyklus způsobí plné vytížení procesoru po dobu do vypršení timeoutu HTTP požadavku. Druhý příklad představuje vytvoření nového HTTP požadavku na sebe zevnitř bežícího vlákna zpracovávajícího HTTP požadavek. To vede k vyčerpání vnitřních vláken určených k obsloužení požadavků. Výsledkem je, že server je schopen akceptovat HTTP požadavky, ale není schopen je vnitřně obsloužit - vzniká deadlock. Vytváření nových vláken je limitované zdroji operačního systému např. na OS Linux je každé Java vlákno mapováno jedna ku jedné na vlákno operačního systému.

Pozn: Některé aplikační servery nabízejí možnost nastavení dedikovaných thread poolu (jak se to řekne česky?) per aplikace.
nešetrnost hot deploymentu
Pokud dojde ke změně JSP aplikační server může snadno nové JSP zkompilovat a použít. V případě tříd to tak snadno nejde. Za nahrávání tříd je v Jave zodpovědný classloader, ale ten neumožňuje prostou výměnu staré třídy za novou. Naštěstí každá webová aplikace má uvnitř serveru vlastní classloader. Pokud je detekovaná (sledováním souborového systému) nová verze některé třídy, zahodí se celý classloader dané aplikace a aplikace resp. její třídy se nahrají znovu novým classloaderem - tomu se říká hot deployment.

Nahrávání celé aplikace při změně každé třídy je v produkčním prostředí systémově drahé. To znamená, že infrastruktura hostingu musí počítat vždy s dvojicí serverů - produkční a testovací. Na testovacím, kde je hot deployment zapnutý, se aplikace vyladí a poté se propaguje na produkční server. To je mnohem náročnější na vývoj než prosté nakopírování nového PHP souboru na server.

Pozn: Při hot deploymentu dochází například na serveru Tomcat k nepříjemnému memory leaku, který vzniká díky tomu, že GC nemůže uvolnit speciální oblast heapu, která slouží k definici tříd.

Ano i tyto problémy jako každé lze nějak řešit nebo omezit jejich dopad. Otázka je proč Sunu tato situace nevadí. Kdyby se totiž podařilo tyto problémy vyřešit, rozšířenost Javy by se nepochybně mnohonásobně zvýšila. Dokonce existuje JSR 121 (Isolation API, Resource management API viz A Resource Management Interface for the Java Platform(PDF)), které bylo úspěšně použito v prototypu takzvané MVN (Multitasking Virtual Machine) a tyto problémy může významnou měrou pomoci redukovat.

Ještě více je zarážející, že koncept MVN a nebo jeho aspekty Sun aplikoval pro oblast mobilních zařízení kde bylo nutné brát zřetel na omezené zdroje. Korunu všemu dává Sun tím, že pro vlastní stránky používá PHP. Ať už je to z jakéhokoliv důvodu, člověka hned napadne že nepojídají jídlo, které sami produkují.

Ač to bude možná znít zvláštně, tak právě nyní je největší možnost s tím něco dělat. Díky otevření Javy jako open source je nyní možné prosadit koncept JSR 121 mnohem snáze. Přenositelná implementace MVM a aplikační server využívající isolation API a resource management by mohla znamenat zlom. V této souvislosti je velice zajímavé, že ať už BEA Weblogic či IBM WebSphere měli vlastní implementace JVM a nešáhli po něčem podobném.

Kdybych si měl vybrat mezi tím, jestli tu bude široce dostupný Java web hosting díky forku JVM na MVN a nebo ideovou čistotou JVM, tak volím raději potenciálně nebezpečnější fork...

čtvrtek 23. listopadu 2006

Podcast volume#2 venku

Neumřeli jsme, žijeme dál a s námi další podcast na téma Open Source Java, Prague Sun Tech Days, JBuilder 2007. Poslouchejte, bavte se a poskytněte nějaký ten feedback. Jinak nejsme blázni nejsme pacienti, jsme stejní jako vy ;-).

Souvisejicí linky

úterý 21. listopadu 2006

CZJUG listopad 2006 Java EE 5 a testovací frameworky

Jenom pro pořádek, protože to již všichni víte, ve čtvrtek nás čeká pravidelné setkání CZJUG tentokrát na téma Java EE 5 a testovací frameworky. Prezentace si pro nás připravili Bruno Bossola a Filippo Diotalevi.

Automatic testing of Enterprise Applications using Open Source tools

In actual enterprise level applications, either web or services based, it's more and more necessary to have a suite of tests to be executed automatically in order to prevent regressions of the system. Commercial tools actually available are quite expensive and they often retain high levels of complexity or too high learning curves. In this session you will be introduced to some mature open source tools, with particular reference to FitNesse for services test and Selenium for web applications test.

From J2EE to Java EE

How the new version of JavaEE affects the way we design and develop applications; how J2EE core patterns change, a few words on the Project Glassfish. The goal of this presentation is to give the listener a brief overview of the new features of the new Java Enterprise Edition platform, and explain how these new features will impact the way we design and develop enterprise applications.

oficiální stránka listopadového setkání

pondělí 20. listopadu 2006

IDE war - víc oleje, JBuilder 2007

Moje Web Tools Project demo vyprovokovalo Roumena k tomu, aby v jiném demu ukázal jak to jde v NetBeans jednodušeji. Spíše než jednodušeji, bych souhlasil s tím, že je to celé o filosofii Eclipse a NetBeans.

Pokud nemáte těch tahanic dost, tak doporučuji názor vyprovokovaný pátymi narozeninami Eclipse viz Is Eclipse Poised to Move into Quiet Ubiquity?.

Aby toho nebylo málo, tak byl vydán JBuilder 2007, který je založen právě na Eclipse.

JBuilder 2007 includes OptimizeIt for code profiling, Team Insight for integration with various collaboration and CI tools, LiveSource for UML modeling of Java projects, and Visual EJB, for GUI design of EJB modules

Více informací o vlastnostech najdete ve feature matrix (PDF). Diskuse viz TSS JBuilder 2007 - redesigned around Eclipse - released.

Ještě Vás to baví? Pokud ne, kašlete na image a používejte ten nástroj, který Vám nejvíce vyhovuje!

pátek 17. listopadu 2006

Jaké byly Sun Tech Days

Ve středu 15. listopadu jsem natěšen vyrazil na své druhé Sun Tech Days (dále v textu STD). Mohu tedy porovnávat minimálně dvě tyto akce. Před tím než se pustím do nějakého hodnocení zrekapituluji přednášky jenž jsme absolvoval a informace, které mě zaujaly.

Keynote
Kafka, Dvořák, Jágr, Havel nebo tak nějak podobně začal šéf českého Sun centra Pavel Šuk úvodní keynote. Ani nevím proč, ale utkvělo mi to v paměti. To nejzajímavější na keynote, byly technické prezentace ukazující sílu Java technologií. Kdo tam byl, toho asi překvapí, že mě spíše nezaujali roboti honící se po bílem ubrusu, ale prezentace swingové desktop aplikace. Nebál bych se to označit za Swing Next Generation, protože L&F této aplikace vypadal naprosto neuvěřitelně na Swing.
Java EE 5 and Glassfish: A Plunge into the Aquarium
Pro mě osobně nejzajímavější prezentace na samotném STD. Aplikační server GlassFish, referenční implementace J2EE 5.0, také slouží jako Sun App Server. Jeden z jeho vývojářů Herold Carr prezentoval koncept tohoto serveru a co bylo zajímavé, pustil se do detailu architektury. Zaujal mě jejich HTTP konektor Grizzly a prezentované výsledky jak dokáže škálovat např. oproti Coyote (tento konektor používá Tomcat). Dále to byl projekt Phobos, který na GlassFishy integruje skriptovací jazyky - zatím mají podporu pro JavaScript.
Sun Java DB, a Small, Easy to Use, Pure Java RDBMS
Apache Derby představení databáze, koncept standalone vs. server, podporované standardy, protokoly. Zaujala mě možnost psaní databázových procedur přímo v Jave. Přednášející mě přesvědčil, že Java DB je dobrá alternativa k HSQLDB, ale jak sám dodal s Oraclem nebo SQL serverem nejde měřit. Byly prezentovány i nějaké výkonnostní charakteristiky, z nichž si pamatuji závěr o nízké paměťové náročnosti. Docela mě zklamala demo, které mělo prezentovat Java DB v rámci appletu. Krom toho, že nebylo pochopitelné "co chce básník říci", tak mi přišlo jako nesmyslný případ užití této DB.
JAX-WS and Tangoing with .NET (WSIT)
Opět Herold Carr tentokrát o WS a především o interoperabilitě mezi Javou a .NETem. Údajně po těsné spolupráci Sunu s Microsoftem se podařilo realizovat nad WS transakce, bezpečnost atd. Mluvilo se také o JAXB (serializace/desiralizace Java do XML a naopak) 2.0.
Web 2.0: The Technologies and Tools to Build It
Tato přednáška mě absolutně zklamala. Doris Chen neřekla ani slovo o Web 2.0 a jeho podpoře v Sun nástrojích. Celá prezentace byla jenom o JSF a to musím dodat, že ani to se nepovedlo. Přišlo mi to jako úsilí smíchat v daném časovém intervalu všechno co umí Java Studio Creator. Na této přednášce jsem se definitivně utvrdil v tom, že Web 2.0 je buzzword. Přednáška zřejmě dostala do názvu Web 2.0, protože se v demu použily AJAX komponenty nabízené z palety Java Studio Creatoru. Jinými slovy definice Web 2.0 aplikace - používá AJAX a v názvu má beta.
In-depth Session: EJB 3.0 and Java Persistence APIs: Experience the Simplicity
Dvouhodinová přednáška o EJB 3.0 rozdělená do několika částí. První část, X let jsme Vám tlačili do hlavy jak je EJB skvělá technologie, abychom si ve verzi 3.0 uvědomili, že je pro vývojáře příšerně složitá na použití. Tak jsme deployment descriptory nahradili anotacemi a pochopili, že ty POJO objekty a IoC ve Srpingu mají něco do sebe. Druhá část, Ty entity beany jsou profláknuté a ten Hibernate umí persitence tak pěkně, inspirujme se a udělejme něco podobného a nazvěme to třeba Java Persistence API. Když už jsme u toho, on by nějaký vývojář chtěl to JPA použít i třeba v desktopové aplikaci, tak ať je to možné. Část tři, EJB 3.0 je samozřejmě podporovaná v našich vývojových nástrojích jako NetBeans. Z mého pohledu celkem zajímavá přednáška.

Když to vezmu kolem a kolem, tak bych úroveň přednášek oznámkoval číslem 4 na desetibodové stupnici. Pokud jste šli na danou přednášku s tím, že jste o dané technologii měli nějaké znalosti, pak jste se většinou nic nového nedozvěděli, na druhou stranu alespoň jste si ty informace osvěžili. Alespoň na přednáškách, které jsme absolvoval já, to tak bylo. Je otázkou jestli jsou vysloveně specializované přednášky vhodné pro akci STD. Bohužel jsem nebyl na žádném hands on lab, který mohl být určený pro hlubší vyzkoušení dané technologie. Ovšem umím si představit, že například pro Java nováčky byl STD úplně úžasně nabitý novými informacemi

Na druhou stranu STD není jenom o těch přednáškách, je to o možnosti potkat bývalé kolegy nebo ostatní vývojáře, dohodnout obchod,vyměnit informace a to šlo díky vytvořenému zázemí celkem dobře.

Ještě mi to nedá a musím vzpomenout soutěžní kvíz firmy QBizm s Java puzzles v rámci STD. Byly to docela chytáky, dva z nich které mě opravdu zaujaly tu volně uvedu.

    try{
      System.out.println("Hello");
      System.exit(0);
    }finally{
      System.out.println("World");
    }

Co bude na výstupu?

  1. Hello
  2. HelloWorld
  3. Nepujde zkompilovat

A druhý puzzle.

    //a.)
    String sql = "ahoj tady " + name + ", jak se mas v " + time + "?";
    
    //b.)
    StringBuffer sql = new StringBuffer();
    sql.append("ahoj tady ");
    sql.append(name);
    sql.append(", jak se mas v ");
    sql.append(time);
    sql.append("?");

Co bude v runtime efektivnější?

  1. a
  2. b
  3. a i b jsou ekvivalentní

Související články

čtvrtek 16. listopadu 2006

WTP flash demo

Jednoduchost tvorby webových aplikací v IDE Eclipse byla často terčem oprávněné kritiky. Časy se ovšem mění a to především díky integrované podpoře Callisto Discovery, pomocí které zvládne WTP nainstalovat a vytvořit web aplikaci úplně aplikaci každý. Na java.cz jsem připravil osmiminutové flash demo, které vás o tom přesvědčí.

Prosím omluvte slabší zvuk a to, že nejde demo ovládat. Pokud má někdo JavaScript kód, který umožňuje flash ovládat, určitě mě kontaktujte. Zkusili bychom to tam zapracovat.

pondělí 13. listopadu 2006

Open source Java pod GPL licencí

Že bude Java open source, to se vědělo již dlouho. Otázkou zůstávalo, která z open source licencí bude použita. V druhé polovině minulého týdne se objevily spekulace, že by to mohla být licence GPL. Spekulace se potvrdily a dnes oznámí Sun CEO Jonathan Schwartz a Rich Green uvolnění klíčových částí Javy pod licencí GPL verze 2 (český překlad GPL PDF).

V první várce, tedy dnes, bude pod GPL uvolněn HotSpot runtime, javac kompilátor, JavaHelp a Sun implementace Java ME. V uvozovkách zbytek Javy bude uvolněn v první polovině roku 2007. GPL licence, kterou Sun zvolil, bude obsahovat výjimku pro classpath, která zaručuje, že bude možné používat core java knihovny bez nutnosti uvolnit vlastní kód jako open source.

GPL licence by měla Javu ochránit před rozdvojením, ale zároveň jí komunitě poskytovat otevřenou a dostupnou pro přístup ke zdrojovým kódům, portovatelnou a modifikovatelnou v případě potřeby. Některé zdroje uvádějí, že Sun nabídne dual licenci, což znamená, že zde budou moci paralelně stále existovat komerční certifikovaná verze Javy jako doposud.

Kromě IBM, které tlačilo na open sourcování Javy již delší dobu se přidali i ne nepodstatné komunity vývojářů a komerčních zákazníků volajících po tomtéž. Rád bych se krátce zmínil o těch podstatných komunitách vývojářů. Možná se budete divit, ale velkou váhu zde má pro Sun například brazilská komunita, která čítá 18000 vývojářů.

V Brazílii je Java nejen velice populární, ale je na ní postavena i řada veřejných (vládních) informačních systémů této země, např. systém pro evidenci zdravotní péče. Není proto divu, že například z této komunity sílil tlak na open sourcování Javy. Brazílie zřejmě není výjimka, jsou zde i další země, kde by se zaváděním open source do státní správy, otázka open source Javy dostala dříve nebo později na stůl.

O open source Jave se bude určitě mluvit na nadcházejících Sun Tech Days v Praze, takže to je určitě o důvod více přijít. Zároveň tomuto tématu věnujeme čas v nějakém z příštích podcastů. Bude mít na Vás uvolnění Javy jako open source nějaký dopad, ať již pozitivní či negativní?

Zdroje

čtvrtek 9. listopadu 2006

Český java podcast je tu!

Tak jsem to spáchali milá Máňo. Já, Filemon, Roumen a Borůvek jsme nahráli přes porodní bolesti první podcast na téma Vývojová prostředí v Jave.

Související linky

neděle 5. listopadu 2006

Transparentní cache pomocí Aspektově Orientovaného Programování

Jeden z důvodů proč jsem si oblíbil aspektově orientované programování (dále AOP) je možnost transparentně rozšiřovat stávající API o další funkčnost bez nutnosti do něj přímo zasahovat. V tomto článku bych vám rád možnosti AOP ukázal na implementaci cacheování vrstvy pro přístup k databázi.

V každé aplikaci máme kousky kódu, které se nám prolínají všemi vrstvami naší aplikace, ale do žádné nepatří konkrétně. Může se jednat o kus kódu pro logování-debug, bezpečnost, auditování, synchronizaci atd. Těmto kouskům kódu můžeme říkat aspekty. To co nám AOP nabízí je že můžeme tyto aspekty prolínat stávajícím kódem aniž bychom tento kód museli modifikovat.

Pomocí AOP si tak velice snadno můžeme napsat například aspekt, který zaloguje jakoukoliv výjimku vyhozenou z našeho API aniž bychom museli dopsat čárku kódu. Obecně vzato se aspekty dělí na dva druhy, vývojové a nebo produkční. Vývojové aspekty slouží ryze pro účely vývoje např. trasování nebo měření výkonu. Vývojové aspekty jsou součástí aplikace pouze po dobu vývoje. Produkční aspekty jsou aspekty, které nám v aplikaci zůstávají napořád a zajišťují jednotlivé vlastnosti systému např. transakce, bezpečnost apod.

Základy AOP

Ať už budete používat jakýkoliv AOP framework tak jsou pojmy společné všem. Tyto pojmy musíte pochopit, abyste porozuměli aspektově orientovanému programovaní.

Join Point
Defiuje stavy, ve kterých se může nacházet vykonávání kódu např. volání metody, volání konstruktoru, statická inicializace atd.
Pointcut
Definuje jazyk, pomocí kterého se lze na jednotlivé Join Ponty zavěsit
Advice
Umožňuje nadefinovat vlastní kód, který se vykoná v závislosti na zavěšení pointcatu. V podstě když nastane tohle, udělám toto.

Pokud dáme dohromady Join Point, Pointcut a Advice dostaneme aspekt. Máme tedy aspekt, který něco umí. Pro psaní aspektu využíváme klasickou syntaxi jazyku Java, kterou kombinujeme se syntaxí AOP. AOP zavádí nové konstrukty jako pointcut, around, before, call atd. Jak aspekt vypadá, ilustruje následující obrázek.

Syntaxe aspektu
Plná velikost obrázku (cca 25KB)>

Otázkou je jak tento aspekt integrovat s naším kódem. To je úkol pro AOP framework a této činnost se říká weaving, což v doslovném překladu znamená proplétání. Weaving provádí takzvaný weaver a k vlastnímu propletení může dojít ve zvoleném časovém okamžiku z hlediska java kódu.

Weaver

Weaver vezme na vstupu nějaké třídy a na výstupu produkuje nějaké třídy, to se může stát v následujících čtyřech časových okamžicích.

compile time weaving
Provádí se během kompilace. K dispozici jsou přímo zdrojové kódy, které jsou zkompilovány a výsledkem jsou zkompilované kód obsahující aspekty. Aspekty mohou být ve formě zdrojových souborů a nebo zkompilované.
post-compile weaving
Provádí se na již zkompilovaném kódu. Výsledkem je opět zkompilovaný kód. Aspekty mohou být ve formě zdrojových souborů a nebo zkompilované.
Load-time weaving
Provádí se ve chvíli kdy zkompilovaný kód natahuje classloader do JVM. Samozřejmě k tomuto se musí použí speciální weaver classloader
Rutnime weaving
Modifikuje zkompilovaný kód, který byl již natažený do JVM, bez nutnosti třídu znovu načítat.

Samozřejmě záleží na dané implementaci AOP frameworku resp. weaveru, jaký typ weavingu podporuje. Například AspectJ, ze kterého v tomto článku vycházím, podporuje kromě runtime weavingu všechny výše uvedené způsoby.

Jak na cacheování pomocí AOP

Cacheování je pěkným příkladem vlastnosti, která se nám prolíná celým systémem. V závislosti na nasazení aplikace (rozdílná data, zatížení atd.) se mohou různit požadavky na to co a kde cacheovat. To v těch horších případech vede k zavádění adhoc "cacheování" různými pokoutnými způsoby přímo do kódu. AOP nám umožňuje řešit cacheování velice elegantním způsobem, bez toho aniž bychom byly nuceni měnit stávající kód.

Jako příklad jsem zvolil situaci, kdy máme vytvořenou vrstvu pro přístup k datům, např. pomocí JDBC a rádi bychom tuto vrstvu cacheovali. Jako AOP framework jsem zvolil AspectJ, který je výborně integrovatelný s vývojovým prostředím Eclipse díky pluginu AJDT. Kromě zvýrazňování syntaxe, doplňování kódu, generování speciálního JavaDocu a dalších důležitých vlastností můžeme aspekty debugovat jako jakoukoliv jinou javovskou aplikaci.

Volba AspectJ neznamená, že bychom byli nějak svázáni s Eclipse. AspectJ má totiž i klasický kompilátor ovládaný z příkazového řádku nebo Antu. Kromě toho obsahuje i AspectJ Browser, což je samostatná GUI aplikace pro vývoj AOP. Ošem zpět k našemu příkladu.

Následující kód ukazuje klasický objekt pro přístup k datům (DAO - data access object), který definuje operace pro zápis, čtení, modifikaci a mazání (CRUD - create, read, update and delete). Pro jednoduchost jsem náš DAO objekt neinfikoval JDBC kódem, místo toho jsem použil java.util.Set, který simuluje databázovou tabulku.

public class FooDao {
  //pro simulaci databazove tabulky pouzijeme Set
  private Set<FooBean> table = new HashSet<FooBean>();
  
  public void update(FooBean bean){
    //JDBC kod pro update objektu v databázi
    //....
    
    //my pro simulaci pouzijeme vnitrni Set
    table.remove(bean);
    table.add(bean);
  }
  
  public void save(FooBean bean){
    //JDBC kod pro insert objektu do databáze
    //....
    
    //my pro simulaci pouzijeme vnitrni Set
    table.add(bean);
  }
  
  public void delete(FooBean bean){
    //JDBC kod pro smazani objektu v databázi
    //....
    
    //my pro simulaci pouzijeme vnitrni Set
    table.remove(bean);
  }
  
  public FooBean findById(int id){
    //JDBC kod pro smazani objektu v databázi
    //....
    
    //my pro simulaci pouzijeme vnitrni Set
    FooBean wanted = new FooBean(id);
    FooBean founded = null;
    for (FooBean bean: table) {
      if(wanted.equals(bean)){      
        founded = bean;
        break;
      }
    }
    return founded;
  }
  
  public List<FooBean> getAll(){
    //JDBC kod pro smazani objektu v databázi
    //....
    
    //my pro simulaci pouzijeme vnitrni Set
    return new ArrayList<FooBean>(table);
  }
}

Nyní jakým způsobem naimplementovat danou cache pomocí aspektu. Musíme si nadefinovat jakým způsobem bude cache nad DAO obejkty fungovat.

Pravidla cacheování
  • Cacheuje se vždy nad daným DAO objektem, tj. všechny cacheované hodnoty jsou sdruženy v jedné skupině. Jméno skupiny odpovídá packagi a třídě DAO objektu.
  • Cacheují se pouze výsledky operací pro čtení.
  • Jakákoliv jiná operace (mazání, modifikace, zápis) nad DAO objektem invaliduje danou cacheovanou skupinu, tj. všechny nacacheované objekty pro dané DAO.

Začneme vytvořením aspektu, který má jako jakýkoliv jiný java objekt konstruktor a vnitřní proměnné. Každý aspekt vystupuje jako singleton, pokud neřekneme jinak. To využijeme k tomu, abychom prostřednictvím konstruktoru zinicializovali cache. Já jsem jako cacheovací framework zvolil Java Caching System z rodiny Jakarta projektů. Zároveň v rámci aspektu nadefinujeme metody pro práci s cache. Ty můžeme označit jako protected pro případ, že bychom v budoucnu vytvořit specializovanější verzi našeho aspektu. Ano, v rámci aspektů můžeme použít dědičnost.

public aspect CacheAspect {  
  private JCS cache; //instance cache manageru
  
  /**
   * Konstruktor aspektu, který inicializuje cache.
   
   @see CacheAspect#initCache()
   */
  public CacheAspect() {
    initCache();
  }  
  
  /**
   * Provede inicializaci cache. Metoda je volána pouze jednou při vytvoření
   * aspektu. 
   */
  protected void initCache(){
    try{
      this.cache = JCS.getInstance("");//získáme defaultní cache
    catch (CacheException e){
      throw new RuntimeException(e);
    }
  }
    
  /**
   * Invaliduje všechny cacheované objekty v dané skupině.
   @param groupName jméno skupiny
   */
  protected void invalidate(String groupName){
    this.cache.invalidateGroup(groupName);
  }
  
  /**
   * Odstraní objekt z dané cacheovací skupiny.
   @param cachedObjectKey klíč objektu, který bude odstraněn 
   @param groupName jméno skupiny 
   */
  protected void remove(String cachedObjectKey, String groupName) {
    this.cache.remove(cachedObjectKey, groupName);
  }
  
  /**
   * Vloží objekt do dane cache skupiny.
   *   
   @param cachedObjectKey klíč objektu
   @param groupName jméno skupiny
   @param cachedObject objekt, který bude cacheován
   */
  protected void put(String cachedObjectKey, String groupName, Object cachedObject){
    try{
      this.cache.putInGroup(cachedObjectKey, groupName, cachedObject);
    }catch(CacheException e){
      throw new RuntimeException(e);
    }
  }
  
  /**
   * Vrací objekt z dané cache skupiny.
   @param cachedObjectKey klíč objektu
   @param groupName jméno skupiny
   @return cacheovaný objekt, pokud ještě není cacheováný pak <code>null</code>
   */
  protected Object get(String cachedObjectKey, String groupName) {
    return this.cache.getFromGroup(cachedObjectKey, groupName);
  }
}

Nyní je na čase v našem aspektu nadefinovat pointcaty, které nám umožní zavěšení na jednotlivé metody DAO objektu.

  /**
   * Pointcut, který zareaguje na každé volání veřejné metody <code>get*<code>
   * nebo <code>find*</code> na objektu <code>*Dao</code>, který leží v package 
   <code>cz.sweb.pichlik.aspectj.samples.dao</code>.
   <p>Pozn:
   * vzor <code>get*</code> je potencionálně nebezpečný, protože by se nám mohl
   * aplikovat i na gettry spadající do rodiny klasických accesoru, které nemají
   * nic společného s CRUD metodami. 
   */
  pointcut getExecs()
      call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.get*(..))
      || call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.find*(..));
  
  /**
   * Pointcut, který zareaguje na každé volání veřejné metody <code>update*<code>
   <code>dalete*</code> nebo <code>save*</code> na objektu <code>*Dao</code>
   * který leží v package <code>cz.sweb.pichlik.aspectj.samples.dao</code>.
   */
  pointcut deleteOrUpdateOrSaveExecs():
    call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.update(..))
    || call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.delete(..))
      || call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.save(..));

Pointcat má své symbolické jméno, na které se budeme moci odkázat v advice části (výkoná část) našeho aspektu. Pravidla pro zavěšení napíšeme na základě předpokladu, že naše DAO objekty leží v package cz.sweb.pichlik.aspectj.samples.dao a definuji veřejné metody.

Nadefinujeme si dva pointcaty, jeden pro čtecí operace a jeden pro CUD operací. Pro zachycení volání metod je použito klíčové slovo call. Pro určení významu operací použijeme obecné pravidlo, že čtecím operacím odpovídají metody get* a find* (hvězdička ja klasický zástupný symbol, který můžeme využít). CUD operacím odpovídají metody save, update a delete.

Nyní nám zbývá nadefinovat dvě advice, jednu pro čtecí operace a jednu pro CUD operace.

  /**
   * Advice, který obaluje metody pro získáni objektu. V případě, že objekt 
   * ještě není cacheován, původní metoda se zavolá a její výsledek se uloží
   * do cache. V případě, že je objekt cacheován, vrací se z cache.
   *  
   @return cacheovaný objekt
   */
  Object around(): getExecs() {
    String groupName = getGroupName(thisJoinPoint);
    String cachedObjectKey = getCachedObjectKey(thisJoinPoint);
    //zjistime jestli je objekt cacheovan
      Object result = get(cachedObjectKey, groupName)
      
      if(result == null) { //objekt neni v cache
        result = proceed();//zavolame puvodni metodu
        if(result != null) {
          put(cachedObjectKey, groupName, result);//vysledek ulozime do cache
        }
      else {
        System.out.println("Objekt:" + result + " vrácen z cache");
      }
      
      return result;
  }
  
  /**
   * Advice před vykonáním smazání, vytvoření nebo změny objektu, která invaliduje celou
   * cacheovanou skupinu.
   */
  before(): deleteOrUpdateOrSaveExecs() {
    String groupName = getGroupName(thisJoinPoint);
    invalidate(groupName);
  }

Advice má svůj typ např. around, before, after, after returning nebo after throwing. Typ určuje kdy bude advice aplikovaná z hlediska volání kódu. Tedy before před před provedením metody, after po provedení metody, after returning po provedení metody s klasickým návratem, after returning po provedení metody ukončené vyhozením výjimky a around vlastní metodu obaluje.

Součástí advice je také jméno pointcatu, případně pointcat výraz, který danou advice aktivuje. V případě CUD operací můžeme využít before advice a invalidovat celou cache skupinu. V případě operací pro čtení musíme použít around advice.

Around advice je specifická v tom, že máme kontrolu nad tím jestli se původní metoda provolá či nikoliv a toho využijeme. V případě, že je hodnota nacechovaná nebudeme původní metodu DAO objektu volat. Jinak necháme původni metodu DAO objektu zavolat pomocí klíčové klauzule proceed() a výsledek uložíme do cache.

V rámci každého aspektu máme přístup k instanční proměnné thisJoinPoint typu org.aspectj.lang.JoinPoint. Díky tomu dokážeme zjistit jméno cílové třídy, jméno volané metody, argumenty metody atd. To nám pomůže stanovit skupinu, ve které budeme cacheovat a klíč, pod kterým budeme ukládat/vybírat do/z cache.

  /**
   * Vrací jméno cacheovací skupiny. To je určeno na základě jména třídy.
   @param jp aktuální joinpoint
   */
  protected String getGroupName(JoinPoint jp){
    return jp.getStaticPart().getSignature().getDeclaringType().getName();
  }
  
  /**
   * Vrací klíč pro cacheovaný objekt. Klíč je vypočítán na základě jména metody, 
   * která se volá a hodnot, které jsou předány jako argumenty (parametry) 
   * metody.
   
   @param jp aktuální joinpoint
   */
  protected String getCachedObjectKey(JoinPoint jp){
    String methodName = jp.getStaticPart().getSignature().getName();
    StringBuilder key = new StringBuilder(methodName);
    //z hodnot argumentu volane metody vytvorime klic cacheovaneho objektu
    Object args[] = jp.getArgs()
    for (Object arg: args) {
      if(arg != null) {
        key.append(arg.hashCode());
      }
    }
    return key.toString();
  }

Celý kód aspektu.

/**
 * Aspekt, který slouží jako cache pro DAO vrstvu. Vrácený objekt je cacheován v 
 * případě, že je vrácen z DAO vrstvy (leží v package <code>cz.sweb.pichlik.aspectj.samples.dao</code> 
 * má v názvu suffix <code>Dao</code>) metodou začínající na <tt>find</tt>
 * a nebo <tt>get</tt>.
 
 <p>Z hlediska klienta DAO vrstvy se vše chová transparentně, klient nepozná 
 * jestli došlo k dotazu na databázi a nebo jestli je objekt vrácen z cache. Jako
 * cacheovací framework byl použit <a href="http://jakarta.apache.org/jcs/index.html">Java Caching System</a>.  
 */
public aspect CacheAspect {  
  private JCS cache; //instance cache manageru
  
  /**
   * Konstruktor aspektu, který inicializuje cache.
   
   @see CacheAspect#initCache()
   */
  public CacheAspect() {
    initCache();
  }  
  
  /**
   * Pointcut, který zareaguje na každé volání veřejné metody <code>get*<code>
   * nebo <code>find*</code> na objektu <code>*Dao</code>, který leží v package 
   <code>cz.sweb.pichlik.aspectj.samples.dao</code>.
   <p>Pozn:
   * vzor <code>get*</code> je potencionálně nebezpečný, protože by se nám mohl
   * aplikovat i na gettry spadající do rodiny klasických accesoru, které nemají
   * nic společného s CRUD metodami. 
   */
  pointcut getExecs()
      call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.get*(..))
      || call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.find*(..));
  
  /**
   * Pointcut, který zareaguje na každé volání veřejné metody <code>update*<code>
   <code>dalete*</code> nebo <code>save*</code> na objektu <code>*Dao</code>
   * který leží v package <code>cz.sweb.pichlik.aspectj.samples.dao</code>.
   */
  pointcut deleteOrUpdateOrSaveExecs():
    call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.update(..))
    || call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.delete(..))
      || call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.save(..));
  
  
  /**
   * Advice, který obaluje metody pro získáni objektu. V případě, že objekt 
   * ještě není cacheován, původní metoda se zavolá a její výsledek se uloží
   * do cache. V případě, že je objekt cacheován, vrací se z cache.
   *  
   @return cacheovaný objekt
   */
  Object around(): getExecs() {
    String groupName = getGroupName(thisJoinPoint);
    String cachedObjectKey = getCachedObjectKey(thisJoinPoint);
    //zjistime jestli je objekt cacheovan
      Object result = get(cachedObjectKey, groupName)
      
      if(result == null) { //objekt neni v cache
        result = proceed();//zavolame puvodni metodu
        if(result != null) {
          put(cachedObjectKey, groupName, result);//vysledek ulozime do cache
        }
      else {
        System.out.println("Objekt:" + result + " vrácen z cache");
      }
      
      return result;
  }
  
  /**
   * Advice před vykonáním smazání, vytvoření nebo změny objektu, která invaliduje celou
   * cacheovanou skupinu.
   */
  before(): deleteOrUpdateOrSaveExecs() {
    String groupName = getGroupName(thisJoinPoint);
    invalidate(groupName);
  }
  
  /**
   * Vrací jméno cacheovací skupiny. To je určeno na základě jména třídy.
   @param jp aktuální joinpoint
   */
  protected String getGroupName(JoinPoint jp){
    return jp.getStaticPart().getSignature().getDeclaringType().getName();
  }
  
  /**
   * Vrací klíč pro cacheovaný objekt. Klíč je vypočítán na základě jména metody, 
   * která se volá a hodnot, které jsou předány jako argumenty (parametry) 
   * metody.
   
   @param jp aktuální joinpoint
   */
  protected String getCachedObjectKey(JoinPoint jp){
    String methodName = jp.getStaticPart().getSignature().getName();
    StringBuilder key = new StringBuilder(methodName);
    //z hodnot argumentu volane metody vytvorime klic cacheovaneho objektu
    Object args[] = jp.getArgs()
    for (Object arg: args) {
      if(arg != null) {
        key.append(arg.hashCode());
      }
    }
    return key.toString();
  }
  
  //-----------------------------------------//
  //    Definice metod pro praci s cache     //
    //-----------------------------------------//
  
  /**
   * Provede inicializaci cache. Metoda je volána pouze jednou při vytvoření
   * aspektu. 
   */
  protected void initCache(){
    try{
      this.cache = JCS.getInstance("");//získáme defaultní cache
    catch (CacheException e){
      throw new RuntimeException(e);
    }
  }
    
  /**
   * Invaliduje všechny cacheované objekty v dané skupině.
   @param groupName jméno skupiny
   */
  protected void invalidate(String groupName){
    this.cache.invalidateGroup(groupName);
  }
  
  /**
   * Odstraní objekt z dané cacheovací skupiny.
   @param cachedObjectKey klíč objektu, který bude odstraněn 
   @param groupName jméno skupiny 
   */
  protected void remove(String cachedObjectKey, String groupName) {
    this.cache.remove(cachedObjectKey, groupName);
  }
  
  /**
   * Vloží objekt do dane cache skupiny.
   *   
   @param cachedObjectKey klíč objektu
   @param groupName jméno skupiny
   @param cachedObject objekt, který bude cacheován
   */
  protected void put(String cachedObjectKey, String groupName, Object cachedObject){
    try{
      this.cache.putInGroup(cachedObjectKey, groupName, cachedObject);
    }catch(CacheException e){
      throw new RuntimeException(e);
    }
  }
  
  /**
   * Vrací objekt z dané cache skupiny.
   @param cachedObjectKey klíč objektu
   @param groupName jméno skupiny
   @return cacheovaný objekt, pokud ještě není cacheováný pak <code>null</code>
   */
  protected Object get(String cachedObjectKey, String groupName) {
    return this.cache.getFromGroup(cachedObjectKey, groupName);
  }
}

Pro ověření funkčnosti, si vytvoříme jednoduchý kód, který bude volat náš DAO objekt.

public static void main(String[] args) {
    FooDao dao = new FooDao();
    FooBean bean = new FooBean(1);
    dao.save(bean);
    dao.findById(1)//puvodni metoda
    dao.findById(1)//cache
    dao.findById(1)//cache
    dao.update(bean);//invalidace cache
    dao.findById(1)//puvodni metoda
    dao.findById(1)//cache
    dao.delete(bean);//invalidace
    dao.save(bean);  //invalidace
    dao.findById(1)//puvodni metoda
    dao.findById(1)//cache
    dao.save(bean);  //invalidace
    dao.getAll();    //puvodni metoda
    dao.getAll();    //cache
    dao.delete(bean);//invalidace
    dao.getAll();    //původní metoda
  }

Abychom si nemuseli zatahovat logovací kód do DAO objektu, uděláme si ještě jeden aspekt, tentokrát logovací. Rozdíl oproti cacheovacímu aspektu je v tom, že pointcat je přímo součástí advice.

/**
 * Logovací aspekt (no spíš trasovací ;-), který slouží k logování událostí DAO vrstvy. Narozdíl od <code>
 * CacheAspectu</code> je <tt>pointcut</tt> nadefinován přímo v rámci <tt>advice</tt>
 * a používá konstruktů <code>within</code> <code>execution</code>. Rozdíl mezi
 <code>execution</code> <code>call</call> je z hlediska toho jestli se zavěšení 
 * provede na volajícím nebo volaném objektu. V případě <code>execution</code> 
 * se zavěšení provede na volaném objektu, oproti <code>call</call> kde je to 
 * na volajícím.   
 <p>
 <code>thisJoinPoint</code> ({@link JoinPoint}) a <code>thisJoinPointStaticPart</code> 
 {@link JoinPoint.StaticPart} jsou implicitní objekty každého aspektu.
 */
public aspect LoggingAspect {
  
  before() : within(cz.sweb.pichlik.aspectj.samples.dao.*Dao&&  execution(public * *(..)){                  
    String clazzName  = 
      thisJoinPoint.getStaticPart().getSignature().getDeclaringType().getName();
    String methodName = 
      thisJoinPointStaticPart.getSignature().getName();
    
    Object args[] = thisJoinPoint.getArgs();
    StringBuilder sb = new StringBuilder("Volána metoda: ");
    sb.append(clazzName);
    sb.append(".");
    sb.append(methodName);
    sb.append("(");
    for (int i = 0; i < args.length; i++) {
      if(i != 0){
        sb.append(",");
      }
      sb.append(args[i]);  
    }
    sb.append(")");
    System.out.println(sb.toString());
  }
}

Taky jsme použili execution místo call (vysvětlení viz. javadoc logovacího aspektu) a within pro určení nad jakým objektem se má logovat.

Nyní už zbývá pouze rozkrýt výstup jenž se objeví při spuštění programu.


Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.save(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.findById(1)
Objekt:cz.sweb.pichlik.aspectj.samples.FooBean@20 vrácen z cache
Objekt:cz.sweb.pichlik.aspectj.samples.FooBean@20 vrácen z cache
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.update(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.findById(1)
Objekt:cz.sweb.pichlik.aspectj.samples.FooBean@20 vrácen z cache
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.delete(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.save(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.findById(1)
Objekt:cz.sweb.pichlik.aspectj.samples.FooBean@20 vrácen z cache
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.save(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.getAll()
Objekt:[cz.sweb.pichlik.aspectj.samples.FooBean@20] vrácen z cache
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.delete(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.getAll()    
    

Aspekty nabízejí další rozměr programování. V tomto článku jsme se dotkli jenom některých možností aspektu. Pokud Vás AOP zaujalo doporučuji nakouknout do dokumentace AspectJ, protože obsahuje velké množství užitečných informací. Na závěr přidávám ještě popis AJDT Eclipse pluginu.

AJDT

Plugin AJDT (update adresa pro Eclipse 3.2.x http://download.eclipse.org/tools/ajdt/32/update) do vývojového prostředí Eclipse nabízí perspektivu Aspect visualization. V ní je graficky vidět aplikování jednotlivých aspektů.

Aspect visualization perspektiva
Plná velikost (cca. 125KB)

Většinu času ovšem stejně stráví člověk při psaní kódu v klasické Java perspektivě. Tu AJDT rozšiřuje na úrovni view Outline, to je krásně vidět na následujícím obrázku v pravo nahoře. Modré blesky ozančují pointcaty a šipky podle typu jednotlivé advice.

Java perspektiva, rozšířené view Outline při editaci aspektu.
Plná velikost (cca. 142KB)

Na následujícím obrázku je opět Java perspektiva, tentokrát při editaci kódu, na který se aplikuje advice. Při najetí na advice symbol (šipky označují typ advice - before, around) je vidět jaká advice, v jakém aspektu.

Java perspektiva při editaci kódu, který je advicnutý.
Plná velikost (cca. 139KB)

Aspekty jdou debugovat jako jakýkolv jiný kód.

Debug perspektiva při ladění aspektu.
Plná velikost (cca. 125KB)

Součástí každého API by měla být jeho dokumentace. Přes Project >> Generate AJdoc... lze vygenerovat obohacený JavaDoc o informace o aspektech a jejich aplikování.

AJDoc - JavaDoc obohacený informacemi o aspektech.
Plná velikost (cca. 54KB)