Programovanie

Pridajte do svojej aplikácie dynamický kód Java

JavaServer Pages (JSP) je pružnejšia technológia ako servlety, pretože dokáže reagovať za dynamických zmien za behu. Viete si predstaviť bežnú triedu Java, ktorá má tiež túto dynamickú schopnosť? Bolo by zaujímavé, keby ste mohli upraviť implementáciu služby bez jej opätovného nasadenia a priebežne aktualizovať svoju aplikáciu.

V článku sa vysvetľuje, ako písať dynamický kód Java. Rozoberá kompiláciu zdrojového kódu za behu, opätovné načítanie triedy a použitie návrhového vzoru Proxy na vykonanie úprav dynamickej triedy transparentných pre jej volajúceho.

Príklad dynamického kódu Java

Začnime príkladom dynamického kódu Java, ktorý ilustruje, čo znamená skutočný dynamický kód, a tiež poskytuje kontext pre ďalšie diskusie. Kompletný zdrojový kód tohto príkladu nájdete v zdrojoch.

Príkladom je jednoduchá aplikácia Java, ktorá závisí od služby s názvom Poštár. Služba Poštár je opísaná ako rozhranie Java a obsahuje iba jednu metódu, deliverMessage ():

verejné rozhranie Postman {void deliverMessage (String msg); } 

Jednoduchá implementácia tejto služby vytlačí správy do konzoly. Triedou implementácie je dynamický kód. Táto trieda, PostmanImpl, je len normálna trieda Java, s výnimkou toho, že sa namiesto kompilovaného binárneho kódu nasadzuje so svojím zdrojovým kódom:

verejná trieda PostmanImpl implementuje Postman {

súkromný výstup PrintStream; public PostmanImpl () {output = System.out; } public void deliverMessage (textová správa) {output.println ("[poštár]" + správa); output.flush (); }}

Aplikácia, ktorá využíva službu Poštár, sa zobrazuje nižšie. V hlavný() metóda, nekonečná slučka načíta reťazcové správy z príkazového riadku a doručí ich prostredníctvom služby Poštár:

