Среда JavaBeans полагается на стандартные механизмы сохранения Java-объектов. Таких механизмов два. Интерфейс java.io. Serializable (см. листинг 18) предусматривает сохранение в стандартной форме графа объектов, доступных из данного. Впрочем, методы writeObject/readObject и здесь предоставляют пользователям определенную свободу. Интерфейс java.io.Externalizable (см. листинг 19) делает эту свободу абсолютной - Java-машина берет на себя лишь сохранение классовой информации, все остальное отдается на откуп пользователю. Реализация интерфейса Externalizable целесообразна в первую очередь тогда, когда есть необходимость представить компонент JavaBeans как элемент другой объектной среды, такой, например, как OLE или OpenDoc.
При сохранении объектов в долговременной памяти приходится учитывать стандартные проблемы, связанные с различным временем жизни компонентов, с изменением их версий и с обеспечением их информационной безопасности; мы, однако, на этих проблемах останавливаться не будем.
Создание нового экземпляра компонентов также сопряжено с некоторыми тонкостями. Экземпляр может создаваться в разных контекстах, например, в контексте приложения или в контексте инструментального окружения. Метод instantiate класса java.beans.Beans (см. листинг 20) наделен достаточным интеллектом, чтобы учесть контекст и сделать все корректно; в этой связи пользователю рекомендуется избегать создания экземпляров иными методами.
Помимо instantiate, класс Beans содержит некоторые другие методы, облегчающие управление компонентами объектной среды (см. листинг 20).
Упаковка сохраненных Java-объектов (и, в частности, компонентов объектной среды) важна прежде всего для эффективной доставки их по сети. Спецификации JavaBeans рекомендуют (но не предписывают) использовать для упаковки формат JAR (Java-архив).
Java-архивы могут содержать файлы классов, результаты сериализации объектов, изображения, справочную информацию и другие ресурсы. С архивом может быть ассоциирован manifest-файл, описывающий его содержимое, в том числе зависимости между компонентами.
На примере сохранения компонентов можно видеть, насколько упрощается жизнь разработчиков за счет продуманной организации Java-технологии. Проблема сохранения решена на уровне стандартного Java-окружения, так что все надстройки (такие как JavaBeans) могут полагаться на существующие механизмы.
Представляется удивительным, что спецификация [3], регламентирующая фундаментальное отношение компоненты/контейнер, не вошла в число первоочередных и дорабатывается только сейчас, в рамках новой версии JavaBeans с рабочим названием Glasgow.
Механизм контейнеров необходим для достижения по крайней мере двух целей:
Первый пункт означает, в частности, инкапсуляцию совокупности компонентов, так что с точки зрения окружения она выглядит как единое целое с набором методов, предоставляемых контейнером. Кроме того, применительно к иерархической структуре возможен систематический обход и обработка ее элементов.
Второй пункт имеет противоположное назначение - инкапсуляцию окружения. Контейнер выступает в роли оболочки, скрывающей от компонентов особенности внешней среды и предоставляющей им свой контекст.
Чтобы избежать употребления перегруженного в Java-среде термина "контейнер", авторы спецификации употребляют сочетание "BeanContext". Мы не будем этого делать, поскольку, помимо предоставления общего контекста, у контейнера есть и другие функции; надеемся, что к путанице это не приведет.
Реализация механизма контейнеров использует служебный интерфейс java.util.Collection, который предполагается включить в одну из ближайших версий Java-среды. Фрагмент описания этого интерфейса, содержащий типичные методы для работы с наборами, приведен на листинге 21.
Интерфейс java.beans.BeanContextChild содержит описания методов, позволяющих ассоциировать с компонентом объемлющий контейнер и опрашивать эту ассоциацию (см. листинг 22). Таким образом, связи, ведущие вниз (от контейнера к компоненту), обслуживает интерфейс Collection, а связи, ведущие вверх, - интерфейс BeanContextChild.
С отношением компоненты/контейнер ассоциировано событие beanContextChanged. Соответствующий интерфейс (BeanContextListener) описан на листинге 23.
Вообще говоря, распространение этого события может происходить в несколько приемов: контейнер, получив извещение от компонента, передает его своим подписчикам, в число которых, вероятно, входит объемлющий контейнер, и т.д. Чтобы распознать подобные "вторичные" события и определить первоисточник, предусмотрены соответствующие методы событийного объекта BeanContextEvent (см. листинг 24).
Изменения совокупности компонентов, входящих в контейнер, обслуживает событийный объект BeanContextMembershipEvent. Он содержит информацию о разности ("дельте") между старым и новым составом контейнера, то есть о том, какие компоненты были добавлены или, напротив, удалены (листинг 25).
Интегрирующим элементом рассматриваемой спецификации является интерфейс java.beans.BeanContext, описывающий связи, идущие как вверх, так и вниз (за счет расширения интерфейсов BeanContextChild и Collection соответственно). Интерфейс BeanContext позволяет также опросить предоставляемые контейнером сервисы и ресурсы. Содержит он и методы для регистрации подписчиков событий (см. листинг 26).
Разумеется, кроме синтаксиса специфицируется семантика методов интерфейса BeanContext.
При добавлении компонента методом add (), унаследованным у интерфейса Collection, контейнер "привязывает" этот компонент к себе, вызывая в нем метод setBeanContext с аргументом this (полноценные компоненты должны реализовывать интерфейс BeanContextChild). В свою очередь, компонент может протестовать против включения в контейнер, возбуждая исключительную ситуацию PropertyVetoException. Если это случится, контейнер обязан отменить добавление, возбудив исключительную ситуацию IllegalArgumentException. При успешном добавлении компонента контейнер распространяет подписчикам событие beanContextChanged с соответствующим объектом-параметром. Контейнер должен подписаться у нового компонента на события, связанные со свойствами последнего, чтобы отслеживать по крайней мере свойство beanContext и не допустить нарушения целостности иерархической структуры. Кроме того, компонент регистрируется как подписчик событий, возбуждаемых контейнером.
При удалении объекта из контейнера производятся обратные действия. В частности, вызывается метод setBeanContext с аргументом null. Если компонент находится в состоянии, не позволяющем произвести удаление, он возбуждает исключительную ситуацию PropertyVetoException, заставляя тем самым контейнер отказаться от удаления.
Отметим, что контейнерная реализация методов интерфейса Collection должна быть безопасной в многопотоковой среде.
Рассмотренная спецификация заполняет очень важную методологическую брешь в JavaBeans. Хотелось бы надеяться, что переход к новой версии прикладного программного интерфейса Java, включающей интерфейс BeanContext, произойдет в ближайшее время.
Компоненты объектной среды обычно взаимодействуют не только с объемлющим контейнером, но и между собой. Механизм событий - это одна грань такого взаимодействия. В дополнение необходима возможность обмена структурированными элементами данных.
Соответствующая спецификация [5] готовится специалистами компаний Sun Microsystems и Lotus Development. И хотя совместная работа только началась, мы сочли необходимым осветить выдвинутые предложения ввиду исключительной важности затрагиваемой темы.
Центральным понятием предлагаемых спецификаций является информационная шина. Компоненты могут подключаться к шине, помещать на нее данные (это делают поставщики) и считывать данные (это делают потребители). Обмен носит асинхронный характер: поставщик, поместив данные, не заботится о том, когда они будут считаны. Для идентификации элементов данных используются их имена.
Информационная шина описывается классом InfoBus. Методы этого класса (реализуемого в рамках виртуальной Java-машины) порождают экземпляры шины, осуществляют подключение компонентов к подходящим экземплярам, отслеживают список шин и подключенных к ним компонентов, распространяет события, обслуживающие обмен данными, и т.п. Фрагмент описания класса InfoBus представлен на листинге 27.
Процесс информационного взаимодействия компонентов в спецификациях InfoBus можно подразделить на пять фаз:
Для подключения к шине компонент должен реализовать интерфейс InfoBusMember, получить ссылку на экземпляр шины и войти в число ее членов. Фрагмент интерфейса InfoBusMember приведен на листинге 28. Обратим внимание, что шина, ассоциируемая с компонентом, трактуется как его свойство, изменение значения которого может отслеживаться на основе механизма событий.
Для облегчения реализации интерфейса InfoBusMember спецификации предлагают класс InfoBusMemberImpl, который, в дополнение к "обязательным", предоставляет еще два удобных метода - joinInfoBus () и leaveInfoBus () (см. листинг 29).
Для реализации второй фазы (прослушивание шины) поставщик должен подписаться на информацию о запросах данных, воспользовавшись методом addDataProducer () класса InfoBus. Аналогично, потребитель должен подписаться на информацию о наличии данных, обратившись к методу addDataConsumer (). Компонент может одновременно являться и поставщиком, и потребителем (типичный пример - промежуточное звено конвейера).
Третью фазы информационного взаимодействия компонентов обслуживают преимущественно событийные объекты. В соответствии с моделью, принятой в спецификации InfoBus, поставщик оповещает потребителей о появлении нового элемента данных. Потребители запрашивают у поставщиков данные, когда в них возникает нужда. Обмен элементом данных становится возможным, если поставщик и потребитель используют для него одно и то же имя. Ответственность за надлежащий выбор имен лежит на разработчике приложения.
Класс InfoBusEvent является базовым для событийных объектов поставки/приема. Его описание приведено на листинге 30.
Поставщик объявляет о наличии новых данных, рассылая всем потребителям шины событийный объект класса InfoBusItemAvailableEvent. Методы этого класса позволяют получить информацию о поставщике и ассоциированный элемент данных.
Потребитель, желающий получить данные, рассылает объект класса InfoBusItemRequestedEvent, описание которого приведено на листинге 31. Обратим внимание на метод setDataItem, позволяющий поставщику, получившему событийный объект, "привязать" к нему элемент данных, выдав его тем самым инициатору запроса.
Формат элемента данных определяется интерфейсами, которые этот элемент реализует. Базовым интерфейсом элементов данных является DataItem, в число преемников которого входят CollectionAccess, DbAccess и др. Поскольку проблема обмена структурированными данными уже решена в рамках абстрактного оконного инструментария (пакет java.awt.datatransfer), представляется естественным и в спецификациях InfoBus пойти тем же путем, что и было сделано.
Спецификации InfoBus только начали свой путь к официальному утверждению. Многие детали еще не определены. Тем не менее, основополагающие решения приняты, и они представляются весьма удачными. В частности, понятие экземпляра шины позволяет структурировать пространство поставщиков и потребителей, уменьшая сложность системы и повышая эффективность обмена данными.
В разных ситуациях компонент объектной среды должен поворачиваться к пользователю разными гранями. Например, в инструментальном окружении необходимо получать информацию об афишируемых характеристиках объекта. В настоящее время такую информацию предоставляет специальный информационной объект, косвенно (по имени класса) ассоциированный с исследуемым и реализующий интерфейс BeanInfo. Однако более естественной была бы не косвенная, а прямая ассоциация, позволяющая единообразно осуществлять доступ к разным "проявлениям" объектов.
Решению сформулированной задачи служит очень важная в концептуальном плане спецификация [4]. Центральное место в ней занимает понятие агрегата - сущности, обладающей "многогранным" поведением, динамически унаследованным у совокупности классов (интерфейсов). В агрегат входят представители соответствующих классов, а также координатор, способный по запросу выдавать нужного представителя.
Спецификации не предусматривают внесения каких-либо изменений в язык Java. Агрегат и входящий в него координатор представлены интерфейсом Aggregate (см. листинг 32). Интерфейс Aggregate содержит методы, позволяющие получить ссылку на объект требуемого класса и опросить поддерживаемый агрегатом набор классов.
Формально каждый представитель является самостоятельным объектом, принадлежащим своему классу, однако с идейной точки зрения более правильно считать, что метод getInstanceOf () возвращает разные проявления одного (агрегатного) объекта.
Спецификации JavaBeans в совокупности с предлагаемыми дополнениями образуют целостную архитектуру компонентной объектной среды, позволяющей накапливать и многократно использовать программистские знания. Уникальным достоинством JavaBeans является Java-фундамент, предоставляющий современный объектный язык, гарантирующий мобильность и информационную безопасность разрабатываемого программного обеспечения.
Компоненты среды JavaBeans оказываются мобильными вдвойне. Мост к ActiveX автоматически "вкладывает" их в эту среду, а средства Java IDL помогают организовать взаимодействие с CORBA-брокерами объектных запросов.
Разработка спецификаций JavaBeans следовала и следует традициям открытости, заложенным на предыдущих этапах развития Java-технологии. Партнерами JavaSoft в этой работе выступали такие известные компании, как Apple Computer, Borland International, IBM, Informix Software, Lotus Development, Netscape Communications, Novell, Oracle, Silicon Graphics, Sybase, Texas Instruments и многие другие. Спецификации доступны для свободного ознакомления, внесения замечаний и предложений.
Политика партнерства способствовала созданию многочисленных инструментальных окружений, поддерживающих процессы разработки и интеграции компонентов JavaBeans. Назовем Data Director for Java (Informix Software), Visual Age for Java (IBM), BeanMachine (Lotus Development), Cosmo Code (Silicon Graphics). На подходе JBuilder (Borland International), Visual JavaScript (Netscape Communications), Java Studio и Java Workshop (SunSoft), PowerJ (Sybase) и др.
Разработчики, делающие ставку на JavaBeans, не рискуют проиграть. Эта компонентная объектная среда вступает в пору зрелости, обладая всеми качествами, необходимыми для успешного продвижения.