Технологии Java

Сериализация и RMI

Сериализация и десериализация

  • Сериализация – запись объекта в байтовый поток
  • Десериализация – чтение объекта из байтового потока
  • Пакет java.io
  • Сериализуется граф достижимых объектов

Сериализация объектов

Десериализация объектов

Что можно сериализовать

  • Автоматически сериализуемые классы
  • Классы, сериализуемые вручную

Как это устроено

Содержание

Автоматическая сериализация

  • Запись
    • Записывается предок
    • Записываются значения всех полей, не имеющих модификатора transient
  • Чтение
    • Выделяется память под объект
    • Считывается предок
    • Считываются значения всех полей, не имеющих модификатора transient

Автоматическая сериализация

  • public class Data implements Serializable {
        private List<Integer> data;
        ...
    }
    
  • public class Data2 extends Data {
        private transient String toStringCache;
        ...
    }
    
    • Почему transient?
    • Закэшированное значение можно посчитать заново

Сериализация вручную

Настраиваемая сериализация

  • Применяется для обеспечения обратной совместимости
  • Методы
    • readObject(ObjectInputStream in) – должен прочесть состояние из потока
    • writeObject(ObjectOutputStream out) – должен записать состояние в поток
  • Процесс чтения
    • Выделение памяти
    • Вызов метода readObject

Сериализация с несериал. предком

Версии сериализованных классов

  • Применяется для обеспечения совместимости когда версии сериализованного объекта меняются
  • Поле
    • private static final long serialVersionUID
      
  • Инструмент
    • serialver <имя класса>
      

Ручная сериализация полей

Пример ручной сериализации

Содержание

Пример ручной сериализации

  • Старая версия
    • class Rectangle {
          int x1, y1, x2, y2
          // toString
          String.format("%d %d %d %d", x1, y1, x2, y2);
      }
      
  • Новая версия
    • class Rectangle {
          int x1, y1, w, h;
          // toString
          String.format("%d %d %d %d", x1, y1, x1+w, y1+h);
      }
      

Ввод-вывод

  • Записываем старую версию
    • ObjectOutputStream oos = ...
      oos.writeObject(new Rectangle(1, 2, 10, 20));
      
  • Читаем новую версию
    • ObjectInputStream ois = ...
      System.out.println(ois.readObject());
      
  • Результат?
    • local class incompatible: stream classdesc serialVersionUID = 6729985638487173456, local class serialVersionUID = 7264412103486061129
    • Как чинить?

Добавим serialVersionUID

  • Изменения
    • private static final long serialVersionUID =
          6729985638487173456L;
      
  • Результат?
    • 1 2 1 2
    • Но как???
    • Лишние поля → пропускаются
    • Отсутствующие поля → значения по умолчанию
      w == h == 0
      

Добавим описание полей

  • Изменения
    • private static final ObjectStreamField[]
          serialPersistentFields = new ObjectStreamField[]{
              new ObjectStreamField("x1", int.class),
              new ObjectStreamField("y1", int.class),
              new ObjectStreamField("w", int.class),
              new ObjectStreamField("h", int.class)
          };
      
  • Результат?
    • 1 2 1 2
    • Ничего не поменялось

Добавим readObject

  • Изменения
    • private void readObject(final ObjectInputStream in) 
              throws IOException 
      {
          ObjectInputStream.GetField fields = in.readFields();
          x1 = fields.get("x1", 0);
          y1 = fields.get("y1", 0);
          w = fields.get("x2", 0) - x1;
          h = fields.get("y2", 0) - y1;
      }
      
  • Результат?
    • 1 2 10 20

Победа?

  • Записываем новую версию
    • ObjectOutputStream oos = ...
      oos.writeObject(new Rectangle(1, 2, 10, 20));
      
  • Читаем новую версию
    • ObjectInputStream ois = ...
      System.out.println(ois.readObject());
      
  • Результат?
    • no such field x2 with type int
    • Новая версия записывает w и h

Добавим writeObject

  • Изменения
    • private void writeObject(final ObjectOutputStream out) 
              throws IOException {
          ObjectOutputStream.PutField fields = out.putFields();
          fields.put("x1", x1);
          fields.put("y1", y1);
          fields.put("x2", x1 + w);
          fields.put("y2", y1 + h);
          out.writeFields();
      }
      
  • Результат?
    • no such field x2 with type int
    • В serialPersistentFields указаны w и h

Исправим описание полей

  • Изменения
    • private static final ObjectStreamField[]
          serialPersistentFields = new ObjectStreamField[]{
              new ObjectStreamField("x1", int.class),
              new ObjectStreamField("y1", int.class),
              new ObjectStreamField("x2", int.class),
              new ObjectStreamField("y2", int.class)
          };
      
  • Результат?
    • 1 2 10 20
    • Это победа!

Подменные объекты

Содержание

Мотивация

  • Что будет, если десериализовать enum?
    • Это работает
    • Новые элементы не создаются
  • Что будет, если десериализовать Class?
    • Это работает
    • Каждый Class остается уникальным

Подмена объекта

  • Применяется, когда сам объект не должен сериализовываться
  • Какой объект записать
  • Какой объект вернуть
    • Object readResolve()
    • Реализуются у разных классов

Remote Method Invocation

  • Механизм, позволяющий объектам из одной Java-машины вызывать методы другой Java-машины
  • Работает по сети
  • Пакет java.rmi

Схема взаимодействия

  • Обработка запроса
  • Возврат результата

Удалённые интерфейсы

  • Удалённый интерфейс – интерфейс, унаследованный от Remote
  • Все методы удалённых интерфейсов должны бросать RemoteException

Передача данных

  • Удалённые объекты
    • Удалённые ссылки
  • Остальные объекты
    • По значению
    • Сериализация

Stub и Skeleton

  • Stub и Skeleton генерируются по удалённому классу
  • Stub служит для передачи данных по сети
    • Реализует все удалённые интерфейсы класса и только их
  • Skeleton служит для приема данных по сети
    • Вызывает методы реального объекта

Мотивация

  • Когда можно убрать удалённый объект?
  • Когда на него нет ссылок
    • Локальные ссылки
    • Удалённые ссылки
  • Объекты могут передаваться по цепочке
    • Неизвестно кому
    • Даже нам самим

Механизм лизинга

  • Объект можно «одолжить» у владельца
    • На время T
  • Если объект нужен
    • Продлить «займ» через T/2
    • Может не удаться
  • Если объект не нужен
    • Ничего не делать

Distributed Garbage Collecting

  • Удаление объектов, на которые больше нет ссылок в распределенной среде
  • Интерфейс Unreferenced

Поиск удалённых объектов (1)

  • Ссылки на удалённые объекты публикуются в RMI registry
  • удалённые объекты ищутся по URL вида //<host>:<port>/<object>, где
    • host:port – местоположение RMI registry
    • object – зарегистрированное имя объекта

Поиск удалённых объектов (2)

  • Класс Naming – интерфейс к RMI registry
  • Методы класса

Экспорт объектов

Банк

  • Cчета идентифицируются строками
  • По счету можно
    • Узнать идентификатор
    • Узнать сумму денег на счете
    • Изменить сумму денег на счете

Удалённый интерфейс банка

  • public interface Bank extends Remote {
        // Создает счет
        public Account createAccount(String id)
            throws RemoteException;
         
        // Возвращает счет
        public Account getAccount(String id)
            throws RemoteException;
    }
    

Удалённый интерфейс счета

  • public interface Account extends Remote {
        // Идентификатор
        public String getId() throws RemoteException;
         
        // Количество денег
        public int getAmount() throws RemoteException;
         
        // Изменить количество денег
        public void setAmount(int amount)
            throws RemoteException;
    }
    

Реализация счета

  • class RemoteAccount implements Account {
        public String getId() {
            return id;
        }
        public int getAmount() {
            return amount;
        }
        public void setAmount(int amount) {
            this.amount = amount;
        }
    }
    

Реализация банка

  • class RemoteBank implements Bank {
        public Account createAccount(String id) {
            Account account = new RemoteAccount(id);
            accounts.put(id, account);
            return account;
        }
        public Account getAccount(String id) {
            return accounts.get(id);
        }
    }
    

Сервер

  • Регистрация банка в RMI registry
    • Bank bank = new RemoteBank();
      try {
          UnicastRemoteObject.exportObject(bank);
          Naming.rebind("//localhost/bank", bank);
      } catch (RemoteException e) {
          System.out.println("Cannot export object: " +
              e.getMessage());
      } catch (MalformedURLException e) {
          System.out.println("Malformed URL");
      }
      

Клиент (1)

  • Получение ссылки на банк
    • Bank bank;
      try {
          bank = (Bank) Naming.lookup("//localhost/bank");
      } catch (NotBoundException e) {
          System.out.println("Bank is not bound");
          return;
      } catch (MalformedURLException e) {
          System.out.println("Bank URL is invalid");
          return;
      }
      

Клиент (2)

  • Создание счета
    • Account account = bank.getAccount("geo");
      if (account == null) {
          System.out.println("Creating account");
          account = bank.createAccount("geo");
      } else {
          System.out.println("Account already exists");
      }
      

Клиент (3)

  • Операции со счетом
    • System.out.println("Money: " + account.getAmount());
      System.out.println("Adding money");
      account.setAmount(account.getAmount() + 100);
      System.out.println("Money: " + account.getAmount());
      

Запуск

  • Создание Stub для классов
    • Этот шаг можно пропустить
    • rmic RemoteAccount RemoteBank
      
  • Запуск RMI Registry
    • rmiregistry
      
  • Запуск сервера
  • Запуск клиента

Фабрики сокетов

Создание Registry на лету

Что пропущено

  • Activation – технология, позволяющая создавать удалённые объекты
  • Загрузка .class файлов с HTTP сервера

Заключение

Содержание

Ссылки по сериализации

Ссылки по RMI

Вопросы

???