Ohjelmointi

Se on sopimuksessa! Objektiversiot JavaBeansille

Kahden viime kuukauden aikana olemme perehtyneet syvällisemmin siihen, miten Java-objektit sarjoitetaan. (Katso "Sarjallisuus ja JavaBeans-määrittely" ja "Tee se Nescafé-tavalla - pakastekuivattuilla JavaBeans-laitteilla".) Tämän kuun artikkelissa oletetaan, että olet jo lukenut nämä artikkelit tai ymmärrät niiden käsittelemät aiheet. Sinun tulisi ymmärtää, mitä sarjallisuus on, miten sitä käytetään Sarjattavissa käyttöliittymä ja miten java.io.ObjectOutputStream ja java.io.ObjectInputStream luokat.

Miksi tarvitset versiota

Se, mitä tietokone tekee, määräytyy sen ohjelmiston perusteella, ja ohjelmisto on erittäin helppo muuttaa. Tällä joustavuudella, jota yleensä pidetään omaisuuseränä, on vastuunsa. Joskus näyttää siltä, ​​että ohjelmisto on liian helppo vaihtaa. Olet epäilemättä törmännyt ainakin yhteen seuraavista tilanteista:

  • Sähköpostitse saamasi tiedostotiedosto ei lue tekstinkäsittelyohjelmassa oikein, koska sinun on vanhempi versio, jonka tiedostomuoto ei ole yhteensopiva

  • Web-sivu toimii eri tavoin eri selaimissa, koska erilaiset selainversiot tukevat erilaisia ​​ominaisuuksia

  • Sovellus ei toimi, koska sinulla on väärä versio tietystä kirjastosta

  • C ++ -sovellustasi ei käännetä, koska otsikko- ja lähdetiedostot ovat yhteensopimattomia versioita

Kaikki nämä tilanteet johtuvat yhteensopimattomista ohjelmistoversioista ja / tai ohjelmiston käsittelemistä tiedoista. Rakennusten, henkilökohtaisten filosofioiden ja joenpohjien tavoin ohjelmat muuttuvat jatkuvasti vastauksena ympäröiviin olosuhteisiin. (Jos et usko, että rakennukset muuttuvat, lue Stewart Brandin erinomainen kirja Kuinka rakennukset oppivat, keskustelu siitä, miten rakenteet muuttuvat ajan myötä. Katso lisätietoja kohdasta Resurssit.) Ilman rakennetta, jolla hallita ja hallita tätä muutosta, mikä tahansa hyödyllinen koko ohjelmistojärjestelmä rappeutuu lopulta kaaokseen. Tavoitteena ohjelmisto versiointi Tarkoituksena on varmistaa, että tällä hetkellä käyttämäsi ohjelmistoversio tuottaa oikeat tulokset, kun se kohtaa muiden itsensä versioiden tuottamia tietoja.

Tässä kuussa keskustelemme siitä, miten Java-luokan versiointi toimii, jotta voimme tarjota JavaBean-versioidemme versionhallinnan. Java-luokkien versiorakenne antaa sinun ilmoittaa sarjallisuusmekanismille, onko tietty tietovirta (eli sarjoitettu objekti) luettavissa tietyllä Java-luokan versiolla. Puhumme luokkien "yhteensopivista" ja "yhteensopimattomista" muutoksista ja siitä, miksi nämä muutokset vaikuttavat versiointiin. Käymme läpi versiorakenteen tavoitteet ja miten java.io paketti täyttää nämä tavoitteet. Ja opimme asettamaan koodiin suojatoimenpiteitä sen varmistamiseksi, että kun luemme eri versioiden objektivirtoja, tiedot ovat aina yhdenmukaisia ​​objektin lukemisen jälkeen.

Versio-vastenmielisyys

Ohjelmistoissa on erilaisia ​​versiointiongelmia, jotka kaikki liittyvät tietojen ja / tai suoritettavan koodin palojen yhteensopivuuteen:

  • Saman ohjelmiston eri versiot saattavat tai eivät välttämättä pysty käsittelemään toistensa tietojen tallennusmuotoja

  • Ohjelmien, jotka lataavat suoritettavan koodin ajon aikana, on kyettävä tunnistamaan ohjelmisto-objektin, ladattavan kirjaston tai objektitiedoston oikea versio tehtävän suorittamiseksi

  • Luokan menetelmillä ja kentillä on oltava sama merkitys kuin luokassa kehittyy, tai olemassa olevat ohjelmat voivat rikkoutua paikoissa, joissa näitä menetelmiä ja kenttiä käytetään

  • Lähdekoodi, otsikkotiedostot, dokumentaatio ja koontikomentosarjat on kaikki koordinoitava ohjelmistokehitysympäristössä sen varmistamiseksi, että binaaritiedostot rakennetaan lähdetiedostojen oikeista versioista

Tämä Java-objektien versiota käsittelevä artikkeli käsittelee vain kolmea ensimmäistä - toisin sanoen binaaristen objektien ja niiden semantiikan versionhallintaa ajonaikaisessa ympäristössä. (Lähdekoodin versiointiin on saatavilla laaja valikoima ohjelmistoja, mutta emme käsittele sitä tässä.)

On tärkeää muistaa, että sarjoitetut Java-objektivirrat eivät sisällä tavukoodeja. Ne sisältävät vain tarvittavat tiedot kohteen rekonstruoimiseksi olettaen sinulla on luokan tiedostot käytettävissä objektin rakentamiseen. Mutta mitä tapahtuu, jos kahden Java-virtuaalikoneen (kirjoittajan ja lukijan) luokkatiedostot ovat eri versioita? Mistä tiedämme, ovatko ne yhteensopivia?

Luokan määrittely voidaan ajatella "sopimukseksi" luokan ja sitä kutsuvan koodin välillä. Tämä sopimus sisältää luokan API (sovelluksen ohjelmointirajapinta). Sovellusliittymän muuttaminen vastaa sopimuksen muuttamista. (Muut luokkamuutokset voivat tarkoittaa myös muutoksia sopimukseen, kuten näemme.) Kun luokka kehittyy, on tärkeää säilyttää luokan aiempien versioiden käyttäytyminen, jotta ohjelmistoa ei rikottu paikoissa, jotka riippuivat annettu käyttäytyminen.

Versiomuutosesimerkki

Kuvittele, että sinulla on menetelmä nimeltä getItemCount () luokassa, mikä tarkoitti Hanki objektin kokonaismäärä, ja tätä menetelmää käytettiin tusinassa paikassa koko järjestelmässäsi. Kuvittele sitten myöhemmin, että muutat getItemCount () tarkoittaa Hanki enimmäismäärä esineitä, joita tällä objektilla on koskaan sisällytetty. Ohjelmistosi todennäköisesti rikkoutuu useimmissa paikoissa, joissa tätä menetelmää käytettiin, koska yhtäkkiä menetelmä raportoi erilaisia ​​tietoja. Pohjimmiltaan olet rikkonut sopimuksen; joten se palvelee sinua oikein, että ohjelmassasi on nyt vikoja.

Ei ole mitään keinoa, muutosten kokonaan kieltäminen, automatisoida täysin tällaisen muutoksen havaitseminen, koska se tapahtuu ohjelman tason tasolla tarkoittaa, ei vain sen merkityksen ilmaisun tasolla. (Jos ajattelet tapaa tehdä tämä helposti ja yleisesti, olet rikkaampi kuin Bill.) Joten, mikäli ongelmaan ei ole olemassa kattavaa, yleistä ja automatisoitua ratkaisua, mitä voi vältämme pääsemästä kuumaan veteen, kun vaihdamme luokkaa (mikä tietysti on pakko)?

Helpoin vastaus tähän kysymykseen on sanoa, että jos luokka muuttuu ollenkaan, sopimuksen pitäminen ei saisi olla "luotettavaa". Loppujen lopuksi ohjelmoija on saattanut tehdä luokalle mitään, ja kuka tietää, toimiiko luokka edelleen mainostetulla tavalla? Tämä ratkaisee versioinnin ongelman, mutta se on epäkäytännöllinen ratkaisu, koska se on aivan liian rajoittava. Jos luokkaa muokataan esimerkiksi suorituskyvyn parantamiseksi, ei ole mitään syytä kieltää luokan uuden version käyttöä yksinkertaisesti siksi, että se ei vastaa vanhaa. Luokkaan voidaan tehdä mikä tahansa määrä muutoksia rikkomatta sopimusta.

Toisaalta jotkut luokkien muutokset takaavat käytännössä sopimuksen rikkomisen: esimerkiksi kentän poistaminen. Jos poistat kentän luokasta, voit silti lukea aiempien versioiden kirjoittamia virtoja, koska lukija voi aina jättää huomioimatta kyseisen kentän arvon. Mutta mieti, mitä tapahtuu, kun kirjoitat streamin, joka on tarkoitettu luettavaksi luokan aiemmille versioille. Kentän arvo puuttuu streamista, ja vanhempi versio määrittää kentälle (mahdollisesti loogisesti epäjohdonmukaisen) oletusarvon, kun se lukee virtaa. Voilà!: Sinulla on rikki luokka.

Yhteensopivat ja yhteensopimattomat muutokset

Temppu objektiversioiden yhteensopivuuden hallintaan on tunnistaa, minkä tyyppiset muutokset voivat aiheuttaa yhteensopimattomuutta versioiden välillä ja mitkä eivät, ja kohdella näitä tapauksia eri tavalla. Java-kielellä kutsutaan muutoksia, jotka eivät aiheuta yhteensopivuusongelmia yhteensopiva muutokset; ne, joita voidaan kutsua yhteensopimaton muutoksia.

Java-sarjaliikennemekanismin suunnittelijoilla oli seuraavat tavoitteet mielessä järjestelmän luomisessa:

  1. Määrittää tapa, jolla luokan uudempi versio voi lukea ja kirjoittaa streamia, jonka luokan edellinen versio voi myös "ymmärtää" ja käyttää oikein

  2. Tarjotaan oletusmekanismi, joka sarjoittaa objektit, joilla on hyvä suorituskyky ja kohtuullinen koko. Tämä on sarjallisuusmekanismi olemme jo keskustelleet kahdesta edellisestä JavaBeans-sarakkeesta, jotka on mainittu tämän artikkelin alussa

  3. Versiointiin liittyvän työn minimoimiseksi luokissa, jotka eivät tarvitse versiointia. Ihannetapauksessa versiotiedot täytyy lisätä luokkaan vain, kun uusia versioita lisätään

  4. Alustaa objektivirta siten, että objektit voidaan ohittaa lataamatta objektin luokkatiedostoa. Tämän ominaisuuden avulla asiakasobjekti voi kulkea objektivirrassa, joka sisältää objekteja, joita se ei ymmärrä

Katsotaanpa, kuinka sarjallisuusmekanismi kohdistaa nämä tavoitteet edellä kuvatun tilanteen valossa.

Sovitettavat erot

Jotkut luokkatiedostoon tehdyt muutokset voivat riippua siitä, etteikö se muuta luokan luokkaa tai mitä muut luokat sitä kutsuvat. Kuten edellä todettiin, näitä kutsutaan yhteensopiviksi muutoksiksi Java-dokumentaatiossa. Luokkatiedostoon voidaan tehdä mikä tahansa määrä yhteensopivia muutoksia muuttamatta sopimusta. Toisin sanoen kaksi luokan versiota, jotka eroavat toisistaan ​​vain yhteensopivien muutosten perusteella, ovat yhteensopivia luokkia: Uudempi versio lukee ja kirjoittaa edelleen aiempien versioiden kanssa yhteensopivia objektivirtoja.

Luokat java.io.ObjectInputStream ja java.io.ObjectOutputStream älä luota sinuun. Ne on suunniteltu oletusarvoisesti erittäin epäilyttäviksi luokkatiedoston maailman rajapinnan muutoksista - mikä tarkoittaa mitä tahansa muuta luokkaa käyttävää luokkaa: julkisten menetelmien ja rajapintojen allekirjoituksia sekä tyyppejä ja muokkaajia julkisten kenttien He ovat itse asiassa niin vainoharhaisia, että tuskin voi muuttaa luokassa mitään aiheuttamatta java.io.ObjectInputStream kieltäytyä lataamasta kurssin edellisen version kirjoittamaa streamia.

Katsotaanpa esimerkkiä. luokan yhteensopimattomuudesta ja ratkaise sitten syntynyt ongelma. Oletetaan, että sinulla on objekti nimeltä InventoryItem, joka ylläpitää varaston käytettävissä olevan osanumeroita ja kyseisen osan määrää. Tämän objektin yksinkertainen muoto JavaBeanina voi näyttää tältä:

001 002 tuo java.beans. *; 003 tuo java.io. *; 004 tuonti Tulostettava; 005 006 // 007 // Versio 1: Tallenna vain määrä käteen ja osanumero 008 // 009010 public class InventoryItem implementates Serializable, Printable {011 012 013 014 015 016 // kentät 017 suojattu int iQuantityOnHand_; 018 suojattu merkkijono sPartNo_; 019 020 public InventoryItem () 021 {022 iQuantityOnHand_ = -1; 023 sPartNo_ = ""; 024} 025 026 public InventoryItem (String _sPartNo, int _iQuantityOnHand) 027 {028 setQuantityOnHand (_iQuantityOnHand); 029 setPartNo (_sPartNo); 030} 031 032 public int getQuantityOnHand () 033 {034 return iQuantityOnHand_; 035} 036 037 public void setQuantityOnHand (int _iQuantityOnHand) 038 {039 iQuantityOnHand_ = _iQuantityOnHand; 040} 041 042 julkinen merkkijono getPartNo () 043 {044 return sPartNo_; 045} 046047 public void setPartNo (String _sPartNo) 048 {049 sPartNo_ = _sPartNo; 050} 051 052 // ... toteuttaa tulostettavan 053 public void print () 054 {055 System.out.println ("Osa:" + getPartNo () + "\ nMäärä käsillä:" + 056 getQuantityOnHand () + "\ n \ n "); 057} 058}; 059 

(Meillä on myös yksinkertainen pääohjelma nimeltä Esittely8a, joka lukee ja kirjoittaa InventoryItems tiedostoon ja tiedostosta käyttämällä objektivirtoja ja käyttöliittymää Tulostettava, joka InventoryItem toteuttaa ja Esittely8a käyttää objektien tulostamiseen. Löydät näiden lähteet täältä.) Demo-ohjelman suorittaminen tuottaa kohtuullisia, joskin jännittämättömiä tuloksia:

C: \ pavut> java Demo8a w-tiedosto SA0091-001 33 Kirjoitettu esine: Osa: SA0091-001 Määrä käsillä: 33 C: \ pavut> java Demo8a r-tiedosto Lue objekti: Osa: SA0091-001 Määrä käsillä: 33 

Ohjelma sarjoittaa ja deserialisoi objektin oikein. Tehdään nyt pieni muutos luokkatiedostoon. Järjestelmän käyttäjät ovat tehneet inventaarin ja löytäneet ristiriitoja tietokannan ja todellisten tuotemäärien välillä. He ovat pyytäneet mahdollisuutta seurata varastosta kadonneiden tuotteiden määrää. Lisätään yksi julkinen kenttä InventoryItem tämä osoittaa varastosta puuttuvien tuotteiden määrän. Lisätään seuraava rivi InventoryItem luokka ja koota uudelleen:

016 // kentät 017 suojattu int iQuantityOnHand_; 018 suojattu merkkijono sPartNo_; 019 julkinen int iQuantityLost_; 

Tiedosto kääntyy hyvin, mutta katso mitä tapahtuu, kun yritämme lukea streamia edellisestä versiosta:

C: \ mj-java \ Column8> java Demo8a r-tiedosto IO-poikkeus: InventoryItem; Paikallinen luokka ei ole yhteensopiva java.io.InvalidClassException: InventoryItem; Paikallinen luokka ei ole yhteensopiva osoitteessa java.io.ObjectStreamClass.setClass (ObjectStreamClass.java:219) osoitteessa java.io.ObjectInputStream.inputClassDescriptor (ObjectInputStream.java:639) osoitteessa java.io.ObjectInputStream.readObject (ObjectInputStream.ja java.io.ObjectInputStream.inputObject (ObjectInputStream.java:820) osoitteessa java.io.ObjectInputStream.readObject (ObjectInputStream.java:284) osoitteessa Demo8a.main (Demo8a.java:56) 

Voi, jätkä! Mitä tapahtui?

java.io.ObjectInputStream ei kirjoita luokan objekteja, kun se luo objektia edustavan tavuvirran. Sen sijaan se kirjoittaa a java.io.ObjectStreamClass, joka on kuvaus luokan. Kohde-JVM: n luokan lataaja käyttää tätä kuvausta luokan tavukoodien löytämiseen ja lataamiseen. Se myös luo ja sisältää 64-bittisen kokonaisluvun nimeltä a SerialVersionUID, joka on eräänlainen avain, joka yksilöi luokkatiedoston version.

SerialVersionUID on luotu laskemalla 64-bittinen turvallinen hajautusarvo seuraavista luokan tiedoista. Sarjausmekanismi haluaa pystyä havaitsemaan muutokset missä tahansa seuraavista asioista:

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