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ä PostmanImpl
lä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 Postinkantaja
on 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: