Programmēšana

Tas ir līgumā! Objekta versijas JavaBeans

Pēdējo divu mēnešu laikā mēs esam iedziļinājušies zināmā mērā par to, kā serializēt objektus Java. (Skat. "Serializācija un JavaBeans specifikācija" un "Dariet to" Nescafé "veidā - ar liofilizētām JavaBeans.") Šī mēneša rakstā tiek pieņemts, ka jūs vai nu jau esat lasījis šos rakstus, vai arī saprotat to aptvertās tēmas. Jums vajadzētu saprast, kas ir serializācija, kā to izmantot Serializējams interfeisu un kā to izmantot java.io.ObjectOutputStream un java.io.ObjectInputStream klases.

Kāpēc jums nepieciešama versiju izstrāde

To, ko dara dators, nosaka tā programmatūra, un programmatūru ir ārkārtīgi viegli mainīt. Šai elastībai, ko parasti uzskata par aktīvu, ir savas saistības. Dažreiz šķiet, ka programmatūra ir arī viegli mainīt. Jūs neapšaubāmi esat nonācis vismaz vienā no šīm situācijām:

  • E-pastā saņemtais dokumenta fails jūsu teksta redaktorā netiks pareizi nolasīts, jo jums ir vecāka versija ar nesaderīgu faila formātu

  • Dažādās pārlūkprogrammās vietne darbojas atšķirīgi, jo dažādas pārlūka versijas atbalsta dažādas funkciju kopas

  • Lietojumprogramma nedarbosies, jo jums ir nepareiza konkrētas bibliotēkas versija

  • Jūsu C ++ netiks apkopots, jo galvenes un avota faili ir nesaderīgas versijas

Visas šīs situācijas izraisa programmatūras nesaderīgas versijas un / vai programmatūras manipulētie dati. Tāpat kā ēkas, personīgās filozofijas un upju gultnes, programmas pastāvīgi mainās, reaģējot uz mainīgajiem apstākļiem ap tām. (Ja jūs nedomājat, ka ēkas mainās, izlasiet Stjuarta Brenda izcilo grāmatu Kā mācās ēkas, diskusija par to, kā struktūras laika gaitā pārveidojas. Plašāku informāciju skatiet resursos.) Bez struktūras, kas kontrolētu un pārvaldītu šīs izmaiņas, jebkura jebkura noderīga lieluma programmatūras sistēma galu galā pārvēršas haosā. Mērķis programmatūrā versijas ir pārliecināties, ka programmatūras versija, kuru pašlaik izmantojat, sniedz pareizus rezultātus, saskaroties ar datiem, ko rada citas pašas versijas.

Šomēnes mēs apspriedīsim, kā darbojas Java klases versijas, lai mēs varētu nodrošināt savu JavaBeans versiju kontroli. Java klašu versiju struktūra ļauj jums norādīt serializācijas mehānismam, vai konkrēta datu plūsma (tas ir, sērijveida objekts) ir nolasāma noteiktā Java klases versijā. Mēs runāsim par "saderīgām" un "nesaderīgām" izmaiņām klasēs un par to, kāpēc šīs izmaiņas ietekmē versiju veidošanu. Mēs izskatīsim versiju struktūras mērķus un to, kā java.io pakete atbilst šiem mērķiem. Mēs iemācīsimies ieviest kodā drošības pasākumus, lai nodrošinātu, ka, lasot dažādu versiju objektu straumes, dati vienmēr ir konsekventi pēc objekta lasīšanas.

Izvairīšanās no versijas

