Ohjelmointi

Lisää dynaaminen Java-koodi sovellukseesi

JavaServer Pages (JSP) on joustavampi tekniikka kuin servlet-sovellukset, koska se pystyy vastaamaan dynaamisiin muutoksiin ajon aikana. Voitteko kuvitella yhteisen Java-luokan, jolla on myös tämä dynaaminen kyky? Olisi mielenkiintoista, jos voisit muokata palvelun toteutusta sijoittamatta sitä uudelleen ja päivittää sovelluksesi lennossa.

Artikkelissa kuvataan dynaamisen Java-koodin kirjoittaminen. Siinä käsitellään ajonaikaisen lähdekoodin kokoamista, luokkien uudelleenlataamista ja välityspalvelimen suunnittelumallin käyttöä dynaamisen luokan muutosten tekemiseksi soittajalle.

Esimerkki dynaamisesta Java-koodista

Aloitetaan esimerkillä dynaamisesta Java-koodista, joka kuvaa todellisen dynaamisen koodin merkitystä ja tarjoaa myös kontekstin jatkokeskusteluille. Löydät tämän esimerkin täydellisen lähdekoodin Resurssit-osiosta.

Esimerkki on yksinkertainen Java-sovellus, joka riippuu Postman-nimisestä palvelusta. Postimies-palvelua kuvataan Java-käyttöliittymäksi, ja se sisältää vain yhden menetelmän, toimittaaMessage ():

julkinen käyttöliittymä Postman {void deliveryMessage (String msg); } 

Tämän palvelun yksinkertainen toteutus tulostaa viestit konsolille. Toteutusluokka on dynaaminen koodi. Tämä luokka, PostmanImpl, on vain normaali Java-luokka, paitsi että se käyttää lähdekoodia käännetyn binäärikoodin sijaan:

public class PostmanImpl toteuttaa Postman {

yksityinen PrintStream-lähtö; public PostmanImpl () {output = System.out; } public void deliveryMessage (String msg) {output.println ("[Postimies]" + msg); output.flush (); }}

Postman-palvelua käyttävä sovellus näkyy alla. vuonna main () menetelmä, ääretön silmukka lukee merkkijonot komentoriviltä ja toimittaa ne Postman-palvelun kautta:

julkinen luokka PostmanApp {

public static void main (String [] args) heittää poikkeuksen {BufferedReader sysin = new BufferedReader (new InputStreamReader (System.in));

// Hanki Postimiesesimerkki Postimies postil = getPostman ();

while (true) {System.out.print ("Kirjoita viesti:"); Merkkijono msg = sysin.readLine (); postman.deliverMessage (viesti); }}

staattinen staattinen postimies getPostman () {// Jätä toistaiseksi pois, palaan myöhemmin myöhemmin}}

Suorita sovellus, kirjoita joitain viestejä, ja näet konsolissa seuraavat lähdöt (voit ladata esimerkin ja suorittaa sen itse):

[DynaCode] Init-luokan näyte.PostmanImpl Kirjoita viesti: hei maailma [Postman] hei maailma Kirjoita viesti: kuinka hieno päivä! Kuinka mukava päivä! Kirjoita viesti: 

Kaikki on suoraviivaista lukuun ottamatta ensimmäistä riviä, joka osoittaa luokan olevan PostmanImpl on koottu ja ladattu.

Nyt olemme valmiita näkemään jotain dynaamista. Muutetaan sovellusta pysäyttämättä PostmanImpllähdekoodi. Uusi toteutus toimittaa kaikki viestit tekstitiedostoon konsolin sijaan:

// MUUTETTU VERSIO julkisen luokan PostmanImpl toteuttaa Postman {

yksityinen PrintStream-lähtö; // muokkauksen alku public PostmanImpl () heittää IOException {output = new PrintStream (new FileOutputStream ("msg.txt")); } // Muokkauksen loppu

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

output.flush (); }}

Siirry takaisin sovellukseen ja kirjoita lisää viestejä. Mitä tapahtuu? Kyllä, viestit menevät nyt tekstitiedostoon. Katso konsolia:

[DynaCode] Init-luokan näyte.PostmanImpl Kirjoita viesti: hei maailma [Postman] hei maailma Kirjoita viesti: kuinka hieno päivä! Kuinka mukava päivä! Kirjoita viesti: Haluan mennä tekstitiedostoon. [DynaCode] Init-luokan näyte.PostmanImpl Kirjoita viesti: myös minä! Kirjoita viesti: 

Ilmoitus [DynaCode] Init-luokan näyte.PostmanImpl ilmestyy uudelleen, mikä osoittaa, että luokka PostmanImpl käännetään uudelleen ja ladataan uudelleen. Jos tarkistat tekstitiedoston msg.txt (työhakemiston alla), näet seuraavat:

[Postman] Haluan mennä tekstitiedostoon. [Postimies] minäkin! 

Hämmästyttävää, eikö? Pystymme päivittämään Postman-palvelun ajon aikana, ja muutos on sovellukselle täysin avoin. (Huomaa, että sovellus käyttää samaa Postman-esiintymää päästäessään käyttöönottojen molempiin versioihin.)

Neljä askelta kohti dynaamista koodia

Haluan paljastaa, mitä kulissien takana tapahtuu. Pohjimmiltaan Java-koodin muuttamiseksi dynaamiseksi on neljä vaihetta:

  • Ota käyttöön valittu lähdekoodi ja seuraa tiedostomuutoksia
  • Käännä Java-koodi ajon aikana
  • Lataa / lataa Java-luokka ajon aikana
  • Linkitä ajantasainen luokka soittajaan

Ota valittu lähdekoodi käyttöön ja seuraa tiedostomuutoksia

Dynaamisen koodin kirjoittamisen aloittamiseksi meidän on ensin vastattava seuraavaan kysymykseen: "Minkä koodin osan tulisi olla dynaaminen - koko sovelluksen vai vain osan luokista?" Teknisesti rajoituksia on vähän. Voit ladata / ladata minkä tahansa Java-luokan ajon aikana. Mutta useimmissa tapauksissa vain osa koodista tarvitsee tällaista joustavuutta.

Postman-esimerkki osoittaa tyypillisen mallin dynaamisten luokkien valinnassa. Riippumatta siitä, miten järjestelmä koostuu, loppujen lopuksi tulee olemaan rakennuspalikoita, kuten palvelut, alijärjestelmät ja komponentit. Nämä rakennuspalikat ovat suhteellisen riippumattomia, ja ne paljastavat toiminnot toisilleen ennalta määritettyjen rajapintojen kautta. Käyttöliittymän takana toteutus on vapaa muuttumaan, kunhan se on käyttöliittymän määrittelemän sopimuksen mukainen. Juuri tätä laatua tarvitsemme dynaamisille luokille. Joten yksinkertaisesti sanottuna: Valitse toteutusluokka dynaamiseksi luokaksi.

Artikkelin loppuosassa teemme seuraavat oletukset valituista dynaamisista luokista:

  • Valittu dynaaminen luokka toteuttaa jonkin verran Java-käyttöliittymää toiminnallisuuden paljastamiseksi
  • Valitun dynaamisen luokan toteutus ei sisällä mitään tilastollista tietoa asiakkaastaan ​​(samanlainen kuin valtioton istuntopapu), joten dynaamisen luokan esiintymät voivat korvata toisensa

Huomaa, että nämä oletukset eivät ole edellytyksiä. Niitä on vain dynaamisen koodin toteuttamisen helpottamiseksi, jotta voimme keskittyä enemmän ideoihin ja mekanismeihin.

Lähdekoodin käyttöönotto on helppo tehtävä valittuja dynaamisia luokkia ajatellen. Kuva 1 näyttää Postman-esimerkin tiedostorakenteen.

Tiedämme, että "src" on lähde ja "bin" on binääri. Yksi huomionarvoinen asia on dynacode-hakemisto, joka pitää sisällään dynaamisten luokkien lähdetiedostot. Tässä esimerkissä on vain yksi tiedosto - PostmanImpl.java. Lokki- ja dynakoodihakemistot vaaditaan sovelluksen suorittamiseen, kun taas src ei ole välttämätön käyttöönottoa varten.

Tiedostomuutosten havaitseminen voidaan saavuttaa vertaamalla muokkauksen aikaleimoja ja tiedostokokoja. Esimerkissämme PostmanImpl.java-tarkistus suoritetaan joka kerta, kun menetelmään vedotaan Postinkantaja käyttöliittymä. Vaihtoehtoisesti voit luoda daemon-säiettä taustalla tarkistaaksesi tiedostomuutokset säännöllisesti. Tämä voi johtaa parempaan suorituskykyyn suurissa sovelluksissa.

Käännä Java-koodi ajon aikana

Kun lähdekoodimuutos on havaittu, pääsemme kokoamisongelmaan. Kun suoritat todellisen työn olemassa olevalle Java-kääntäjälle, ajonaikainen kokoaminen voi olla kakku. Monet Java-kääntäjät ovat käytettävissä, mutta tässä artikkelissa käytämme Javac-kääntäjää, joka sisältyy Sunin Java-alustaan, Standard Edition (Java SE on Sunin uusi nimi J2SE: lle).

Vähintään voit koota Java-tiedoston vain yhdellä käskyllä ​​edellyttäen, että Javac-kääntäjän sisältävä tools.jar on luokan polulla (työkalut.jar löytyy kohdasta / lib /):

 int errorCode = com.sun.tools.javac.Main.compile (uusi merkkijono [] {"-classpath", "bin", "-d", "temp / dynacode_classes", "dynacode / näyte / PostmanImpl.java" }); 

Luokka com.sun.tools.javac.Main on Javac-kääntäjän ohjelmointirajapinta. Se tarjoaa staattisia menetelmiä Java-lähdetiedostojen kokoamiseksi. Yllä olevan lauseen suorittamisella on sama vaikutus kuin käynnillä javac komentoriviltä samoilla argumenteilla. Se kokoaa lähdetiedoston dynacode / sample / PostmanImpl.java määritetyn luokan polkupyörän avulla ja lähettää sen luokkatiedoston kohdehakemistoon / temp / dynacode_classes. Kokonaisluku palaa virhekoodina. Nolla tarkoittaa menestystä; mikä tahansa muu numero osoittaa, että jokin on mennyt pieleen.

com.sun.tools.javac.Main luokka tarjoaa myös toisen koota() menetelmä, joka hyväksyy lisäosan PrintWriter parametri, kuten alla olevassa koodissa näkyy. Yksityiskohtaiset virheilmoitukset kirjoitetaan PrintWriter jos kokoaminen epäonnistuu.

 // Määritetty com.sun.tools.javac.Main public static int compile (String [] argumentit); public static int compile (String [] argumentoi, PrintWriter ulos); 

Oletan, että useimmat kehittäjät tuntevat Javac-kääntäjän, joten lopetan tähän. Lisätietoja kääntäjän käytöstä on Resursseissa.

Lataa / lataa Java-luokka ajon aikana

Koottu luokka on ladattava ennen kuin se tulee voimaan. Java on joustava luokkien lataamisessa. Se määrittelee kattavan luokan latausmekanismin ja tarjoaa useita luokkakuormaajien toteutuksia. (Katso lisätietoja luokan lataamisesta kohdasta Resurssit.)

Alla oleva esimerkkikoodi näyttää, kuinka luokka ladataan ja ladataan uudelleen. Perusidea on ladata dynaaminen luokka omalla URLClassLoader. Aina kun lähdetiedostoa muutetaan ja käännetään uudelleen, hylätään vanha luokka (myöhemmin roskakoriin) ja luodaan uusi URLClassLoader ladata luokan uudelleen.

// Dir sisältää käännetyt luokat. TiedostoluokatDir = uusi tiedosto ("/ temp / dynacode_classes /");

// Vanhempi classloader ClassLoader parentLoader = Postman.class.getClassLoader ();

// Lataa luokka "näyte.PostmanImpl" omalla luokkakuormaajalla. URLClassLoader loader1 = uusi URLClassLoader (uusi URL [] {classDir.toURL ()}, parentLoader); Luokka cls1 = loader1.loadClass ("sample.PostmanImpl"); Postimies postimies1 = (Postimies) cls1.uusi asia ();

