Programavimas

Tai yra sutartyje! „JavaBeans“ objektų versijos

Per pastaruosius du mėnesius mes nuodugniai išnagrinėjome, kaip susisteminti „Java“ objektus. (Žr. „Serializavimas ir„ JavaBeans “specifikacija“ ir „Padarykite tai„ Nescafé “būdu - su liofilizuotais„ JavaBeans “.) Šio mėnesio straipsnyje daroma prielaida, kad jūs arba jau perskaitėte šiuos straipsnius, arba suprantate juose aptariamas temas. Turėtumėte suprasti, kas yra serializavimas, kaip naudoti Serijinis sąsaja ir kaip naudotis java.io.ObjectOutputStream ir java.io.ObjectInputStream klasės.

Kodėl jums reikia versijų

Tai, ką daro kompiuteris, lemia jo programinė įranga, o programinę įrangą labai lengva pakeisti. Šis lankstumas, paprastai laikomas turtu, turi savo įsipareigojimų. Kartais atrodo, kad programinė įranga yra taip pat lengva pakeisti. Neabejotinai susidūrėte su bent viena iš šių situacijų:

  • El. Paštu gautas dokumento failas nebus tinkamai nuskaitytas jūsų teksto redagavimo priemonėje, nes jūsų yra senesnė versija su nesuderinamu failo formatu

  • Skirtingose ​​naršyklėse tinklalapis veikia skirtingai, nes skirtingos naršyklės versijos palaiko skirtingus funkcijų rinkinius

  • Programa nebus vykdoma, nes turite netinkamą konkrečios bibliotekos versiją

  • C ++ nebus kompiliuojamas, nes antraštės ir šaltinio failai yra nesuderinamų versijų

Visas šias situacijas sukelia nesuderinamos programinės įrangos versijos ir (arba) duomenys, kuriais programinė įranga manipuliuoja. Kaip ir pastatai, asmeninės filosofijos ir upių vagos, programos nuolat keičiasi, atsižvelgdamos į besikeičiančias aplinkines sąlygas. (Jei nemanote, kad pastatai keičiasi, perskaitykite puikią Stewarto Brando knygą Kaip mokosi pastatai, diskusija apie tai, kaip struktūros laikui bėgant keičiasi. Daugiau informacijos žr. Ištekliai.) Neturint struktūros, leidžiančios kontroliuoti ir valdyti šį pakeitimą, bet kokia bet kokio naudingo dydžio programinės įrangos sistema galiausiai perauga į chaosą. Programinės įrangos tikslas versijimas yra užtikrinti, kad programinės įrangos, kurią šiuo metu naudojate, versija pateikia teisingus rezultatus, kai ji aptinka kitų savo versijų duomenis.

Šį mėnesį aptarsime, kaip veikia „Java“ klasės versijos, kad galėtume užtikrinti „JavaBeans“ versijų valdymą. „Java“ klasių versijų struktūra leidžia jums nurodyti serializavimo mechanizmui, ar tam tikrą duomenų srautą (tai yra serijinį objektą) gali nuskaityti tam tikra „Java“ klasės versija. Kalbėsime apie „suderinamus“ ir „nesuderinamus“ klasių pakeitimus ir kodėl šie pakeitimai turi įtakos versijoms. Peržiūrėsime versijų struktūros tikslus ir kaip java.io paketas atitinka šiuos tikslus. Išmoksime įdėti apsaugos priemones į savo kodą, kad užtikrintume, jog skaitant įvairių versijų objektų srautus, duomenys visada bus nuoseklūs perskaičius objektą.

Versijos vengimas

Programinėje įrangoje yra įvairių versijų problemų, kurios visos susijusios su duomenų dalių ir (arba) vykdomojo kodo suderinamumu:

  • Skirtingos tos pačios programinės įrangos versijos gali arba negalės tvarkyti vienas kito duomenų saugojimo formatų

  • Programos, įkeliančios vykdomąjį kodą vykdymo metu, turi sugebėti nustatyti teisingą programinės įrangos objekto, įkeliamos bibliotekos ar objekto failo versiją, kad atliktų užduotį.

  • Klasės metodai ir laukai turi išlaikyti tą pačią prasmę, kaip ir klasė, arba esamos programos gali sugesti vietose, kur naudojami tie metodai ir laukai

  • Šaltinio kodas, antraštės failai, dokumentacija ir komponavimo scenarijai turi būti suderinti programinės įrangos kūrimo aplinkoje, siekiant užtikrinti, kad dvejetainiai failai būtų sukurti iš teisingų šaltinio failų versijų

Šis straipsnis apie „Java“ objektų versijas skirtas tik pirmiesiems trims - tai yra dvejetainių objektų versijų valdymui ir jų semantikai vykdymo aplinkoje. (Yra daugybė programinės įrangos, leidžiančios versijos šaltinio kodą, tačiau čia to nenagrinėjame.)

Svarbu atsiminti, kad nuosekliuose „Java“ objektų srautuose nėra baitų kodų. Juose yra tik informacija, reikalinga objektui rekonstruoti darant prielaidą jūs turite klasės failus, skirtus objektui sukurti. Bet kas atsitiks, jei dviejų „Java“ virtualiųjų mašinų (JVM) (rašytojo ir skaitytojo) klasės failai yra skirtingų versijų? Kaip sužinoti, ar jie suderinami?

Klasės apibrėžimas gali būti suprantamas kaip „sutartis“ tarp klasės ir kodo, kuris vadina klasę. Ši sutartis apima klasės API (Taikomųjų programų programavimo sąsaja). API pakeitimas prilygsta sutarties pakeitimui. (Kiti klasės pakeitimai taip pat gali reikšti sutarties pakeitimus, kaip matysime.) Besivystant klasei svarbu išlaikyti ankstesnių klasės versijų elgesį, kad nesulaužytų programinės įrangos tose vietose, kurios priklausė nuo duotas elgesys.

Versijų keitimo pavyzdys

Įsivaizduokite, kad turite metodą, vadinamą getItemCount () klasėje, o tai reiškė gauti bendrą daiktų, esančių šiame objekte, skaičių, ir šis metodas buvo naudojamas keliolikoje vietų jūsų sistemoje. Tada įsivaizduokite, kad vėliau pasikeisite getItemCount () reikšti gauti maksimalų daiktų, kuriuos turi šis objektas, skaičių kada nors sulaikytas. Jūsų programinė įranga greičiausiai suges daugelyje vietų, kuriose buvo naudojamas šis metodas, nes staiga metodas praneš apie kitą informaciją. Iš esmės jūs sulaužėte sutartį; taigi tai jums gerai, kad jūsų programoje dabar yra klaidų.

Negalima visiškai atsisakyti pakeitimų, kad būtų galima visiškai automatizuoti tokio pobūdžio pokyčių aptikimą, nes tai vyksta programos lygiu reiškia, ne tik to, kaip ta prasmė yra išreikšta. (Jei sugalvosite, kaip tai padaryti paprastai ir paprastai, būsite turtingesni už Bilą.) Taigi, jei nėra išsamaus, bendro ir automatinio šios problemos sprendimo, gali mes stengiamės išvengti patekimo į karštą vandenį, kai keičiame klases (ko, žinoma, privalome)?

Lengviausias atsakymas į šį klausimą yra pasakyti, kad jei klasė keičiasi iš viso, nereikėtų „patikėti“ išlaikyti sutartį. Galų gale programuotojas galėjo ką nors padaryti klasei ir kas žino, ar klasė vis dar veikia taip, kaip skelbiama? Tai išsprendžia versijų problemą, tačiau tai yra nepraktiškas sprendimas, nes jis pernelyg ribojantis. Tarkime, jei klasė modifikuojama siekiant pagerinti našumą, nėra jokios priežasties neleisti naudoti naujos klasės versijos vien todėl, kad ji neatitinka senosios. Nepažeidžiant sutarties, klasėje gali būti padarytas bet koks pakeitimų skaičius.

Kita vertus, kai kurie klasių pakeitimai praktiškai garantuoja sutarties pažeidimą: pavyzdžiui, lauko ištrynimas. Jei ištrinsite lauką iš klasės, vis tiek galėsite skaityti srautus, parašytus ankstesnėmis versijomis, nes skaitytojas visada gali nepaisyti to lauko vertės. Bet pagalvokite apie tai, kas nutinka, kai rašote srautą, kurį ketina perskaityti ankstesnės klasės versijos. To lauko vertės sraute nebus, o senesnė versija priskirs (galbūt logiškai nesuderinamą) numatytąją reikšmę tam laukui, kai jis skaitys srautą. Voilà!: Jūs turite sulaužytą klasę.

Suderinami ir nesuderinami pakeitimai

Objektų versijų suderinamumo valdymo gudrybė yra nustatyti, kurie pakeitimai gali sukelti versijų nesuderinamumą, o kurie - ne, ir skirtingai traktuoti šiuos atvejus. Kalbant „Java“ kalba, vadinami pakeitimai, kurie nesudaro suderinamumo problemų suderinamas pokyčiai; tie, kurie gali būti vadinami nesuderinamas pokyčiai.

„Java“ serializavimo mechanizmo kūrėjai, sukurdami sistemą, turėjo omenyje šiuos tikslus:

  1. Apibrėžti būdą, kaip naujesnė klasės versija gali skaityti ir rašyti srautus, kuriuos ankstesnė klasės versija taip pat gali „suprasti“ ir tinkamai naudoti

  2. Pateikti numatytąjį mechanizmą, kuris sujungia objektus su geru našumu ir tinkamu dydžiu. Tai yra serializavimo mechanizmas mes jau aptarėme dviejuose ankstesniuose „JavaBeans“ stulpeliuose, paminėtuose šio straipsnio pradžioje

  3. Norėdami sumažinti su versijomis susijusį darbą klasėse, kurioms nereikia versijų. Idealiu atveju versijų informaciją klasėje reikia pridėti tik tada, kai pridedamos naujos versijos

  4. Norėdami suformatuoti objekto srautą taip, kad objektus būtų galima praleisti neįkeliant objekto klasės failo. Ši galimybė leidžia kliento objektui pereiti objektų srautą, kuriame yra nesuprantamų objektų

Pažiūrėkime, kaip serializavimo mechanizmas siekia šių tikslų, atsižvelgiant į aukščiau išdėstytą situaciją.

Derinami skirtumai

Kai kurie klasės failo pakeitimai gali priklausyti nuo to, ar nepakeis klasės sutarties ir kaip ją vadins kitos klasės. Kaip minėta pirmiau, tai vadinama suderinamais „Java“ dokumentacijos pakeitimais. Nepakeitus sutarties, klasės faile gali būti padarytas bet koks suderinamų pakeitimų skaičius. Kitaip tariant, dvi klasės versijos, kurios skiriasi tik suderinamais pakeitimais, yra suderinamos klasės: naujesnėje versijoje toliau bus skaitomi ir rašomi objektų srautai, suderinami su ankstesnėmis versijomis.

Klasės java.io.ObjectInputStream ir java.io.ObjectOutputStream nepasitiki tavimi. Pagal numatytuosius nustatymus jie yra labai įtartini dėl bet kokių klasės failo sąsajos su pasauliu pakeitimų - tai reiškia, kad viskas matoma bet kuriai kitai klasei, kuri gali naudoti šią klasę: viešųjų metodų ir sąsajų parašai bei tipai ir modifikatoriai. viešųjų laukų. Tiesą sakant, jie yra tokie paranojiški, kad vargu ar ką nors gali pakeisti klasėje, nesukeldamas to java.io.ObjectInputStream atsisakyti įkelti srautą, parašytą ankstesnės klasės versijos.

Pažvelkime į pavyzdį. klasės nesuderinamumo ir tada išspręskite kylančią problemą. Tarkime, kad turite objektą, vadinamą InventoryItem, kuriame saugomi sandėlyje esančių detalių numeriai ir kiekis. Paprasta to objekto forma kaip „JavaBean“ gali atrodyti maždaug taip:

001 002 importuoti java.beans. *; 003 importuoti java.io. *; 004 importas spausdinamas; 005 006 // 007 // 1 versija: tiesiog saugokite kiekį rankoje ir dalies numerį 008 // 009 010 public class InventoryItem implementals Serializable, Printable {011 012 013 014 015 016 // laukai 017 apsaugoti int iQuantityOnHand_; 018 saugoma eilutė 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 viešoji eilutė getPartNo () 043 {044 return sPartNo_; 045} 046 047 public void setPartNo (String _sPartNo) 048 {049 sPartNo_ = _sPartNo; 050} 051 052 // ... įgyvendina spausdinamą 053 public void print () 054 {055 System.out.println ("Part:" + getPartNo () + "\ nKiekis ranka:" + 056 getQuantityOnHand () + "\ n \ n "); 057} 058}; 059 

(Mes taip pat turime paprastą pagrindinę programą, vadinamą Demonstracija8a, kuris skaito ir rašo InventoryItems į ir iš failo, naudojant objektų srautus ir sąsają Spausdinti, kuris InventoryItem įgyvendina ir Demonstracija8a naudoja objektams spausdinti. Šių šaltinį galite rasti čia.) Vykdant demonstracinę programą gaunami pagrįsti, jei ne jaudinantys, rezultatai:

C: \ beans> java Demo8a w file SA0091-001 33 Parašytas objektas: dalis: SA0091-001 Kiekis po ranka: 33 C: \ pupos> java Demo8a r failas Skaityti objektą: Dalis: SA0091-001 Kiekis po ranka: 33 

Programa teisingai serializuoja ir deserializuoja objektą. Dabar atlikime nedidelį klasės failo pakeitimą. Sistemos vartotojai atliko inventorizaciją ir nustatė neatitikimų tarp duomenų bazės ir faktinio prekių skaičiaus. Jie paprašė galimybės stebėti iš sandėlio prarastų daiktų skaičių. Pridėkime vieną viešą lauką InventoryItem tai rodo daiktų, kurių trūksta sandėliuke, skaičių. Įterpiame šią eilutę į InventoryItem klasę ir kompiliuokite:

016 // laukai 017 apsaugotas int iQuantityOnHand_; 018 saugoma eilutė sPartNo_; 019 public int iQuantityLost_; 

Failas kompiliuojamas gerai, bet pažiūrėkite, kas nutinka, kai bandome perskaityti ankstesnės versijos srautą:

C: \ mj-java \ Column8> java Demo8a r failo IO išimtis: InventoryItem; Vietinė klasė nesuderinama java.io.InvalidClassException: InventoryItem; Vietinė klasė nesuderinama adresu java.io.ObjectStreamClass.setClass (ObjectStreamClass.java:219) adresu java.io.ObjectInputStream.inputClassDescriptor (ObjectInputStream.java:639) adresu java.io.ObjectInputStream.readObject (ObjectInputStream.ja java.io.ObjectInputStream.inputObject (ObjectInputStream.java:820) at java.io.ObjectInputStream.readObject (ObjectInputStream.java:284) at Demo8a.main (Demo8a.java:56) 

Oi, bičiuli! Kas nutiko?

java.io.ObjectInputStream nerašo klasės objektų, kai kuria objektų vaizdavimo baitų srautą. Užuot rašęs a java.io.ObjectStreamClass, kuris yra a apibūdinimas klasės. Paskirties JVM klasės krautuvas naudoja šį aprašą, norėdamas rasti ir įkelti klasės baitekodus. Taip pat sukuriamas ir įtraukiamas 64 bitų sveikasis skaičius, vadinamas a „SerialVersionUID“, kuris yra tam tikras raktas, unikaliai identifikuojantis klasės failo versiją.

„SerialVersionUID“ yra sukurtas apskaičiuojant 64 bitų saugų maišos informaciją apie šią klasę. Serijinis mechanizmas nori, kad būtų galima aptikti bet kurio iš šių dalykų pokyčius:

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