Работа с распределенными объектами: RMI, Java IDL.

Краткий сравнительный анализ технологий

Я не хочу даже пытаться сосчитать количество разработчиков, принадлежащих к тому или иному лагерю, но все же рискну предположить что 90% из вас относятся к 3-му лагерю и просто хотят иметь прочную технологию, решающую те задачи, для которых она и предназначалась. При сравнении двух схожих технологий приходится сталкиваться со свойственными им индивидуальными особенностями, достоинствами и недостатками. То же самое наблюдается при сравнении RMI и CORBA. (Обратите внимание, термины CORBA и Java IDL взаимозаменяемы. CORBA является открытой спецификацией, "принадлежащей" OMG, а Java IDL является реализацией этой спецификации.)

Некоторые считают самым большим преимуществом RMI тот факт, что эта технология позволяет создавать решения, полностью базирующиеся на Java. Это означает что создание RMI приложений весьма просто, и все удаленные объекты обладают теми же возможностями, что и локальные объекты (так что вы можете приводить их к типу и к родительскому класса). Разумеется, самое большое достоинство RMI является также и самым большим ее недостатком: эта технология позволяет создавать решения, полностью базирующиеся на Java, что весьма сужает сферу ее применения!

CORBA хотя и является независимым от языка решением, но в то же время усложняет процесс разработки приложений и делает недоступными возможности Java по сборке мусора и, возможную в RMI, передачу через экземпляр(pass-by-copy). Пока формальные запросы for proposals (RFP) CORBA передаются через экземпляры, они никогда не будет поддерживаться RMI, из-за сложностей возникающих при преобразовании объектов из одного языка в другой.

В следующей таблице предпринята попытка подвести итоги сравнения возможностей, предоставляемых этими двумя архитектурами. Рассмотрев эту таблицу мы приступим к разработке двух простых Java приложений используя RMI и Java IDL.

Запомните, Java IDL просто реализация (хотя и является подмножеством) спецификации CORBA. Следовательно термины Java IDL и CORBA по праву могут считаться взаимозаменяемыми.

Сравнительный анализ особенностей RMI и CORBA 
Возможности CORBA RMI
Отклики сервера Да Да
Передача по значению Нет Да
Динамическое обнаружение Да Нет
Динамические вызовы Да Да
Поддержка множественной транспортировки Да Да
Именование URL Нет (ORB-зависимый) Да
Firewall Proxy Нет (ORB-зависимый) Да
Независимость от языка Да Нет
Языково-независимый Wire Protocol Да (через IIOP) Нет (В будущем, IIOP?!?)
Постоянные имена Да No
Низкоуровневая (Wire-level)  безопасность Да (через CORBASecurity) Да (через SSL)
Низкоуровневые(Wire-level) транзакции Да (через CORBA OTS) Да (через JTS)

MortgageCalc 1.0 -- одно приложение, две технологии

Для того чтобы подчеркнуть сходства и различия между RMI и Java IDL, я счел полезным создать две версии одного и того же приложения -- MortgageCalc 1.0 -- каждая из которых базируется на разных технологиях (см. ссылки на полные исходные коды для этих двух приложений). Каждое приложение включает клиентскую и серверную части, используемые для вычислений и отображения ипотечных платежей, зависящих от ряда начальных условий (продолжительность займа, процентная ставка, налоги, страхование и авансовые платежи). Поскольку оба приложения выполняют одни и те же вычисления, мы можем инкапсулировать код, отвечающий за вычисления, в отдельный класс Java, который будет использоваться обеими программами.

Это позволит нам сфокусировать внимание на особенностях разработки приложений при помощи RMI и Java IDL. Прежде чем начать, я дам краткие разъяснения формул, используемые для вычислений. Примечание: Для этих вычислений использована формула ипотеки, принятая в США. В других странах, например в Канаде, используется поквартальная система процентных ставок и, как следствие, другие вычисления.

Месячные платежи (PMT) включают долю от основную суммы займа плюс проценты, которые выплачиваются банку ежемесячно. Кроме этого, ваш суммарный платеж (TOTPMT) также включает городской и общий по стране налоги (TAX) и страховку дома (INS). Другими словами, формула ежемесячных платежей выглядит следующим образом:


TOTPMT = PMT + TAX + INS

Суммы TAX и INS весьма просты для вычисления (Общая сумма займа/12), но с PMT дела обстоят немного сложнее. Не вдаваясь в детали, для вычислений принята следующая формула:


PMT = BAL * (INT / (1 - (1 + INT) ** -MON))

BAL - это первоначальная сумма займа, INT - ежемесячный процент, а MON - число месяцев погашения займа (Кол-во лет * 12).

Класс PaymentCalc

Для того чтобы в дальнейшем использовать код для расчетов в обоих примерах, нам потребуется создать простой Java класс, PaymentCalc. Если эти финансовые приложения в будущем будут усложняться, можно будет внести изменения в расчеты обоих приложений, модифицировав только PaymentCalc. Сейчас PaymentCalc содержит два метода -- PI() и PITI(). Метод PI() вычисляет сумму платежей, включающую основную сумму и процент, тогда как метод PITI() вычисляет сумму платежей, включающую основную сумму, процент, налоги и страховку.

class PaymentCalc
{
   /*
    PI вычисляет только основную сумму и процент
   */
   double PI(double Balance, double AnnualInterestRate, int YearsLength)
   {
      double BAL = Balance;
      double INT = (AnnualInterestRate / 12);
      double MON = (YearsLength * 12);

      double PMT = BAL * (INT / (1 - java.lang.Math.pow(1 + INT, -MON)));

      return PMT;
   }

   /*
    PITI вычисляет общую сумму платежей, включая налоги и страховку
   */
   double PITI(double Balance, double AnnualInterestRate, int YearsLength,
      int AnnualInsurance, int AnnualTaxes)
   {
      double BAL = Balance;
      double INT = (AnnualInterestRate / 12);
      double MON = (YearsLength * 12);

      double PMT = BAL * (INT / (1 - java.lang.Math.pow(1 + INT, -MON)));
      double INS = (AnnualInsurance / 12);
      double TAX = (AnnualTaxes / 12);
      double TOTPMT = PMT + INS + TAX;

      return TOTPMT;
    }
}

Серверная часть RMI и Java IDL приложений использует класс PaymentCalc для выполнения вычислений и в зависимости от продолжительности и размера займа и возвращает клиентскому приложению массив с указанием возможных вариантов займов и платежи по ним.

Основанное на RMI приложение MortgageCalc

Разработка RMI приложений состоит из следующих этапов:

  1. Описание удаленного интерфейса

  2. Реализация удаленного интерфейса путем создания класса (серверное приложение)

  3. Создание клиентских stubs и скелетов сервера используя rmic

  4. Запуск реестра RMI и регистрация удаленных объектов

  5. Создание клиентского приложения

  6. Запуск клиентского приложения и подключение к серверу

Обратите внимание на то, что эти этапы несколько похожи на рассмотренные несколько месяцев назад примеры использования CORBA. Основное различие заключается в первом этапе. Используя RMI, мы описываем удаленный интерфейс на языке программирования Java вместо IDL.

1 этап: Описание удаленного интерфейса

На первом этапе потребуется указать операции, которые должен выполнять наш сервер. Простейшее приложение может вычислять отдельные платежи, исходя из основной суммы займа, процентной ставки и срока займа, но мы пойдем немного дальше. Вместо расчетов отдельных платежей, пусть наш сервер рассчитывает варианты платежей, базирующиеся на таких данных, как сумма кредита  (5%, 10% или 20%) и продолжительность займа (15 или 30 лет). Эти данные дадут нам возможность изучить передачу типов данных клиента и сервера. В случае с RMI, мы возвращаем объект Vector Чтобы получить список объектов, содержащих вычисленные данные.

Серверное приложение реализует интерфейс Calculate.

public interface Calculate extends java.rmi.Remote
{
  public java.util.Vector calcPI(int HousePrice,
                  double InterestRate) throws java.rmi.RemoteException;
  public java.util.Vector calcPITI(int HousePrice, double InterestRate,
                  int Insurance, int Taxes) throws java.rmi.RemoteException;
}

Методы этого объекта возвращают объект Vector, играющего роль контейнера для других объектов. Теперь мы должны описать сам класс, ResultSet, хранящий данные расчетов для методов интерфейса. Запомните что отдельное обращение к одному из методов Calculate возвращает несколько объектов ResultSet, хранимых в объекте Vector. Также запомните что класс ResultSet реализует интерфейс java.io.Serializable. При использовании RMI необходимо чтобы все элементы данных, передаваемых между локальными и удаленными объектами реализовали интерфейс Serializable.

import java.io.*;

public class ResultSet implements Serializable
{
  int Years;
  double DownPayment;
  double Balance;
  double InterestRate;
  double MonthlyPayment;

  private void writeObject(ObjectOutputStream s) throws IOException
  {
    // save the data to a stream
    s.writeInt(Years);
    s.writeDouble(DownPayment);
    s.writeDouble(Balance);
    s.writeDouble(InterestRate);
    s.writeDouble(MonthlyPayment);  
  }

  private void readObject(ObjectInputStream s) throws IOException
  {
    Years = s.readInt();
    DownPayment = s.readDouble();
    Balance = s.readDouble();
    InterestRate = s.readDouble();
    MonthlyPayment = s.readDouble();
  }
}

2 этап: Реализация удаленного интерфейса

Второй этап на самом деле состоит из двух небольших операций. На первой мы реализуем интерфейс Calculate, поскольку вторая операция заключается в разработке серверного приложения, которое представляет собой класс, реализующий этот интерфейс. Это делает класс сервера доступным для всех клиентских приложений, которые его используют. Большая часть кода отведенного на реализацию этого этапа весьма проста.

Единственный фрагмент, который может показаться непонятным - вызов Naming.rebind() из конструктора CalculateImpl. Класс Namingопределен в java.rmi.Naming и используется для регистрации объекта в реестре Java RMI. (Эта операция схожа с концепцией регистрации объектов CORBA при помощи адаптера основных объектов, BOA, в ORB.)

Начнем с кода для реализации класса CalculateImpl:

import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;

public class CalculateImpl extends UnicastRemoteObject
  implements Calculate
{
  private PaymentCalc calc;

  public CalculateImpl(String name) throws RemoteException
  {
    super();
    try
    {
      Naming.rebind(name, this);
      calc = new PaymentCalc();
    }
    catch (Exception e)
    {
      System.out.println("Исключительная ситуация: " + e.getMessage());
      e.printStackTrace();
    }
  }

  public java.util.Vector calcPI(int HousePrice, double
                                 InterestRate) throws java.rmi.RemoteException
  {
    java.util.Vector retObject = new java.util.Vector(6);

    //5%, 15 лет
    ResultSet set1 = new ResultSet();
    set1.Years = 15;
    set1.DownPayment = HousePrice * .05;
    set1.Balance = HousePrice * .95;
    set1.InterestRate = InterestRate;
    set1.MonthlyPayment = calc.PI(set1.Balance, set1.InterestRate, set1.Years);
    retObject.addElement(set1);

    //5%, 30 лет
    ResultSet set2 = new ResultSet();
    set2.Years = 30;
    set2.DownPayment = HousePrice * .05;
    set2.Balance = HousePrice * .95;
    set2.InterestRate = InterestRate;
    set2.MonthlyPayment = calc.PI(set2.Balance, set2.InterestRate, set2.Years);
    retObject.addElement(set2);

    //10%, 15 лет
    ResultSet set3 = new ResultSet();
    set3.Years = 15;
    set3.DownPayment = HousePrice * .10;
    set3.Balance = HousePrice * .90;
    set3.InterestRate = InterestRate;
    set3.MonthlyPayment = calc.PI(set3.Balance, set3.InterestRate, set3.Years);
    retObject.addElement(set3);

    //10%, 30 лет
    ResultSet set4 = new ResultSet();
    set4.Years = 30;
    set4.DownPayment = HousePrice * .10;
    set4.Balance = HousePrice * .90;
    set4.InterestRate = InterestRate;
    set4.MonthlyPayment = calc.PI(set4.Balance, set4.InterestRate, set4.Years);
    retObject.addElement(set4);

    //20%, 15 лет
    ResultSet set5 = new ResultSet();
    set5.Years = 15;
    set5.DownPayment = HousePrice * .20;
    set5.Balance = HousePrice * .80;
    set5.InterestRate = InterestRate;
    set5.MonthlyPayment = calc.PI(set5.Balance, set5.InterestRate, set5.Years);
    retObject.addElement(set5);

    //20%, 30 лет
    ResultSet set6 = new ResultSet();
    set6.Years = 30;
    set6.DownPayment = HousePrice * .20;
    set6.Balance = HousePrice * .80;
    set6.InterestRate = InterestRate;
    set6.MonthlyPayment = calc.PI(set6.Balance, set6.InterestRate, set6.Years);
    retObject.addElement(set6);

    return retObject;
  }

public java.util.Vector calcPITI(int HousePrice, double InterestRate,
             int Insurance, int Taxes) throws java.rmi.RemoteException
  {
    java.util.Vector retObject = new java.util.Vector(6);

    //5%, 15 лет
    ResultSet set1 = new ResultSet();
    set1.Years = 15;
    set1.DownPayment = HousePrice * .05;
    set1.Balance = HousePrice * .95;
    set1.InterestRate = InterestRate;
    set1.MonthlyPayment = calc.PITI(set1.Balance, set1.InterestRate,
                                    set1.Years, Insurance, Taxes);
    retObject.addElement(set1);

    //5%, 30 лет
    ResultSet set2 = new ResultSet();
    set2.Years = 30;
    set2.DownPayment = HousePrice * .05;
    set2.Balance = HousePrice * .95;
    set2.InterestRate = InterestRate;
    set2.MonthlyPayment = calc.PITI(set2.Balance, set2.InterestRate,
                                    set2.Years, Insurance, Taxes);
    retObject.addElement(set2);

    //10%, 15 лет
    ResultSet set3 = new ResultSet();
    set3.Years = 15;
    set3.DownPayment = HousePrice * .10;
    set3.Balance = HousePrice * .90;
    set3.InterestRate = InterestRate;
    set3.MonthlyPayment = calc.PITI(set3.Balance, set3.InterestRate,
                                    set3.Years, Insurance, Taxes);
    retObject.addElement(set3);

    //10%, 30 лет
    ResultSet set4 = new ResultSet();
    set4.Years = 30;
    set4.DownPayment = HousePrice * .10;
    set4.Balance = HousePrice * .90;
    set4.InterestRate = InterestRate;
    set4.MonthlyPayment = calc.PITI(set4.Balance, set4.InterestRate,
                                    set4.Years, Insurance, Taxes);
    retObject.addElement(set4);

    //20%, 15 лет
    ResultSet set5 = new ResultSet();
    set5.Years = 15;
    set5.DownPayment = HousePrice * .20;
    set5.Balance = HousePrice * .80;
    set5.InterestRate = InterestRate;
    set5.MonthlyPayment = calc.PITI(set5.Balance, set5.InterestRate,
                                    set5.Years, Insurance, Taxes);
    retObject.addElement(set5);

    //20%, 30 лет
    ResultSet set6 = new ResultSet();
    set6.Years = 30;
    set6.DownPayment = HousePrice * .20;
    set6.Balance = HousePrice * .80;
    set6.InterestRate = InterestRate;
    set6.MonthlyPayment = calc.PITI(set6.Balance, set6.InterestRate,
                                    set6.Years, Insurance, Taxes);
    retObject.addElement(set6);

    return retObject;
}
}

Теперь нам просто нужно создать основное серверное приложение, MortageCalcServer, которое реализует класс и регистрирует его при помощи менеджера безопасности RMI. Эта задача может быть реализована всего лишь несколькими строками кода:

import java.rmi.*;
import java.rmi.server.*;

public class MortgageCalcServer
{
  public static void main(String args[])
  {
    //Set up the server's security manager
    System.setSecurityManager(new RMISecurityManager());

    try
    {
      CalculateImpl calculator = new CalculateImpl("JavaWorld MortgageCalc");
      System.out.println("JavaWorld MortgageCalc запущен!");
    }
    catch (Exception e)
    {
      System.out.println("Exception: " + e.getMessage());
      e.printStackTrace();
    }
  }
}

Объект RMISecurityManager используется для того чтобы убедиться в правильности загрузки классов. После того как это сделано (это необходимо), мы можем просто создать экземпляр объекта CalculateImpl, который регистрирует объект CalculateImpl в реестре RMI.

3 этап: Генерация stubs используя rmic

Инструмент rmic, приложение, входящее в состав JDK, получает в качестве входящих данных все созданные вами реализации удаленных объектов (в файлах форм или классов) и в качестве выходных данных выдает stubs клиентов  и скелеты сервера. Stub для удаленного объекта - это прокси со стороны клиента для удаленного объекта. Этот прокси отвечает за содержание удаленного объекта, размещение параметров этого объекта и информирование удаленных сылок о завершении вызова. Скелет - окружение сервера, которое содержит метод, посылающий вызовы реальным реализациям удаленных объектов. Этот скелет отвечает за удаление (unmarshalling) параметров, вызов реальной реализации, а затем размещении (marshalling) результирующих значений.

Запуская rmic, он генерирует файлы CalculateImpl_Stub.class и CalculateImpl_Skel.class:


rmic CalculateImpl

4 этап: Регистрация объектов

Подобно OSAgent, который мы запускаем для настройки используя Visigenic VisiBroker ORB, появившуюся в октябре  "Corba meets Java" feature, JDK 1.2 поставляется с инструментом, rmiregistry, регистрируюем все запускаемые реализации объектов на локальном компьютере. Начните использование этого инструмента с запуска следующей команды:


start rmiregistry

Вы регистрируете и запускаете сервер запуская стандартный интерпретатор Java:


java MortgageCalcServer

5 этап: Создание клиента

Теперь вы можете расслабиться, поскольку самая трудоемкая часть работы уже позади. Теперь мы должны сосредоточиться на сервере RMI, ожидающем пока какой-нибудь клиент не подключится к нему. Теперь давайте создадим клиентское приложение, которое сделает это. Все клиентские приложения RMI вначале должны установить менеджер безопасности RMI (как мы уже делали это при разработке сервера RMI), затем находят удаленный объект, который они будут использовать. Как только эти задачи выполнены, приложение может использовать объект так, как если бы он запускался из памяти окружения программы. Приложению MortgageCalcClient передаются три параметра командной строки: ServerName, HousePrice и InterestRate. Используя эти параметры она вычисляет ваши платежи по закладной и выводит процентные ставки.

import java.rmi.*;
import java.rmi.registry.*;
import java.rmi.server.*;

public class MortgageCalcClient
{
  public static void main(String args[])
  {
    if (args.length != 3)
    {
       System.out.println("Применение: java MortgageCalcClient ИмяСервера СтоимостьДома ПроцентнаяСтавка");
       System.out.println("Процентная ставка должна задаваться в десятичной форме!");
       System.exit(0);
    }

    String ServerName = args[0];
    Integer HousePrice= new Integer(args[1]);
    Double InterestRate = new Double(args[2]);

    //Установка менеджера безопасности
    System.setSecurityManager(new RMISecurityManager());

    try
    {
       Calculate calc = (Calculate)Naming.lookup("rmi://"
                         + args[0] + "/" + "JavaWorld MortgageCalc");

       java.util.Vector v = calc.calcPI(HousePrice.intValue(),
                                        InterestRate.doubleValue());
       for (int i=0; i < 6; i++)
       {
           ResultSet s = (ResultSet)v.elementAt(i);
           System.out.println("Условия займа=" + s.Years + " лет");
           System.out.println("Авансовый платеж=$" + s.DownPayment);
           System.out.println("Процентная ставка=" + s.InterestRate * 100 + "%");
           System.out.println("Ежемесячные платежи=$" + s.MonthlyPayment);
           System.out.println();
        }
    }
    catch (Exception e)
    {
        System.err.println("Исключительная ситуация" + e);
    }
    System.exit(0);
  }
}

В настоящее время утилита rmiregistry не может находить автоматически запускаемые объекты, хранимые на сервере. В ней также отсутствуют динамический поиск и динамические вызовы. Java IDL, базирующаяся на CORBA 2.0, обладает этими возможностями и будет рассмотрена далее.

Основанное на IDL Java приложение MortgageCalc

Для этого примера мы используем компоненты Java IDL, включенные в состав Java Development Kit 1.2. Поскольку CORBA уже детально обсуждалась в моих предыдущих статьях, давайте не будем углубляться в теорию CORBA и сосредоточим внимание на собственно использовании инструментов JDK 1.2  для создания Java-CORBA приложений. Для разработки этого и других апплетов и/или CORBA приложений, нам потребуется реализовать следующие этапы:

  1. Создание описаний всех объектов используя IDL

  2. Компиляция кода IDL используя компилятор idltojava для создания кода скелета сервера и кода stub клиента

  3. Реализация объекта сервера

  4. Создание серверного приложения

  5. Создание клиентского приложения

Единственной сложностью, отличающую это приложение от Java/CORBA приложений, которые мы разрабатывали ранее, является использование сервиса имен Java IDL. Причина этого отличия заключается в том, что Java IDL пока что не включает поддержку для хранилища интерфейса (interface repository). (Рассматривая эту проблему в более ранних статьях, используя Visigenic Visibroker ORB, мы использовали утилиту osagent для нахождения объектов и их получения из структуры хранилища интерфейса). Вместо этого, Java IDL поддерживает полнофункциональную реализацию сервиса имен "Сервис общих объектов CORBA" (COS). (для более полной информации о сервере имен COS см. раздел ссылок) Включение сервиса имен позволяет Java IDL и его сервису имен взаимодействовать с другими CORBA 2.0-совместимыми ORB'ами через набор OMG интерфейсов.

Несколько слов о сервесе имен Java IDL (naming service)

Сервис имен Java IDL обеспечивает древовидный каталог для ссылок на объекты, подобный тому который представляет файловая система для хранения файлов. Комбинация ссылок на объект и присвоенного ей имени, называется именной связкой (name binding). Именные связки хранятся в ветвях дерева каталогов и называются оглавлением имен (naming context). Продолжая аналогию, ссылка на объект по отношению к оглавлению имен то же, что и файл по отношению к директории в вашей файловой системе.

Для связи с сервисом имен Java IDL, клиентское приложение должно знать имя хоста и номер порта запущенного сервера. После установления соединения клиент запрашивает оглавление имен а затем извлекает ссылку на необходимый Java объект, чтобы в последствии использовать его в приложении. Обратите внимание, поскольку этот сервис CORBA-совместимый, даже нет необходимости использовать сервис имен, включенный в состав JDK 1.2. Другими словами, клиентское приложение JDK 1.2 может быть создано при помощи Java IDL, но может быть подключено к сервису имен Visigenic используя стандартизованные интерфейсы сервиса имен. Этот сервис весьма прост (в конце концов, это всего лишь первое предложение от JavaSoft). Ссылки на объект временные и будут удалены после завершения работы сервиса имен. Более полную реализацию сервиса имен можно найти у других производителей програмного обеспечения CORBA  (Expersoft, IONA, Visigenic, и многие другие) и любые апплеты или приложения, которые вы напишите для работы с сервисом имен Java IDL будут прекрасно работать с сервисами от других производителей. Поскольку CORBA является объектно-ориентированной архитектурой, для вас не должен стать сюрпризом тот факт, что  сервис имен сам доступен через набор интерфейсов, хранящихся в модуле CosNaming.

Первый из этих интерфейсов, NamingContext, содержит методы необходимые для обеспечения именных связок и их разрешения (resolution). Второй интерфейс, BindingIterator, содержит методы, используемые для перебора именных связок для поиска искомого объекта.

Сервис имен Java IDL будет использоваться для обнаружения ссылки на объект. В нашем примере, на объект сервера Calculate.

1 этап: Описание интерфейса с использованием IDL

Примечание: Java IDL не является реализацией Языка описания интерфейсов OMG. Как мы уже говорили, Java IDL по сути дела CORBA 2.0 ORB, который использует IDL для описания интерфейсов. Это может привести к недоразумениям, связанным с использованием буквенной абривеатуры "IDL" в названии ORB, но это не остановило его разработчиков.

Процесс описания интерфейса Calculate в IDL немного отличается от процесса описания соответствующего интерфейса в Java (см. исходный код выше, для Java RMI интерфейса Calculate).

Причина заключается в том, что в RMI мы можем вернуть результат нашего платежа по закладной, хранящийся в объекте java.lang.Vector. Мы также описали отдельный Java класс (ResultSet) для данных, хранящихся в объекте Vector.

IDL позволит нам получить группы объектов в массиве или последовательности. Изучение Java-в-IDL преобразования покажет чтооба этих IDL типа данных преобразуются в массив Java, поэтому не имеет значения который из них мы выберем. Для этого примера, мы используем IDL последовательность. На следующем листинге представлен файл Calculate.IDL который будет использован для генерации кода для нашего stub  и скелета на 2-м шаге.

struct ResultSet
{
    long Years;
    double DownPayment;
    double Balance;
    double InterestRate;
    double MonthlyPayment;
};

typedef sequence<ResultSet> MortgagePayments;

interface Calculate
{
    MortgagePayments calcPI(in long HousePrice, in double InterestRate);
    MortgagePayments calcPITI(in long HousePrice, in double InterestRate,
                              in long Insurance, in long Taxes);
};

Как вы видите, структура ResultSet практически идентична нашему Java классу ResultSet описаный в примере для RMI. Интерфейс Calculate также очень напомминает RMI интерфейс Calculate, за исключением того что этот возвращает последовательность MortgagePayments.

2 этап: Применение idltojava

Компилятор idltojava - это IDL компилятор от JavaSoft. На время написания этой статьи он не был включен в состав JDK 1.2 Beta 2, и поэтому должен быть скачан отдельно (см. Ссылки).

Для работы этого компилятора потребуется препроцессор Microsoft Visual C++, следовательно вы должны установить на свою систему Visual C++. (Убедитесь что запустили файл vcvars32 для для правильной настройки вашего окружения.) После того как все установлено, просто запустите следующую команду для создания необходимых клиентских stubs и скелетов сервера:


idltojava Calculate.idl

В результате будут созданы 10 исходных файлов Java, включая конечный Calculate.java, представленный далее:

public interface Calculate extends org.omg.CORBA.Object
{
  ResultSet[] calcPI(int HousePrice, double InterestRate);
  ResultSet[] calcPITI(int HousePrice, double InterestRate,
                       int Insurance, int Taxes);
}

Как вы видите, эот интерфейс практически идентичен RMI интерфейсу Calculate. Версия для RMI возвращает Java класс Vector, тогда как версия Java IDL возвращает массив объектов ResultSet. В остальном эти два класса идентичны.

3 этап: Реализация объекта сервера

В документации по Java IDL рассматриваются две части вашего серверного приложения, называемые служба (servant) и сервер. Это хороший способ разобраться во взаимодействии между вашей реализации класса (мы использовали имя CalculateImpl ради большего сходства) и серверным приложением, ответственным за инициализацию ORB, регистрацию службы и перехвате клиентских запросов. Поскольку реализация осталась практически без изменений, класс Java IDL CalculateImpl весьма похож на класс RMI CalculateImpl.

class CalculateImpl extends _CalculateImplBase
{
  ResultSet[] calcPI(int HousePrice, double InterestRate)
  {
    ResultSet[] retObject = new ResultSet[6];

    //5%, 15 лет
    ResultSet set1 = new ResultSet();
    set1.Years = 15;
    set1.DownPayment = HousePrice * .05;
    set1.Balance = HousePrice * .95;
    set1.InterestRate = InterestRate;
    set1.MonthlyPayment = calc.PI(set1.Balance, set1.InterestRate, set1.Years);
    retObject[0] = set1;

    //5%, 30 лет
    ResultSet set2 = new ResultSet();
    set2.Years = 30;
    set2.DownPayment = HousePrice * .05;
    set2.Balance = HousePrice * .95;
    set2.InterestRate = InterestRate;
    set2.MonthlyPayment = calc.PI(set2.Balance, set2.InterestRate, set2.Years);
    retObject[1] = set2;

    //10%, 15 лет
    ResultSet set3 = new ResultSet();
    set3.Years = 15;
    set3.DownPayment = HousePrice * .10;
    set3.Balance = HousePrice * .90;
    set3.InterestRate = InterestRate;
    set3.MonthlyPayment = calc.PI(set3.Balance, set3.InterestRate, set3.Years);
    retObject[2] = set3;

    //10%, 30 лет
    ResultSet set4 = new ResultSet();
    set4.Years = 30;
    set4.DownPayment = HousePrice * .10;
    set4.Balance = HousePrice * .90;
    set4.InterestRate = InterestRate;
    set4.MonthlyPayment = calc.PI(set4.Balance, set4.InterestRate, set4.Years);
    retObject[3] = set4;

    //20% down, 15 years
    ResultSet set5 = new ResultSet();
    set5.Years = 15;
    set5.DownPayment = HousePrice * .20;
    set5.Balance = HousePrice * .80;
    set5.InterestRate = InterestRate;
    set5.MonthlyPayment = calc.PI(set5.Balance, set5.InterestRate, set5.Years);
    retObject[4] = set5;

    //20%, 30 лет
    ResultSet set6 = new ResultSet();
    set6.Years = 30;
    set6.DownPayment = HousePrice * .20;
    set6.Balance = HousePrice * .80;
    set6.InterestRate = InterestRate;
    set6.MonthlyPayment = calc.PI(set6.Balance, set6.InterestRate, set6.Years);
    retObject[5] = set6;

    return retObject;
  }

  ResultSet[] calcPITI(int HousePrice, double InterestRate,
                       int Insurance, int Taxes)
  {
    ResultSet[] retObject = new ResultSet[6];

    //5%, 15 лет
    ResultSet set1 = new ResultSet();
    set1.Years = 15;
    set1.DownPayment = HousePrice * .05;
    set1.Balance = HousePrice * .95;
    set1.InterestRate = InterestRate;
    set1.MonthlyPayment = calc.PITI(set1.Balance, set1.InterestRate,
                                    set1.Years, Insurance, Taxes);
    retObject[0] = set1;

    //5%, 30 лет
    ResultSet set2 = new ResultSet();
    set2.Years = 30;
    set2.DownPayment = HousePrice * .05;
    set2.Balance = HousePrice * .95;
    set2.InterestRate = InterestRate;
    set2.MonthlyPayment = calc.PITI(set2.Balance, set2.InterestRate,
                                    set2.Years, Insurance, Taxes);
    retObject[1] = set2;

    //10%, 15 лет
    ResultSet set3 = new ResultSet();
    set3.Years = 15;
    set3.DownPayment = HousePrice * .10;
    set3.Balance = HousePrice * .90;
    set3.InterestRate = InterestRate;
    set3.MonthlyPayment = calc.PITI(set3.Balance, set3.InterestRate,
                                    set3.Years, Insurance, Taxes);
    retObject[2] = set3;

    //10%, 30 лет
    ResultSet set4 = new ResultSet();
    set4.Years = 30;
    set4.DownPayment = HousePrice * .10;
    set4.Balance = HousePrice * .90;
    set4.InterestRate = InterestRate;
    set4.MonthlyPayment = calc.PITI(set4.Balance, set4.InterestRate,
                                    set4.Years, Insurance, Taxes);
    retObject[3] = set4;

    //20%, 15 лет
    ResultSet set5 = new ResultSet();
    set5.Years = 15;
    set5.DownPayment = HousePrice * .20;
    set5.Balance = HousePrice * .80;
    set5.InterestRate = InterestRate;
    set5.MonthlyPayment = calc.PITI(set5.Balance, set5.InterestRate,
                               &nbs                                     set5.Years, Insurance, Taxes);
    retObject[4] = set5;

    //20%, 30 лет
    ResultSet set6 = new ResultSet();
    set6.Years = 30;
    set6.DownPayment = HousePrice * .20;
    set6.Balance = HousePrice * .80;
    set6.InterestRate = InterestRate;
    set6.MonthlyPayment = calc.PITI(set6.Balance, set6.InterestRate,
                                    set6.Years, Insurance, Taxes);
    retObject[6] = set6;

    return retObject;
  }
}

4 этап: Создание серверного приложения

Как я уже отмечал раньше, основное различие между серверным приложением и приложением RMI MortgageCalcServer заключается в том, что первое использует сервис имен Java IDL. Мы делаем это, когда сначала ищем корневое содержание имен (все равно что искать корневой каталог в файловой системе), затем когда добавляем в него NameComponent для привязки нашего объекта calculator. После выполнения этих операций, приложение просто переходит в режим ожидания и перехватывает запросы клиентов.

import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;

public class MortgageCalcServer
{
  public static void main(String args[])
  {
    try
    {
      // create and initialize the ORB
      ORB orb = ORB.init(args, null);Э

      // create servant and register it with the ORB
      CalculateImpl calculator = new CalculateImpl();
      orb.connect(calculator);

      // получение содержания корневых имен
      org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
      NamingContext ncRef = NamingContextHelper.narrow(objRef);Э

      // привязка ссылки на объект к Naming
      NameComponent nc = new NameComponent("JavaWorld MortgageCalc", "");
      NameComponent path[] = {nc};
      ncRef.rebind(path, calculator);Э

      // ожидание вызовов от клиентов
      java.lang.Object sync = new java.lang.Object();
      synchronized (sync)
      {
         sync.wait();
      }Э
    }
    catch (Exception e)
    {
         System.err.println("ОШИБКА: " + e);
e.printStackTrace(System.out);
    }
  }
}

5 этап: Создание клиентского приложения

Клиентское приложение (MortgageCalcClient) инициализирует ORB, устанавливает ссылки на объекты используя сервис имен, затем осуществляет те же операции, что и клиентское приложение RMI.

import org.omg.CosNaming.*;
import org.omg.CORBA.*;

public class MortgageCalcClient
{
  public static void main(String args[])
  {
    if (args.length != 3)
    {
       System.out.println("Применение: java MortgageCalcClient
                           ");
       System.out.println("Процентная ставка должна задаваться в десятичной форме!");
       System.exit(0);
    }

    String ServerName = args[0];
    Integer HousePrice= new Integer(args[1]);
    Double InterestRate = new Double(args[2]);

    try
    {
       // создает и инициализирует ORB
       ORB orb = ORB.init(args, null);

       // get the root naming context
       org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
       NamingContext ncRef = NamingContextHelper.narrow(objRef);

       // resolve the Object Reference in Naming
       NameComponent nc = new NameComponent("JavaWorld MortgageCalc", "");
       NameComponent path[] = {nc};
       Calculate calc = CalculateHelper.narrow(ncRef.resolve(path));

       ResultSet[] v = calc.calcPI(HousePrice.intValue(),
                                   InterestRate.doubleValue());
       for (int i=0; i < 6; i++)
       {
           ResultSet s = v[i];
           System.out.println("Условия займа=" + s.Years + " лет");
           System.out.println("Авансовый платеж=$" + s.DownPayment);
           System.out.println("Процентная ставка=" + s.InterestRate * 100 + "%");
           System.out.println("Ежемесячные платежи=$" + s.MonthlyPayment);
           System.out.println();

       }
     }
     catch (Exception e)
     {
       System.out.println("Ошибка : " + e) ;
       e.printStackTrace(System.out);
     }
   }
}

Заметьте что, за исключением кода использованного для инициализации ORB и resolve ссылки на объект, остальной  код этого приложения практически идентичен коду клиентского приложения RMI. для запуска этого приложения сначала запустите сервис имен Java IDL используя команду: tnameserv

Hosted by uCoz