/ * * Käynnistä postman1: ssä ... * Sitten PostmanImpl.java muokataan ja käännetään uudelleen. * /

// Lataa luokka "sample.PostmanImpl" uudella luokkakuormaajalla. URLClassLoader loader2 = uusi URLClassLoader (uusi URL [] {classDir.toURL ()}, parentLoader); Luokka cls2 = loader2.loadClass ("sample.PostmanImpl"); Postimies postimies2 = (Postimies) cls2.uusi asia ();

/ * * Toimi nyt postman2: n kanssa ... * Älä huoli loader1: stä, cls1: stä ja postman1: stä * ne kerätään automaattisesti roskiin. * /

Kiinnitä huomiota parentLoader luodessasi omaa luokkatietokonetta. Periaatteessa sääntö on, että vanhemman luokan lataajan on annettava kaikki riippuvuudet, joita lapsi tarvitsee. Joten esimerkkikoodissa dynaaminen luokka PostmanImpl riippuu käyttöliittymästä Postinkantaja; siksi käytämme Postinkantajaon luokkansa vanhempi luokka.

Olemme vielä yhden askeleen päässä dynaamisen koodin viimeistelystä. Palautetaan mieleen aiemmin esitetty esimerkki. Siellä dynaaminen luokan uudelleenlataus on läpinäkyvää soittajalle. Mutta yllä olevassa esimerkkikoodissa meidän on silti vaihdettava palvelun esiintymä postimies1 että postimies2 kun koodi muuttuu. Neljäs ja viimeinen vaihe poistaa tämän manuaalisen muutoksen tarpeen.

Linkitä ajantasainen luokka soittajaan

Kuinka pääset ajan tasalla olevaan dynaamiseen luokkaan staattisella viitteellä? Ilmeisesti suora (normaali) viittaus dynaamisen luokan objektiin ei tee temppua. Tarvitsemme jotain asiakkaan ja dynaamisen luokan välille - välityspalvelimen. (Katso kuuluisa kirja Suunnittelumalleja lisätietoja välityspalvelinkuviosta.)

Tässä välityspalvelin on luokka, joka toimii dynaamisen luokan käyttöliittymänä. Asiakas ei kutsu dynaamista luokkaa suoraan; välityspalvelin tekee sen sijaan. Välityspalvelin välittää kutsut sitten backend-dynaamiseen luokkaan. Kuva 2 esittää yhteistyötä.

Kun dynaaminen luokka latautuu uudelleen, meidän on vain päivitettävä välityspalvelimen ja dynaamisen luokan välinen linkki, ja asiakas käyttää edelleen samaa välityspalvelimen esiintymää päästäessään uudelleen ladattuun luokkaan. Kuva 3 esittää yhteistyötä.

Tällä tavalla muutokset dynaamiseen luokkaan tulevat läpinäkyviksi soittajalle.

Java Reflection -sovellusliittymä sisältää kätevän apuohjelman välityspalvelinten luomiseen. Luokka java.lang.heijastaa.Proxy tarjoaa staattisia menetelmiä, joiden avulla voit luoda välityspalvelinilmentymiä mille tahansa Java-käyttöliittymälle.

Alla oleva esimerkkikoodi luo käyttöliittymälle välityspalvelimen Postinkantaja. (Jos et ole perehtynyt java.lang.heijastaa.Proxy, tutustu Javadociin ennen jatkamista.)

 InvocationHandler handler = uusi DynaCodeInvocationHandler (...); Postimiehen välityspalvelin = (Postman) Välityspalvelin.newProxyInstance (Postman.class.getClassLoader (), uusi luokka [] {Postman.class}, käsittelijä); 

Palasi valtakirja on nimettömän luokan kohde, joka jakaa saman luokkakuormaajan Postinkantaja käyttöliittymä ( newProxyInstance () menetelmän ensimmäinen parametri) ja toteuttaa Postinkantaja käyttöliittymä (toinen parametri). Menetelmän kutsuminen valtakirja esimerkki lähetetään käsittelijäon vedota() menetelmä (kolmas parametri). Ja käsittelijäToteutus voi näyttää seuraavalta: