Serializare

Utilizand fluxurile putem scrie aplicatii care salveaza si incarca datele in fisiere. Java permite si un mecanism mai avansat si anume serializarea obiectelor. In forma cea mai simpla serializarea obiectelor inseamna salvarea si restaurarea starii obiectelor. Obiectele oricarei clase care implementeaza interfata Serializable, pot fi salvate  intr-un stream(fluxde date) si restaurate  din acesta.  Pachetul java.io continedoua clase specialeObjectInputStream respectiv ObjectOutputStream pentru serializarea tipurilor primitive. Subclasele claselor serializabile vor fi si ele serializabile. Mecanismul de serializare salveaza variabilele membri nestatice si netransiente. Daca un obiect este serializat, atunciorice alt obiect care contine o referinta la obiectul serializat va sisi el insusi serializat. Se poate serializa orice multime de obiecte interconectateintr-o structura de graf.
 
Utilizand serializarea putem salva obictele create pe o platforma si le putem transmite prin retea pe un alt calculator care nu trebuie neaparat sa se ruleze pe aceasi platforma, totusi permite restauraea corecta a obiectelor.

Pentru a intelege serializarea vom trata urmatoarele elemente Java:
  1. interfata Serializable
  2. interfata Externalizable
  3. modificatorul transient

1. Interfata Serializable

public interface Serializable

Aceasta interfata nu declara nici metode, nici atribute, serveste doar pentru identificarea obiectelor serializabile. Clasele care nu implementeaza aceasta interfata nu vor putea fi serializate sau deserializate. Operatia de deserializare presupune ca clasa obievtului poseda si un constructor cu vizibilitate publica si fara argumente. Atributele obiectului vor fi initializate dintr-un flux de date. In cazul serializarii unui obiect care apartine unui graf de obiecte daca se ajunge la un obiect care nu implementeaza interfata Serializable, atunci se genereaza exceptia NoSerializableException . Clasele care necesita o tratare speciala pe parcursul serializarii si a deserializarii trebuie sa implementeze urmatoarele doua metode cu urmatoarele signaturi:

 private void writeObject(java.io.ObjectOutputStream out)
     throws IOException
 private void readObject(java.io.ObjectInputStream in)
     throws IOException, ClassNotFoundException;
 

Metoda writeObject()  este responsabila pentru salvarea starii obiectului, iar metoda readObject() are responsabilitatea restaurarii starii obiectului. Salvarea starii, adica a atributelor intr-un flux ObjectOutputStream se efectueaza cu metoda writeObject() respectiv cu metodele writeInt(), writeDouble() etc., metodele interfetei DataOutput.

import java.util.*;
import java.io.*;

class AccountSer implements Serializable {
   private String username;
   private String password;

   public AccountSer( String username, String password ){
       System.out.println("Constructor Account( String, String )");
       this.username = username;
       this.password = password;
   }

   public AccountSer(){
       System.out.println("Default Constructor Account()");
      
   }

   public void readObject( ObjectInputStream in ) throws ClassNotFoundException
   {
       System.out.println("Method readObject, class Account");
       try{
           username = ( String )in.readObject();
           password = ( String )in.readObject();       
       }
       catch( IOException e ){
           System.out.println(e);
       }
   }

   public void writeObject( ObjectOutputStream out ) throws IOException
   {
       System.out.println("Method writeObject, class Account");
       out.writeObject( username );
       out.writeObject( password );
  
   }

   public String toString(){
       return username+password;
   }

   public static void main( String args[] )
   {
       System.out.println("Creating an object");
       AccountSer a = new AccountSer("MyName","MyPassword");
       try{
           System.out.println("Saving the object");
           ObjectOutputStream out = new ObjectOutputStream(
               new FileOutputStream("Account.ser"));
           out.writeObject( a );
           out.close();

           System.out.println("Restoring the object");
           ObjectInputStream in = new ObjectInputStream( new FileInputStream("Account.ser"));
           Account b = (Account) in.readObject();
           in.close();
           System.out.println( b );           

       }
       catch( Exception e)
       {
           System.out.println( e );
       }
   }
}

2. Interfata Externalizable

Utilizand aceasta interfata avem posibilitatea de a controla cum se salveaza starea unui obiect. Adica putem sa salvam doar valorile anumitor atribute. In exemplul precedent am salvat si atributul password, iar daca obiectul este transmis prin retea si este interceptat de catre cineva, atunci oricine poate sa deserializeze, afland astfel parola utilizatorului. Vom rescrie exemplul uttilizand doar salvarea atributului username.
Interfata Externalizable extinde interfata Serializable, adaugand doua metode:
void readExternal(ObjectInput in)
    Obiectul care implementeaza aceasta metoda poate s-o utilizeze pentru restaurarea starii obiectelor cu  metodele readObject() pentru obiecte oarecare, siruri de caractere(String) si tablouri, iar metodele din DataInput pentru tipuri de date primitive.          
 void writeExternal(ObjectOutput  out)
          Obiectul care implementeazaaceasta metoda poate s-o utilizeze pentru salvarea starii obiectelor cu  metodele readObject() pentru obiecte oarecare, siruri de caractere(String)si tablouri, iar metodele din DataInput pentru tipuri de date primitive.          

In exemplul urmator nu slavam atributul password, dar incercam sa restaram la deserializarea obiectului, astfel obtinem valoarea null pentru acest atribut.

import java.util.*;
import java.io.*;

class Account implements Externalizable {
   private String username;
   private String password;

   public Account( String username, String password ){
       System.out.println("Constructor Account( String, String )");
       this.username = username;
       this.password = password;
   }

   public Account(){
       System.out.println("Default Constructor Account()");
      
   }

   public void readExternal( ObjectInput in ) throws ClassNotFoundException
   {
       System.out.println("Method readExternal, class Account");
       try{
           username = ( String )in.readObject();
           password = ( String )in.readObject();       
       }
       catch( IOException e ){
           System.out.println(e);
       }
   }

   public void writeExternal( ObjectOutput out ) throws IOException
   {
       System.out.println("Method writeExternal, class Account");
       out.writeObject( username );
      
  
   }

   public String toString(){
       return username+password;
   }

   public static void main( String args[] )
   {
       System.out.println("Creating an object");
       Account a = new Account("MyName","MyPassword");
       try{
           System.out.println("Saving the object");
           ObjectOutputStream out = new ObjectOutputStream(
               new FileOutputStream("Account.txt"));
           out.writeObject( a );
           out.close();

           System.out.println("Restoring the object");
           ObjectInputStream in = new ObjectInputStream( new FileInputStream("Account.txt"));
           Account b = (Account) in.readObject();
           in.close();
           System.out.println( b );           

       }
       catch( Exception e)
       {
           System.out.println( e );
       }
   }
}

3. Modificatorul transient

Se utilizeaza pentru atribute care nu trebuie serializate. In exemplul precedent ar trebui sa declaram transient atributul password.

  Un exemplu de program client-server care lucreaza cu obiecte

In exemplele de pana acum prin soclurile au fost utilizate pentru trimiterea unor date primitive. In scrierea si citirea acestor date am utilizat fluxurile DataOutputStream respectiv DataInputStream. Daca dorim insa sa trimitem obiecte intre aplicatii care se ruleaza pe diferite hosturiatunci va trebui sa utilizam mecanismul de serializare. Fluxurile ObjectInputStream si ObjectOutputStream sunt utilizate ca fluxuri pentru obiecte serializate. Inainte de scrierea obiectului cu metoda writeObject() obiectulva fi serializat, iar dupa citirea obiectului cu metoda readObject(), obiectul se deserializeaza.

In exemplul urmator vom construi un server care rezolva cateva tipuri de cereri. Gama serviciilor oferite de acest server se poate extinde prin adaugarea a noi clase la aplicatie.Pentru diferite tipuri de cereri seintroduce o clasa de baza serializabila cu numele Cerere.

public class Cerere implements Serializable {
}
Aceasta clasa fiind serializabila toate clasele derivate din aceasta vorfi implicit serializabile. Vom deriva din aceasta clasa o clasa CerereData .Clasa defineste o cerere pentru data calendaristica. Ori de cate ori serverul primeste un asemenea obiect trimite ca si raspuns un obiect de tip java.util.Date. O alta clasa derivata din aceasta esete CerereCalcul care va fi  superclasa tuturor claselor cereri de tip calcul. Aceasta clasa va avea o metoda Objectexecute() pentru a executa calcului dorit. De fapt clasa CerereCalcul serveste ca clasa de baza si de aceea la acest nivel nu efectueza niciun calcul, returneaza null. Clasele derivate vor redefini aceasta metodacompletand cu efectuarea calculelor specifice acelor clase. Un exemplude clasa care efectueaza un calcul va fi clasa CerereRidicareLaPutere.

Serverul va avea o arhitectura paralela, pentru fiecare client se creaza un fir de executie ( un obiect DeservireClient ) si toate cererile suntrezolvate de catre acesta.

Diagrama de clasa a aplicatiei:

Sursele:

import java.io.Serializable;

public class Cerere implements Serializable 
{
}
public class CerereData extends Cerere 
{
}
public class CerereCalcul extends Cerere 
{

        public java.lang.Object execute() 
        {
                return null;
        }
}
public class CerereRidicareLaPatrat extends CerereCalcul 
{
        private int n;


        public CerereRidicareLaPatrat(int n) 
        {
                this.n = n;
        }


        public java.lang.Object execute() 
        {
                return new Integer( n* n );
        }
}
import java.io.IOException;

public class Server 
{

        public static void main(String[] args) throws IOException 
        {       
                if( args.length != 1 ){
                        System.out.println("Utilizare java Server <port>");
                        System.exit( 0 );
                }
                java.net.ServerSocket ss = new java.net.ServerSocket( Integer.parseInt( args[ 0 ] )); 
                while( true )
                        new DeservireClient( ss.accept() ).start();
        }
}
import java.net.Socket;
import java.net.SocketException;

public class DeservireClient extends Thread 
{
        private Socket clientSocket;

        public DeservireClient(java.net.Socket clientsocket) throws SocketException 
        {
                this.clientSocket = clientsocket;
        }


        public void run() 
        {
                try{
                        java.io.ObjectInputStream in = new java.io.ObjectInputStream( clientSocket.getInputStream());
                        java.io.ObjectOutputStream out = new java.io.ObjectOutputStream( clientSocket.getOutputStream());
                        while( true ){
                                out.writeObject( executaCerere( in.readObject()));
                                out.flush();
                        }
                }
                // Clientul inchide conexiunea
                catch( java.io.EOFException e1 ){
                        try{
                                clientSocket.close();
                        }
                        catch( java.io.IOException e2 ){ System.out.println( e2 ); }
                }
                // Eroare I/O
                catch( java.io.IOException e2 ){ System.out.println( e2 ); }

                // Cerere necunoscuta
                catch( ClassNotFoundException e3 ){ System.out.println( e3 ); }
        }

        public java.lang.Object executaCerere(java.lang.Object cerere) 
        {
                if( cerere instanceof CerereData )
                        return new java.util.Date();
                else
                        if( cerere instanceof CerereCalcul )
                                return ((CerereCalcul) cerere).execute();
                        else
                                return null;

        }
}
public class Client 
{

        public static void main(String[] args) 
        {
                if( args.length != 2 )
                {
                        System.out.println("Utilizare java Client <numehost> <porthost> ");
                        System.exit( 0 );
                }
                try{
                        java.net.Socket socket = new java.net.Socket( args[ 0 ] , Integer.parseInt( args[ 1 ] ));
                        java.io.ObjectOutputStream out = new java.io.ObjectOutputStream( socket.getOutputStream());
                        java.io.ObjectInputStream in = new java.io.ObjectInputStream( socket.getInputStream());
                        // Se trimite prima cerere
                        out.writeObject( new CerereData() );
                        out.flush();
                        System.out.println( in.readObject());
                        // Se trimite a doua cerere
                        out.writeObject( new CerereRidicareLaPatrat( 5 ) );
                        out.flush();
                        System.out.println( in.readObject());

                        socket.close();
                }
                // Eroare I/O
                catch( java.io.IOException e ){
                        System.out.println( e );
                }
                // Tip raspuns necunoscut
                catch( ClassNotFoundException e1 ){
                        System.out.println( e1 );
                }


        }
}