verejná trieda PostmanApp {

public static void main (String [] args) vyvolá Výnimku {BufferedReader sysin = nový BufferedReader (nový InputStreamReader (System.in));

// Získanie inštancie Postman Postman postman = getPostman ();

while (true) {System.out.print ("Zadajte správu:"); Reťazec msg = sysin.readLine (); postman.deliverMessage (správa); }}

súkromný statický poštár getPostman () {// Vynechať zatiaľ, vráti sa neskôr}}

Spustite aplikáciu, zadajte niektoré správy a v konzole sa vám zobrazia výstupy, ako sú nasledujúce (príklad si môžete stiahnuť a spustiť sami):

[DynaCode] Vzorka triedy Init.PostmanImpl Zadajte správu: ahoj svet [Poštár] ahoj svet Zadaj správu: aký pekný deň! [Poštár], pekný deň! Zadajte správu: 

Všetko je jednoduché až na prvý riadok, ktorý naznačuje, že trieda PostmanImpl je zostavený a načítaný.

Teraz sme pripravení vidieť niečo dynamické. Bez zastavenia aplikácie poďme upraviť PostmanImplzdrojový kód. Nová implementácia doručuje všetky správy do textového súboru namiesto do konzoly:

// MODIFIKOVANÁ VERZIA verejná trieda PostmanImpl implementuje Postman {

súkromný výstup PrintStream; // Začiatok úpravy public PostmanImpl () vyvolá IOException {output = new PrintStream (new FileOutputStream ("msg.txt")); } // Koniec úprav

public void deliverMessage (textová správa) {output.println ("[poštár]" + správa);

output.flush (); }}

Posuňte sa späť do aplikácie a zadajte ďalšie správy. Čo sa bude diať? Áno, správy teraz prechádzajú do textového súboru. Pozrite sa na konzolu:

[DynaCode] Vzorka triedy Init.PostmanImpl Zadajte správu: ahoj svet [Poštár] ahoj svet Zadaj správu: aký pekný deň! [Poštár], pekný deň! Zadajte správu: Chcem prejsť do textového súboru. [DynaCode] Ukážka triedy Init.PostmanImpl Zadajte správu: aj ja! Zadajte správu: 

Všimnite si [DynaCode] Ukážka triedy Init.PostmanImpl sa opäť objaví, čo naznačuje, že trieda PostmanImpl je prekompilovaný a znovu načítaný. Ak skontrolujete textový súbor msg.txt (v pracovnom adresári), uvidíte nasledujúce:

[Poštár] Chcem ísť do textového súboru. [Poštár] aj ja! 

Úžasné, že? Službu Poštár sme schopní aktualizovať za behu programu a zmena je pre aplikáciu úplne transparentná. (Všimnite si, že aplikácia používa rovnakú inštanciu Postmana na prístup k obom verziám implementácií.)

Štyri kroky k dynamickému kódu

Dovoľte mi prezradiť, čo sa deje v zákulisí. V zásade existujú štyri kroky, ktoré majú zabezpečiť, aby bol kód Java dynamický:

  • Nasadte vybraný zdrojový kód a sledujte zmeny súborov
  • Zostavte kód Java za behu programu
  • Načítanie / opätovné načítanie triedy Java za behu
  • Prepojte aktuálnu triedu s jej volajúcim

Nasadte vybraný zdrojový kód a sledujte zmeny súborov

Aby sme mohli začať písať nejaký dynamický kód, prvá otázka, ktorú si musíme zodpovedať, je: „Ktorá časť kódu by mala byť dynamická - celá aplikácia alebo iba niektoré z tried?“ Technicky existuje niekoľko obmedzení. Za behu môžete načítať / načítať ľubovoľnú triedu Java. Vo väčšine prípadov však túto úroveň flexibility vyžaduje iba časť kódu.

Príklad Postmana demonštruje typický vzor pri výbere dynamických tried. Bez ohľadu na to, ako je systém zostavený, nakoniec budú existovať stavebné prvky, ako sú služby, subsystémy a komponenty. Tieto stavebné bloky sú relatívne nezávislé a navzájom si vystavujú svoje funkcie prostredníctvom preddefinovaných rozhraní. Za rozhraním je implementácia, ktorú je možné meniť, pokiaľ je v súlade so zmluvou definovanou rozhraním. Presne túto kvalitu potrebujeme pre dynamické triedy. Takže jednoducho povedané: Vyberte triedu implementácie, ktorá bude dynamickou triedou.

Vo zvyšku článku urobíme nasledujúce predpoklady týkajúce sa vybraných dynamických tried:

  • Zvolená dynamická trieda implementuje niektoré rozhranie Java na odhalenie funkčnosti
  • Implementácia vybranej dynamickej triedy neobsahuje žiadne stavové informácie o klientovi (podobne ako fazuľa bezstavovej relácie), takže inštancie dynamickej triedy sa môžu navzájom nahrádzať.

Upozorňujeme, že tieto predpoklady nie sú nevyhnutnými predpokladmi. Existujú iba preto, aby bola realizácia dynamického kódu o niečo jednoduchšia, aby sme sa mohli viac sústrediť na nápady a mechanizmy.

S ohľadom na vybrané dynamické triedy je nasadenie zdrojového kódu ľahká úloha. Obrázok 1 zobrazuje štruktúru súboru príkladu Postmana.

Vieme, že „src“ je zdroj a „bin“ je binárny súbor. Jedna vec, ktorú stojí za zmienku, je adresár dynacode, ktorý obsahuje zdrojové súbory dynamických tried. Tu v príklade je iba jeden súbor - PostmanImpl.java. Na spustenie aplikácie sú potrebné adresáre bin a dynacode, zatiaľ čo src nie je na nasadenie potrebný.

Zistenie zmien súboru je možné dosiahnuť porovnaním časových pečiatok úprav a veľkostí súborov. V našom príklade sa kontrola súboru PostmanImpl.java vykonáva zakaždým, keď sa na serveri vyvolá metóda Poštár rozhranie. Alternatívne môžete na pozadí plodiť vlákno démona, ktoré pravidelne kontroluje zmeny súborov. To môže mať za následok lepší výkon pre rozsiahle aplikácie.

Zostavte kód Java za behu programu

Po zistení zmeny zdrojového kódu prichádzame k problému s kompiláciou. Delegovaním skutočnej úlohy na existujúci kompilátor Java môže byť runtime kompilácia hračkou. Mnoho kompilátorov Java je k dispozícii na použitie, ale v tomto článku používame kompilátor Javac zahrnutý v platforme Sun Java Standard Edition (Java SE je nový názov spoločnosti Sun pre J2SE).

Minimálne môžete zostaviť súbor Java pomocou jedného príkazu za predpokladu, že súbor toolss.jar, ktorý obsahuje kompilátor Javac, je na ceste triedy (súbory tools.jar nájdete v priečinku / lib /):

 int errorCode = com.sun.tools.javac.Main.compile (nový reťazec [] {"-classpath", "bin", "-d", "/ temp / dynacode_classes", "dynacode / sample / PostmanImpl.java" }); 

Trieda com.sun.tools.javac.Hlavne je programovacie rozhranie kompilátora Javac. Poskytuje statické metódy na kompiláciu zdrojových súborov Java. Vykonanie vyššie uvedeného príkazu má rovnaký efekt ako spustenie javac z príkazového riadku s rovnakými argumentmi. Kompiluje zdrojový súbor dynacode / sample / PostmanImpl.java pomocou zadaného priečinka triedy classpath a výstupný súbor svojej triedy odošle do cieľového adresára / temp / dynacode_classes. Celé číslo sa vráti ako kód chyby. Nula znamená úspech; akékoľvek iné číslo naznačuje, že sa niečo pokazilo.

The com.sun.tools.javac.Hlavne triedy poskytuje aj ďalší zostaviť () metóda, ktorá akceptuje ďalšie PrintWriter ako je uvedené v kóde nižšie. Podrobné chybové správy budú napísané na server PrintWriter ak kompilácia zlyhá.

 // Definované v com.sun.tools.javac.Hlavná verejná statická int kompilácia (String [] args); verejný statický int kompilát (String [] args, PrintWriter out); 

Predpokladám, že väčšina vývojárov je oboznámená s kompilátorom Javac, takže sa tu zastavím. Viac informácií o tom, ako používať prekladač, nájdete v Zdrojoch.

Načítanie / opätovné načítanie triedy Java za behu

Zkompilovaná trieda musí byť načítaná skôr, ako sa prejaví. Java je flexibilná pri načítavaní tried. Definuje komplexný mechanizmus načítania tried a poskytuje niekoľko implementácií Classloaderov. (Ďalšie informácie o načítaní triedy nájdete v časti Zdroje.)

Ukážkový kód uvedený nižšie ukazuje, ako načítať a znovu načítať triedu. Základnou myšlienkou je načítať dynamickú triedu pomocou našej vlastnej URLClassLoader. Kedykoľvek sa zmení a prekompiluje zdrojový súbor, vyradíme starú triedu (pre neskoršie odvoz odpadu) a vytvoríme novú URLClassLoader triedu znova načítať.

// Adresár obsahuje skompilované triedy. File classesDir = new File ("/ temp / dynacode_classes /");

// Nadradený triedny nakladač ClassLoader parentLoader = Postman.class.getClassLoader ();

// Trieda načítania „sample.PostmanImpl“ pomocou nášho vlastného načítavača tried. URLClassLoader loader1 = nový URLClassLoader (nová URL [] {classesDir.toURL ()}, parentLoader); Trieda cls1 = loader1.loadClass ("sample.PostmanImpl"); Poštár poštár1 = (Poštár) cls1.newInstance ();

/ * * Vyvolať na postman1 ... * Potom sa upraví a prekompiluje PostmanImpl.java. * /

// Znovu načítať triedu „sample.PostmanImpl“ pomocou nového načítavača tried. URLClassLoader loader2 = nový URLClassLoader (nová URL [] {classesDir.toURL ()}, parentLoader); Trieda cls2 = loader2.loadClass ("sample.PostmanImpl"); Poštár poštár2 = (Poštár) cls2.newInstance ();

/ * * Odteraz pracujte s postman2 ... * Nerobte si starosti s loader1, cls1 a postman1 *, budú automaticky zhromažďované odpadky. * /

Venujte pozornosť parentLoader pri vytváraní vlastného triediča. V zásade platí pravidlo, že nadradený zavádzač musí poskytovať všetky závislosti, ktoré podriadený ukladač vyžaduje. Takže vo vzorovom kóde dynamická trieda PostmanImpl záleží na rozhraní Poštár; preto používame Poštártrieda ako rodičovský trieda.

Sme ešte jeden krok od dokončenia dynamického kódu. Pripomeňme si predtým predstavený príklad. Dynamické načítanie triedy je tam pre volajúceho transparentné. Ale vo vyššie uvedenom ukážkovom kóde musíme ešte zmeniť inštanciu služby z poštár1 do poštár2 keď sa kód zmení. Štvrtý a posledný krok odstráni potrebu tejto manuálnej zmeny.

Prepojte aktuálnu triedu s jej volajúcim

Ako získate prístup k aktuálnej dynamickej triede so statickým odkazom? Zdá sa, že priamy (normálny) odkaz na objekt dynamickej triedy tento trik neurobí. Potrebujeme niečo medzi klientom a dynamickou triedou - proxy. (Pozri slávnu knihu Dizajnové vzory pre viac informácií o vzore Proxy.)

V tomto prípade je proxy trieda fungujúca ako prístupové rozhranie dynamickej triedy. Klient nevyvoláva dynamickú triedu priamo; namiesto toho robí proxy. Proxy server potom preposiela vyvolania do dynamickej triedy back-endu. Obrázok 2 zobrazuje spoluprácu.

Pri opätovnom načítaní dynamickej triedy stačí aktualizovať prepojenie medzi proxy a dynamickou triedou a klient naďalej používa na prístup k znovu načítanej triede rovnakú inštanciu proxy. Obrázok 3 zobrazuje spoluprácu.

Týmto spôsobom sa zmeny dynamickej triedy stanú pre volajúceho transparentnými.

Rozhranie Java Reflection API obsahuje užitočný nástroj na vytváranie serverov proxy. Trieda java.lang.reflect.Proxy poskytuje statické metódy, ktoré vám umožňujú vytvárať inštancie proxy servera pre ľubovoľné rozhranie Java.

Ukážkový kód uvedený nižšie vytvára server proxy pre rozhranie Poštár. (Ak nie ste oboznámení s java.lang.reflect.Proxy, prosím, predtým si pozrite Javadoc.)

 Obslužný program InvocationHandler = nový DynaCodeInvocationHandler (...); Poštár proxy = (Poštár) Proxy.newProxyInstance (Postman.class.getClassLoader (), nová trieda [] {Postman.class}, obsluha); 

Vrátený splnomocnenec je objekt anonymnej triedy, ktorá zdieľa rovnaký triedny nakladač s Poštár rozhranie ( newProxyInstance () prvý parameter metódy) a implementuje Poštár rozhranie (druhý parameter). Metóda vyvolania na splnomocnenec inštancia je odoslaná do psovodje vzývať() metóda (tretí parameter). A psovodImplementácia môže vyzerať takto:

$config[zx-auto] not found$config[zx-overlay] not found