Programiranje

V svojo aplikacijo dodajte dinamično kodo Java

JavaServer Pages (JSP) je bolj prilagodljiva tehnologija kot strežniški programčki, saj se lahko med izvajanjem odziva na dinamične spremembe. Si predstavljate skupni razred Java, ki ima tudi to dinamično sposobnost? Zanimivo bi bilo, če bi lahko spremenili izvajanje storitve, ne da bi jo prerazporedili, in sproti posodabljali svojo aplikacijo.

V članku je razloženo, kako napisati dinamično kodo Java. Govori o prevajanju izvorne kode med izvajanjem, ponovnem nalaganju razredov in uporabi vzorca načrtovanja proxy za spreminjanje dinamičnega razreda, ki je pregleden za klicatelja.

Primer dinamične kode Java

Začnimo s primerom dinamične kode Java, ki ponazarja, kaj resnična dinamična koda pomeni, in ponuja tudi nekaj konteksta za nadaljnje razprave. Poglejte celotno izvorno kodo tega primera v virih.

Primer je preprosta aplikacija Java, ki je odvisna od storitve, imenovane Postman. Storitev Poštar je opisana kot vmesnik Java in vsebuje samo en način, dostaviti sporočilo ():

javni vmesnik Postman {void dostavMessage (String msg); } 

Preprosta izvedba te storitve natisne sporočila na konzolo. Razred izvajanja je dinamična koda. Ta razred, PoštarImpl, je le običajen razred Java, le da namesto zbrane binarne kode uporabi svojo izvorno kodo:

javni razred PostmanImpl izvaja Postman {

zasebni izhod PrintStream; public PostmanImpl () {output = System.out; } public void dostavMessage (String msg) {output.println ("[Poštar]" + sporočilo); output.flush (); }}

Aplikacija, ki uporablja storitev Poštar, je prikazana spodaj. V glavni () metoda, neskončna zanka prebere niz sporočil iz ukazne vrstice in jih dostavi prek storitve Poštar:

javni razred PostmanApp {

javna statična void main (String [] args) vrže izjemo {BufferedReader sysin = new BufferedReader (new InputStreamReader (System.in));

// Pridobitev primerka poštarja poštar poštar = getPostman ();

while (true) {System.out.print ("Enter a message:"); Niz msg = sysin.readLine (); postman.deliverMessage (msg); }}

zasebni statični poštar getPostman () {// Za zdaj izpustite, vrnil se bo pozneje}}

Izvedite aplikacijo, vnesite nekaj sporočil in v konzoli boste videli izhode, kot so naslednji (primer lahko prenesete in zaženete sami):

[DynaCode] Init class sample.PostmanImpl Vnesite sporočilo: hello world [Postman] hello world Vnesite sporočilo: kakšen lep dan! [Poštar] kako lep dan! Vnesite sporočilo: 

Vse je preprosto, razen prve vrstice, ki označuje, da je razred PoštarImpl je sestavljen in naložen.

Zdaj smo pripravljeni videti nekaj dinamičnega. Ne da bi ustavili aplikacijo, jo spremenimo PoštarImplizvorna koda. Nova izvedba namesto v konzolo dostavi vsa sporočila v besedilno datoteko:

// SPREMENJENA RAZLIČICA javni razred PostmanImpl izvaja Postman {

zasebni izhod PrintStream; // Začetek spremembe public PostmanImpl () vrže IOException {output = new PrintStream (new FileOutputStream ("msg.txt")); } // Konec spremembe

javna void DeliveryMessage (niz sporočila) {output.println ("[Poštar]" + sporočilo);

output.flush (); }}

Vrnite se na aplikacijo in vnesite več sporočil. Kaj se bo zgodilo? Da, sporočila gredo zdaj v besedilno datoteko. Poglejte konzolo:

[DynaCode] Init class sample.PostmanImpl Vnesite sporočilo: hello world [Postman] hello world Vnesite sporočilo: kakšen lep dan! [Poštar] kako lep dan! Vnesite sporočilo: Želim odpreti besedilno datoteko. [DynaCode] Vstavi vzorec razreda.PostmanImpl Vnesite sporočilo: tudi jaz! Vnesite sporočilo: 

Opaziti [DynaCode] Vzori vzorec razreda.PostmanImpl se ponovno prikaže, kar pomeni, da je razred PoštarImpl se prevede in ponovno naloži. Če preverite besedilno datoteko msg.txt (v delovnem imeniku), boste videli naslednje:

[Poštar] Rad bi šel do besedilne datoteke. [Poštar] tudi jaz! 

Neverjetno, kajne? Storitev Postman lahko posodobimo med izvajanjem, aplikacija pa je popolnoma pregledna. (Upoštevajte, da aplikacija uporablja isti primerek Poštarja za dostop do obeh različic izvedb.)

Štirje koraki do dinamične kode

Naj razkrijem, kaj se dogaja v zakulisju. V bistvu obstajajo štirje koraki za dinamično kodo Java:

  • Razporedite izbrano izvorno kodo in spremljajte spremembe datotek
  • Prevedite kodo Java med izvajanjem
  • Naložite / znova naložite razred Java med izvajanjem
  • Povežite posodobljeni razred s klicateljem

Razporedite izbrano izvorno kodo in spremljajte spremembe datotek

Za začetek pisanja neke dinamične kode je prvo vprašanje, na katero moramo odgovoriti, "Kateri del kode mora biti dinamičen - celotna aplikacija ali samo nekateri razredi?" Tehnično je malo omejitev. Med izvajanjem lahko naložite / znova naložite kateri koli razred Java. Toda v večini primerov le del kode potrebuje to stopnjo prilagodljivosti.

Primer Poštar prikazuje tipičen vzorec pri izbiri dinamičnih razredov. Ne glede na to, kako je sestavljen sistem, bodo na koncu gradniki, kot so storitve, podsistemi in komponente. Ti gradniki so relativno neodvisni in si prek vnaprej določenih vmesnikov izpostavljajo funkcionalnosti. Za vmesnikom je izvedbo, ki jo lahko spreminjate, če je v skladu s pogodbo, ki jo določa vmesnik. To je točno ta kakovost, ki jo potrebujemo za dinamične razrede. Torej preprosto: Izberite izvedbeni razred, ki bo dinamični.

V preostalem delu članka bomo podali naslednje predpostavke o izbranih dinamičnih razredih:

  • Izbrani dinamični razred implementira nekaj Java vmesnika za izpostavljanje funkcionalnosti
  • Izvedba izbranega dinamičnega razreda ne vsebuje nobenih informacij o stanju njegovega odjemalca (podobno kot seja brez sestave brez stanja), zato lahko primerki dinamičnega razreda medsebojno nadomeščajo

Upoštevajte, da te predpostavke niso pogoj. Obstajajo samo zato, da je uresničitev dinamične kode nekoliko lažja, da se lahko bolj osredotočimo na ideje in mehanizme.

Z mislijo na izbrane dinamične razrede je uvajanje izvorne kode enostavna naloga. Slika 1 prikazuje datotečno strukturo primera poštarja.

Vemo, da je "src" izvorni, "bin" pa binarni. Treba je omeniti imenik dynacode, ki vsebuje izvorne datoteke dinamičnih razredov. Tu je v primeru le ena datoteka - PostmanImpl.java. Imenika bin in dynacode sta potrebna za zagon aplikacije, medtem ko src za uvajanje ni potreben.

Zaznavanje sprememb datotek je mogoče doseči s primerjavo časovnih žigov sprememb in velikosti datotek. V našem primeru se preverjanje PostmanImpl.java izvede vsakič, ko se metoda pokliče na Poštar vmesnik. Lahko pa tudi ustvarite nit demona v ozadju, da redno preverjate spremembe datotek. To lahko privede do boljše zmogljivosti za obsežne aplikacije.

Prevedite kodo Java med izvajanjem

Ko zaznamo spremembo izvorne kode, pridemo do težave s prevajanjem. Z delegiranjem pravega opravila obstoječemu prevajalniku Java je lahko izvajalno kompiranje pika na i. Za uporabo je na voljo veliko prevajalnikov Java, vendar v tem članku uporabljamo prevajalnik Javac, vključen v Sun-ovo platformo Java, Standard Edition (Java SE je novo ime Sun-a za J2SE).

Datoteko Java lahko sestavite vsaj z enim stavkom, pod pogojem, da je tools.jar, ki vsebuje prevajalnik Javac, na poti do razreda (tools.jar najdete pod / lib /):

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

Razred com.sun.tools.javac.Main je programski vmesnik prevajalnika Javac. Zagotavlja statične metode za prevajanje izvornih datotek Java. Izvajanje zgornje izjave ima enak učinek kot zagon javac iz ukazne vrstice z enakimi argumenti. Izvirno datoteko prevede dynacode / sample / PostmanImpl.java z uporabo določenega koša za razred razredov in svojo datoteko razreda prikaže v ciljni imenik / temp / dynacode_classes. Celo število se vrne kot koda napake. Zero pomeni uspeh; katera koli druga številka pomeni, da je šlo kaj narobe.

The com.sun.tools.javac.Main razred ponuja tudi drugo prevajanje () metoda, ki sprejme dodatno PrintWriter parameter, kot je prikazano v spodnji kodi. Podrobna sporočila o napakah bodo zapisana v PrintWriter če kompilacija ne uspe.

 // Določeno v com.sun.tools.javac.Main public static int compile (String [] args); javno statično prevajanje int (String [] args, PrintWriter out); 

Predvidevam, da večina razvijalcev pozna prevajalnik Javac, zato se bom ustavil tukaj. Za več informacij o uporabi prevajalnika glejte Viri.

Naložite / znova naložite razred Java med izvajanjem

Prevedeni razred je treba naložiti, preden začne veljati. Java je prilagodljiva glede nalaganja razredov. Določa celovit mehanizem za nalaganje razredov in ponuja več izvedb nalagalnikov razredov. (Za več informacij o nalaganju predavanj glejte Viri.)

Spodnja vzorčna koda prikazuje, kako naložiti in znova naložiti razred. Osnovna ideja je naložiti dinamični razred z uporabo našega URLClassLoader. Kadarkoli se izvorna datoteka spremeni in prevede, stari razred zavržemo (za kasnejše zbiranje smeti) in ustvarimo novega URLClassLoader za ponovno nalaganje razreda.

// Dir vsebuje zbrane razrede. Datotečni razrediDir = nova datoteka ("/ temp / dynacode_classes /");

// Nadrejeni učitelj razredov ClassLoader parentLoader = Postman.class.getClassLoader ();

// Naložimo razred "sample.PostmanImpl" z lastnim naložiteljem razredov. URLClassLoader loader1 = nov URLClassLoader (nov URL [] {classesDir.toURL ()}, nadrejeniLoader); Razred cls1 = loader1.loadClass ("sample.PostmanImpl"); Poštar poštar1 = (Poštar) cls1.newInstance ();

/ * * Prikliči na postman1 ... * Nato se PostmanImpl.java spremeni in ponovno prevede. * /

// Znova naložite razred "sample.PostmanImpl" z novim naložiteljem razredov. URLClassLoader loader2 = nov URLClassLoader (nov URL [] {classesDir.toURL ()}, nadrejeniLoader); Razred cls2 = loader2.loadClass ("sample.PostmanImpl"); Poštar poštar2 = (Poštar) cls2.newInstance ();

/ * * Od zdaj naprej sodelujte s poštarjem ... * Ne skrbite za loader1, cls1 in poštar1 *, smeti se bodo zbirali samodejno. * /

Bodite pozorni na nadrejeniLoader pri ustvarjanju lastnega učitelja. V bistvu velja pravilo, da mora nadrejeni nalagalnik razredov zagotoviti vse odvisnosti, ki jih zahteva nadrejeni učitelj nalaganja učiteljev. V vzorčni kodi je torej dinamični razred PoštarImpl odvisno od vmesnika Poštar; zato uporabljamo Poštarje nalagalnik razredov kot nadrejeni nalagalnik razredov.

Do dokončanja dinamične kode je še en korak. Spomnimo se prej predstavljenega primera. Tam je dinamično ponovno nalaganje razreda pregledno za klicatelja. Toda v zgornji vzorčni kodi moramo še vedno spremeniti primerek storitve iz poštar1 do poštar2 ko se koda spremeni. Četrti in zadnji korak bo odpravil potrebo po tej ročni spremembi.

Povežite posodobljeni razred s klicateljem

Kako dostopati do posodobljenega dinamičnega razreda s statično referenco? Očitno neposredna (običajna) referenca na objekt dinamičnega razreda ne bo naredila trika. Potrebujemo nekaj med odjemalcem in dinamičnim razredom - proxy. (Glej znamenito knjigo Vzorci oblikovanja za več o vzorcu proxy.)

Tu je proxy razred, ki deluje kot vmesnik za dostop dinamičnega razreda. Naročnik ne prikliče dinamičnega razreda neposredno; namesto tega namesto njega. Nato proxy posreduje klice v dinamični razred zaledja. Slika 2 prikazuje sodelovanje.

Ko se dinamični razred znova naloži, moramo samo posodobiti povezavo med proxyjem in dinamičnim razredom, odjemalec pa še naprej uporablja isti primerek proxyja za dostop do ponovno naloženega razreda. Slika 3 prikazuje sodelovanje.

Na ta način spremembe v dinamičnem razredu postanejo pregledne za klicatelja.

API za razmislek Java vključuje priročen pripomoček za ustvarjanje posredniških strežnikov. Razred java.lang.reflect.Proxy ponuja statične metode, ki vam omogočajo ustvarjanje primerkov proxy za kateri koli vmesnik Java.

Spodnja vzorčna koda ustvari proxy za vmesnik Poštar. (Če niste seznanjeni z java.lang.reflect.Proxy, pred nadaljevanjem si oglejte Javadoc.)

 InvocationHandler handler = new DynaCodeInvocationHandler (...); Postman proxy = (Postman) Proxy.newProxyInstance (Postman.class.getClassLoader (), nov razred [] {Postman.class}, vodnik); 

Vrnjeni zastopnik je objekt anonimnega razreda, ki deli isti naložitelj razredov z Poštar vmesnik ( newProxyInstance () prvi parameter parametra) in izvaja Poštar vmesnik (drugi parameter). Priklic metode na zastopnik primerek pošlje v vodnikje prikliči () metoda (tretji parameter). In vodnikIzvajanje lahko izgleda takole:

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