Programmēšana

Pievienojiet savai lietojumprogrammai dinamisku Java kodu

JavaServer Pages (JSP) ir elastīgāka tehnoloģija nekā servleti, jo tā var reaģēt uz dinamiskām izmaiņām izpildlaika laikā. Vai varat iedomāties kopēju Java klasi, kurai arī ir šī dinamiskā spēja? Būtu interesanti, ja jūs varētu modificēt pakalpojuma ieviešanu, to nepārvietojot, un atjauninātu lietojumprogrammu.

Rakstā ir paskaidrots, kā rakstīt dinamisku Java kodu. Tajā tiek apspriesta izpildlaika pirmkodu apkopošana, klases atkārtota ielāde un starpniekservera dizaina modeļa izmantošana, lai dinamiskās klases modifikācijas būtu pārskatāmas tās zvanītājam.

Dinamiska Java koda piemērs

Sāksim ar dinamiskā Java koda piemēru, kas ilustrē patiesā dinamiskā koda nozīmi, kā arī sniedz zināmu kontekstu turpmākajām diskusijām. Lūdzu, atrodiet šī piemēra pilnu pirmkodu sadaļā Resursi.

Piemērs ir vienkārša Java lietojumprogramma, kas ir atkarīga no pakalpojuma ar nosaukumu Postman. Pastnieka pakalpojums tiek aprakstīts kā Java saskarne, un tajā ir tikai viena metode, piegādātMessage ():

publiskā saskarne Pastnieks {void deliveryMessage (String msg); } 

Vienkārša šī pakalpojuma ieviešana izdrukā ziņojumus konsolē. Ieviešanas klase ir dinamiskais kods. Šī klase, PastnieksImpl, ir tikai parasta Java klase, izņemot to, ka tā apkopotā binārā koda vietā ievieto avota kodu:

publiskā klase PostmanImpl īsteno Pastnieks {

privāta PrintStream izeja; public PostmanImpl () {output = System.out; } public void deliveryMessage (String msg) {output.println ("[Pastnieks]" + msg); izeja.skalot (); }}

Zemāk tiek parādīta lietojumprogramma, kas izmanto pakalpojumu Pastnieks. Iekš galvenais () metodi bezgalīga cilpa nolasa virknes ziņojumus no komandrindas un piegādā tos, izmantojot pakalpojumu Postman:

publiskā klase PostmanApp {

public static void main (String [] args) izmet izņēmumu {BufferedReader sysin = new BufferedReader (new InputStreamReader (System.in));

// Iegūstiet pastnieka instanci Pastnieks pastnieks = getPostman ();

while (true) {System.out.print ("Ievadiet ziņojumu:"); String msg = sysin.readLine (); pastnieks.deliverMessage (msg); }}

privāts statisks pastnieks getPostman () {// pagaidām izlaist, atgriezīsies vēlāk}}

Izpildiet lietojumprogrammu, ievadiet dažus ziņojumus, un konsolē būs redzami šādi rezultāti (piemēram, varat lejupielādēt piemēru un palaist to pats):

[DynaCode] Sākotnējās klases paraugs.PostmanImpl Ievadiet ziņojumu: sveika pasaule [Pastnieks] sveika pasaule Ievadiet ziņojumu: cik jauka diena! [Pastnieks] cik jauka diena! Ievadiet ziņojumu: 

Viss ir vienkārši, izņemot pirmo līniju, kas norāda, ka klase PastnieksImpl tiek sastādīts un ielādēts.

Tagad mēs esam gatavi redzēt kaut ko dinamisku. Neapstādinot lietojumprogrammu, pārveidosim PastnieksImplavota kods. Jaunā ieviešana visus ziņojumus piegādā teksta failā, nevis konsolē:

// PĀRVEIDOTĀS VERSIJAS publiskā klase PostmanImpl īsteno Postman {

privāta PrintStream izeja; // Modifikācijas sākums public PostmanImpl () throws IOException {output = new PrintStream (new FileOutputStream ("msg.txt")); } // Modifikācijas beigas

public void deliveryMessage (String msg) {output.println ("[Pastnieks]" + msg);

izeja.skalot (); }}

Pārslēdzieties atpakaļ uz lietojumprogrammu un ievadiet vairāk ziņojumu. Kas notiks? Jā, ziņojumi tagad nonāk teksta failā. Paskaties uz konsoli:

[DynaCode] Sākotnējās klases paraugs.PostmanImpl Ievadiet ziņojumu: sveika pasaule [Pastnieks] sveika pasaule Ievadiet ziņojumu: cik jauka diena! [Pastnieks] cik jauka diena! Ievadiet ziņojumu: Es gribu doties uz teksta failu. [DynaCode] Init klases paraugs.PostmanImpl Ievadiet ziņojumu: es arī! Ievadiet ziņojumu: 

Paziņojums [DynaCode] Init klases paraugs.PostmanImpl atkal parādās, norādot, ka klase PastnieksImpl tiek kompilēts un pārkrauts. Pārbaudot teksta failu msg.txt (darba direktorijā), redzēsiet:

[Pastnieks] Es gribu doties uz teksta failu. [Pastnieks] arī es! 

Pārsteidzoši, vai ne? Mēs varam atjaunināt pakalpojumu Postman izpildlaika laikā, un izmaiņas lietojumprogrammai ir pilnīgi pārredzamas. (Ievērojiet, ka programma izmanto vienu un to pašu Pastnieka instanci, lai piekļūtu abām ieviešanas versijām.)

Četri soļi dinamiskā koda virzienā

Ļaujiet man atklāt, kas notiek aiz ainas. Būtībā ir četras darbības, lai Java kodu padarītu dinamisku:

  • Izvietojiet atlasīto pirmkodu un pārraugiet failu izmaiņas
  • Apkopo Java kodu izpildlaikā
  • Ielādēt / pārlādēt Java klasi izpildlaikā
  • Saistiet jaunāko klasi ar zvanītāju

Izvietojiet atlasīto pirmkodu un pārraugiet failu izmaiņas

Lai sāktu rakstīt dinamisko kodu, pirmais jautājums, uz kuru mums jāatbild, ir šāds: "Kurai koda daļai jābūt dinamiskai - visai lietojumprogrammai vai tikai dažām klasēm?" Tehniski ir maz ierobežojumu. Izpildes laikā varat ielādēt / pārlādēt jebkuru Java klasi. Bet vairumā gadījumu šāda līmeņa elastība nepieciešama tikai koda daļai.

Pastnieka piemērs parāda tipisku dinamisko klašu izvēles modeli. Neatkarīgi no tā, kā sistēma tiek veidota, galu galā būs tādi pamatelementi kā pakalpojumi, apakšsistēmas un komponenti. Šie veidojošie elementi ir salīdzinoši neatkarīgi, un tie viens otram atklāj funkcijas, izmantojot iepriekš definētas saskarnes. Aiz saskarnes ir tā ieviešanas iespēja, kuru var brīvi mainīt, ja vien tā atbilst saskarnes definētajam līgumam. Tieši šī kvalitāte mums ir nepieciešama dinamiskām nodarbībām. Tā vienkārši sakot: Izvēlieties ieviešanas klasi kā dinamisko klasi.

Pārējā raksta daļā mēs izdarīsim šādus pieņēmumus par izvēlētajām dinamiskajām klasēm:

  • Izvēlētā dinamiskā klase ievieš kādu Java saskarni, lai parādītu funkcionalitāti
  • Izvēlētās dinamiskās klases ieviešana nesatur nekādu valstisku informāciju par savu klientu (līdzīgi kā sesijas pupiņai bez valsts), tāpēc dinamiskās klases gadījumi var aizstāt viens otru

Lūdzu, ņemiet vērā, ka šie pieņēmumi nav priekšnoteikumi. Tie pastāv tikai tāpēc, lai nedaudz atvieglotu dinamiskā koda realizāciju, lai mēs varētu vairāk koncentrēties uz idejām un mehānismiem.

Paturot prātā izvēlētās dinamiskās klases, avota koda izvietošana ir viegls uzdevums. 1. attēlā parādīta pastnieka faila struktūra.

Mēs zinām, ka "src" ir avots un "bin" ir binārs. Jāatzīmē viena lieta - dinamakoda direktorijs, kurā glabājas dinamisko klašu avota faili. Šajā piemērā ir tikai viens fails—PostmanImpl.java. Lai palaistu lietojumprogrammu, ir nepieciešami atkritumu un dinamokoda direktoriji, savukārt izvietošanai nav nepieciešams src.

Failu izmaiņu noteikšanu var panākt, salīdzinot modifikācijas laika zīmogus un failu izmērus. Piemēram, pārbaude PostmanImpl.java tiek veikta katru reizi, kad tiek izsaukta metode Pastnieks interfeiss. Alternatīvi, jūs varat nārstot dēmonu pavedienu fonā, lai regulāri pārbaudītu faila izmaiņas. Tas var radīt labāku sniegumu liela mēroga lietojumprogrammām.

Apkopo Java kodu izpildlaikā

Pēc avota koda maiņas konstatēšanas mēs nonākam pie apkopošanas problēmas. Deleģējot reālo darbu esošam Java kompilatoram, izpildlaika apkopošana var būt kūkas gabals. Daudzi Java kompilatori ir pieejami lietošanai, taču šajā rakstā mēs izmantojam Javac kompilatoru, kas iekļauts Sun Java platformas Standard Edition versijā (Java SE ir Sun jaunais nosaukums J2SE).

Vismaz jūs varat apkopot Java failu tikai ar vienu paziņojumu, nodrošinot, ka tools.jar, kurā ir Javac kompilators, atrodas klases ceļā (tools.jar varat atrast zem / lib /):

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

Klase com.sun.tools.javac.Galvenais ir Javac kompilatora programmēšanas saskarne. Tas nodrošina statiskas metodes Java avota failu apkopošanai. Iepriekš minētā paziņojuma izpildei ir tāds pats efekts kā skriešanai javac no komandrindas ar tiem pašiem argumentiem. Tas apkopo avota failu dynacode / sample / PostmanImpl.java, izmantojot norādīto classpath bin, un izvada savu klases failu mērķa direktorijā / temp / dynacode_classes. Vesels skaitlis atgriežas kā kļūdas kods. Nulle nozīmē panākumus; jebkurš cits skaitlis norāda, ka kaut kas nav kārtībā.

The com.sun.tools.javac.Galvenais klase nodrošina arī citu sastādīt() metode, kas pieņem papildu PrintWriter parametrs, kā parādīts zemāk esošajā kodā. Detalizēti kļūdu ziņojumi tiks rakstīti PrintWriter ja apkopošana neizdodas.

 // Definēts com.sun.tools.javac.Main public static int compile (String [] args); public static int compile (String [] argumentē, PrintWriter out); 

Es pieņemu, ka lielākā daļa izstrādātāju ir pazīstami ar Javac kompilatoru, tāpēc es apstāšos šeit. Lai iegūtu papildinformāciju par kompilatora izmantošanu, lūdzu, skatiet resursus.

Ielādēt / pārlādēt Java klasi izpildlaikā

Kompilētā klase ir jāielādē, pirms tā stājas spēkā. Java ir elastīga attiecībā uz klases ielādi. Tas nosaka visaptverošu klases iekraušanas mehānismu un nodrošina vairākas klases krāvēju ieviešanas iespējas. (Lai iegūtu vairāk informācijas par klases iekraušanu, skatiet resursus.)

Tālāk redzamajā koda paraugā parādīts, kā klasi ielādēt un atkārtoti ielādēt. Pamatideja ir ielādēt dinamisko klasi, izmantojot mūsu pašu URLClassLoader. Ikreiz, kad avota fails tiek mainīts un kompilēts, mēs izmetam veco klasi (atkritumu savākšanai vēlāk) un izveidojam jaunu URLClassLoader lai vēlreiz ielādētu klasi.

// Dir satur apkopotās klases. Failu klasesDir = new File ("/ temp / dynacode_classes /");

// vecāks classloader ClassLoader parentLoader = Postman.class.getClassLoader ();

// Ielādējiet klasi "sample.PostmanImpl" ar mūsu pašu klases krāvēju. URLClassLoader loader1 = jauns URLClassLoader (jauns URL [] {classDir.toURL ()}, parentLoader); Klase cls1 = loader1.loadClass ("sample.PostmanImpl"); Pastnieks pastnieks1 = (Pastnieks) cls1.newStance ();

/ * * Izsaukt postman1 ... * Pēc tam PostmanImpl.java tiek modificēts un kompilēts. * /

// Pārlādēt klasi "sample.PostmanImpl" ar jaunu classloader. URLClassLoader loader2 = jauns URLClassLoader (jauns URL [] {classDir.toURL ()}, parentLoader); Klase cls2 = loader2.loadClass ("sample.PostmanImpl"); Pastnieks pastnieks2 = (Pastnieks) cls2.newInstance ();

/ * * Turpmāk strādājiet ar postman2 ... * Neuztraucieties par loader1, cls1 un postman1 * tie tiks automātiski savākti atkritumi. * /

Pievērsiet uzmanību parentLoader veidojot savu classloader. Būtībā noteikums ir tāds, ka vecākajam klases skolotājam jānodrošina visas atkarības, kas nepieciešamas bērnu klases ielādēšanai. Tātad parauga kodā dinamiskā klase PastnieksImpl atkarīgs no saskarnes Pastnieks; tāpēc mēs izmantojam Pastnieksir vecākais.

Mēs joprojām esam spēruši soli līdz dinamiskā koda pabeigšanai. Atgādināsim iepriekš ieviesto piemēru. Dinamiskā klases atkārtota ielāde ir zvanītājam pārskatāma. Bet iepriekš minētajā koda paraugā mums joprojām ir jāmaina pakalpojuma instance no pastnieks1 uz pastnieks2 kad kods mainās. Ceturtais un pēdējais solis novērsīs nepieciešamību veikt šīs manuālās izmaiņas.

Saistiet jaunāko klasi ar zvanītāju

Kā piekļūt atjauninātajai dinamiskajai klasei ar statisku atsauci? Acīmredzot tieša (normāla) atsauce uz dinamiskās klases objektu nepadarīs šo triku. Mums ir nepieciešams kaut kas starp klientu un dinamisko klasi - starpniekserveris. (Skat. Slaveno grāmatu Dizaina modeļi lai uzzinātu vairāk par starpniekservera modeli.)

Šeit starpniekserveris ir klase, kas darbojas kā dinamiskas klases piekļuves saskarne. Klients tieši neizsauc dinamisko klasi; tā vietā dara starpniekserveris. Pēc tam starpniekserveris pārsūta izsaukumus uz aizmugures dinamisko klasi. 2. attēlā parādīta sadarbība.

Kad dinamiskā klase tiek atkārtoti ielādēta, mums vienkārši jāatjaunina saite starp starpniekserveri un dinamisko klasi, un klients turpina izmantot to pašu starpniekservera gadījumu, lai piekļūtu pārlādētajai klasei. 3. attēlā parādīta sadarbība.

Tādā veidā izmaiņas dinamiskajā klasē kļūst pārskatāmas tās zvanītājam.

Java atspoguļošanas API ietver ērtu starpniekserveru izveidošanas lietderību. Klase java.lang.reflect.Proxy nodrošina statiskas metodes, kas ļauj jums izveidot starpniekservera gadījumus jebkuram Java interfeisam.

Tālāk redzamais koda paraugs rada interfeisa starpniekserveri Pastnieks. (Ja neesat pazīstams ar java.lang.reflect.Proxy, lūdzu, pirms turpināšanas ieskatieties Javadoc.)

 InvocationHandler handler = jauns DynaCodeInvocationHandler (...); Pastnieka starpniekserveris = (Pastnieks) Starpniekserveris.newProxyInstance (Pastnieks.klase.getClassLoader (), jauna klase [] {Pastnieks.klase}, apdarinātājs); 

Atgriezās starpniekserveris ir anonīmas klases objekts, kuram ir tāds pats klases ielādētājs kā Pastnieks saskarne ( newProxyInstance () metodes pirmais parametrs) un ievieš Pastnieks interfeiss (otrais parametrs). Metodes izsaukšana uz starpniekserveris instance tiek nosūtīta uz apdarinātājs's izsaukt () metode (trešais parametrs). Un apdarinātājsieviešana var izskatīties šādi:

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