Programmatūrā ir dažādas versiju veidošanas problēmas, kas visas attiecas uz saderību starp datu gabaliem un / vai izpildāmo kodu:

  • Dažādas vienas un tās pašas programmatūras versijas var vai nevarēs apstrādāt citu datu glabāšanas formātus

  • Programmām, kuras izpildes laikā ielādē izpildāmo kodu, jāspēj identificēt programmatūras objekta, ielādējamas bibliotēkas vai objekta faila pareizo versiju, lai veiktu šo darbu.

  • Klases metodēm un laukiem jāsaglabā tā pati nozīme kā klases attīstībai, vai arī esošās programmas var salūzt vietās, kur tiek izmantotas šīs metodes un lauki

  • Pirmkods, galvenes faili, dokumentācija un būvēšanas skripti programmatūras būvēšanas vidē ir jākoordinē, lai nodrošinātu, ka bināros failus veido pareizās avota failu versijas

Šis raksts par Java objektu versijām attiecas tikai uz pirmajiem trim - tas ir, bināro objektu versiju kontroli un to semantiku izpildlaika vidē. (Avota koda versijai ir pieejams plašs programmatūras klāsts, taču mēs to šeit neaptveram.)

Ir svarīgi atcerēties, ka sērijveida Java objektu straumēs nav baitkodu. Tie satur tikai objekta rekonstrukcijai nepieciešamo informāciju pieņemot objekta izveidei jums ir pieejami klases faili. Bet kas notiek, ja divu Java virtuālo mašīnu (JVM) (rakstītāja un lasītāja) klases failiem ir dažādas versijas? Kā mēs varam zināt, vai tie ir saderīgi?

Klases definīciju var uzskatīt par "līgumu" starp klasi un kodu, kas izsauc klasi. Šis līgums ietver klases API (lietojumprogrammu saskarne). API maiņa ir līdzvērtīga līguma maiņai. (Citas klases izmaiņas var nozīmēt arī līguma izmaiņas, kā mēs to redzēsim.) Klasei attīstoties, ir svarīgi saglabāt iepriekšējo klases versiju uzvedību, lai nesalauztu programmatūru vietās, kas bija atkarīgas no tā dota uzvedība.

Versijas maiņas piemērs

Iedomājieties, ka jums ir metode, ko sauc getItemCount () klasē, kas nozīmēja iegūt kopējo objektu skaitu, ko šis objekts satur, un šī metode tika izmantota divpadsmit vietās visā jūsu sistēmā. Tad vēlāk iedomājieties, ka maināties getItemCount () nozīmēt iegūt maksimālo objektu skaitu, kāds ir šim objektam jebkad saturēts. Jūsu programmatūra, visticamāk, saplīsīs lielākajā daļā vietu, kur tika izmantota šī metode, jo pēkšņi metode ziņos par citu informāciju. Būtībā jūs esat pārkāpis līgumu; tāpēc tas jums labi noder, ka jūsu programmā tagad ir kļūdas.

Nekādā gadījumā nevar pilnībā automatizēt izmaiņas, lai pilnībā automatizētu šāda veida izmaiņu noteikšanu, jo tas notiek programmas līmenī nozīmē, ne tikai līmenī, kā šī nozīme tiek izteikta. (Ja jūs domājat par veidu, kā to izdarīt viegli un vispār, jūs būsiet bagātāks par Bilu.) Tātad, ja nav pilnīga, vispārēja un automatizēta šīs problēmas risinājuma, var mēs darām, lai izvairītos no nokļūšanas karstā ūdenī, kad mainām klases (kas, protams, mums ir jādara)?

Vieglākā atbilde uz šo jautājumu ir teikt, ka, ja klase mainās pavisam, nevajadzētu "uzticēties" līguma uzturēšanai. Galu galā programmētājs, iespējams, ir kaut ko darījis klasei, un kas zina, vai klase joprojām darbojas kā reklamēta? Tas atrisina versiju problēmu, taču tas ir nepraktisks risinājums, jo tas ir pārāk ierobežojošs. Ja klase ir pārveidota, lai uzlabotu sniegumu, teiksim, nav pamata atteikt jaunās klases versijas izmantošanu tikai tāpēc, ka tā neatbilst vecajai. Klasē var tikt izdarīts jebkurš skaits izmaiņu, nepārkāpjot līgumu.

No otras puses, dažas klases izmaiņas praktiski garantē līguma laušanu: piemēram, lauka dzēšana. Dzēšot lauku no klases, jūs joprojām varēsit lasīt straumes, kuras rakstījušas iepriekšējās versijas, jo lasītājs vienmēr var ignorēt šī lauka vērtību. Bet padomājiet par to, kas notiek, rakstot straumi, kas paredzēta lasīšanai klases iepriekšējās versijās. Šī lauka vērtība straumē nebūs, un vecākā versija, lasot straumi, šim laukam piešķirs (iespējams, loģiski pretrunīgu) noklusējuma vērtību. Voilà!: Tev ir salauzta klase.

Saderīgas un nesaderīgas izmaiņas

Objekta versiju savietojamības pārvaldības triks ir identificēt, kāda veida izmaiņas var izraisīt versiju nesaderību un kuras neizraisīs, un izturēties pret šiem gadījumiem atšķirīgi. Java valodā runājot, tiek izsauktas izmaiņas, kas nerada saderības problēmas savietojams izmaiņas; tie, kurus var saukt nesaderīgs izmaiņas.

Java sērijas mehānisma dizaineriem, izveidojot sistēmu, bija prātā šādi mērķi:

  1. Lai definētu veidu, kā jaunāka klases versija var lasīt un rakstīt straumes, kuras iepriekšējā klases versija arī var "saprast" un pareizi izmantot

  2. Nodrošināt noklusējuma mehānismu, kas sērijveidā objektus nodrošina ar labu veiktspēju un saprātīgu izmēru. Tas ir serializācijas mehānisms mēs jau esam apsprieduši divās iepriekšējās JavaBeans slejās, kas minētas šī raksta sākumā

  3. Lai samazinātu ar versijām saistīto darbu klasēs, kurām nav nepieciešama versiju izstrāde. Ideālā gadījumā informācija par versijām klasē jāpievieno tikai tad, kad tiek pievienotas jaunas versijas

  4. Lai formatētu objekta straumi tā, lai objektus varētu izlaist, neielādējot objekta klases failu. Šī spēja ļauj klienta objektam šķērsot objekta straumi, kurā ir objekti, kurus tas nesaprot

Apskatīsim, kā serializācijas mehānisms risina šos mērķus, ņemot vērā iepriekš izklāstīto situāciju.

Samierināmas atšķirības

Dažas klases failā veiktās izmaiņas var būt atkarīgas no tā, lai nemainītu līgumu starp klasi un kā citas klases to varētu nosaukt. Kā minēts iepriekš, Java dokumentācijā tās sauc par saderīgām izmaiņām. Nemainot līgumu, klases failā var veikt jebkādu skaitu saderīgu izmaiņu. Citiem vārdiem sakot, divas klases versijas, kas atšķiras tikai ar saderīgām izmaiņām, ir saderīgas klases: Jaunākā versija turpinās lasīt un rakstīt objektu straumes, kas ir saderīgas ar iepriekšējām versijām.

Nodarbības java.io.ObjectInputStream un java.io.ObjectOutputStream neuzticos tev. Tie ir paredzēti, lai pēc noklusējuma būtu ārkārtīgi aizdomīgi par visām klases faila saskarnes izmaiņām pasaulē - tas ir, jebkas, kas redzams jebkurai citai klasei, kas var izmantot šo klasi: publisko metožu un saskarņu paraksti un veidi un modifikatori publisko lauku. Patiesībā viņi ir tik paranoiski, ka gandrīz neko nevar mainīt klasē, to neizraisot java.io.ObjectInputStream atteikties ielādēt straumi, kuru ir uzrakstījusi iepriekšējā klases versija.

Apskatīsim piemēru. klases nesaderību un pēc tam atrisiniet radušos problēmu. Pieņemsim, ka jums ir objekts, ko sauc InventoryItem, kas uztur daļu numurus un konkrētās daļas daudzumu, kas pieejams noliktavā. Šī objekta kā JavaBean vienkārša forma varētu izskatīties apmēram šādi:

001 002 ievest java.beans. *; 003 importēt java.io. *; 004 imports Printable; 005 006 // 007 // Versija 1: vienkārši glabājiet daudzumu pie rokas un daļas numurs 008 // 009 010 public class InventoryItem implementates Serializable, Printable {011 012 013 014 015 016 // lauki 017 aizsargāti int iQuantityOnHand_; 018 aizsargāta virkne 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 public String getPartNo () 043 {044 return sPartNo_; 045} 046 047 public void setPartNo (String _sPartNo) 048 {049 sPartNo_ = _sPartNo; 050} 051 052 // ... ievieš drukājamu 053 public void print () 054 {055 System.out.println ("Part:" + getPartNo () + "\ nLīdzeklis pie rokas:" + 056 getQuantityOnHand () + "\ n \ n "); 057} 058}; 059 

(Mums ir arī vienkārša galvenā programma ar nosaukumu Demo8a, kas lasa un raksta InventoryItems uz un no faila, izmantojot objektu straumes un saskarni Drukājams, kas InventoryItem īsteno un Demo8a izmanto objektu drukāšanai. To avotu varat atrast šeit.) Demonstrācijas programmas palaišana sniedz saprātīgus, ja ne aizraujošus, rezultātus:

C: \ pupas> java Demo8a w fails SA0091-001 33 Uzrakstīts objekts: Daļa: SA0091-001 Daudzums uz rokas: 33 C: \ pupas> java Demo8a r fails Lasīt objektu: Daļa: SA0091-001 Daudzums uz rokas: 33 

Programma pareizi serializē un deserializē objektu. Tagad veiksim nelielas izmaiņas klases failā. Sistēmas lietotāji ir veikuši inventarizāciju un ir atraduši neatbilstības starp datu bāzi un faktisko vienību skaitu. Viņi ir pieprasījuši iespēju izsekot no noliktavas pazaudēto priekšmetu skaitu. Pievienosim vienotu publisko lauku InventoryItem kas norāda noliktavā trūkstošo priekšmetu skaitu. Mēs ievietojam šādu rindu InventoryItem klase un pārkompilēt:

016 // lauki 017 aizsargāts int iQuantityOnHand_; 018 aizsargāta virkne sPartNo_; 019 public int iQuantityLost_; 

Fails sastāda labi, taču apskatiet, kas notiek, kad mēģinām lasīt straumi no iepriekšējās versijas:

C: \ mj-java \ Column8> java Demo8a r fails IO izņēmums: InventoryItem; Vietējā klase nav savietojama java.io.InvalidClassException: InventoryItem; Vietējā klase nav saderīga vietnē java.io.ObjectStreamClass.setClass (ObjectStreamClass.java:219) vietnē java.io.ObjectInputStream.inputClassDescriptor (ObjectInputStream.java:639) vietnē java.io.ObjectInputStream.readObject (ObjectInputStream.ja java.io.ObjectInputStream.inputObject (ObjectInputStream.java:820) vietnē java.io.ObjectInputStream.readObject (ObjectInputStream.java:284) vietnē Demo8a.main (Demo8a.java:56) 

Ak, puisīt! Kas notika?

java.io.ObjectInputStream neraksta klases objektus, kad tas veido baitu plūsmu, kas attēlo objektu. Tā vietā tas raksta a java.io.ObjectStreamClass, kas ir a apraksts klases. Galamērķa JVM klases iekrāvējs izmanto šo aprakstu, lai atrastu un ielādētu klases baitkodus. Tas arī izveido un ietver 64 bitu veselu skaitli, ko sauc par a SerialVersionUID, kas ir sava veida atslēga, kas unikāli identificē klases faila versiju.

The SerialVersionUID tiek izveidots, aprēķinot šādas informācijas par klasi 64 bitu drošu jaukšanu. Serializācijas mehānisms vēlas spēt noteikt izmaiņas kādā no šīm lietām:

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