Своей популярностью язык Java во многом обязан обширным библиотекам, таким как пакеты AWT. В качестве основных причин быстрого развития Java-библиотек можно рассматривать возможности динамической связи элементов Java-программ и компонентную модель построения таких библиотек. Следует отметить, что компонентная модель построения библиотек не является неотъемлемой частью языка Java, но разработчики активно пользуются ею для упрощения своей работы.
Компонентная модель позволяет не только составлять программы из отдельных элементов, но и упрощает создание средств визуальной разработки. Концепция JavaBeans, которой вооружилась компания JavaSoft с целью развития Java, по сути, и представляет собой реализацию компонентной модели. Ниже мы рассмотрим модель JavaBeans и приведем пример ее использования.
Компоненты, контейнеры и события
Компонентная модель JavaBeans (буквально - "зерна Java") состоит из трех основных элементов - настраиваемых компонентов, контейнеров, объединяющих компоненты в единое целое, и событий, позволяющих компонентам взаимодействовать между собой. Это базовые элементы, которые составляют основу модели JavaBeans.
В компонентной модели JavaBeans используются стандартные для Java элементы, в частности спецификации самопредставления классов (introspection), сериализации (преобразования Java-объектов в последовательность байтов) и их упаковки для хранения на диске (спецификация JAR). Эти элементы технологии Java позволяют сохранять компоненты JavaBeans на жестком диске или передавать их по сети. Таким образом, спецификации JavaBeans дают возможность строить сложные клиент-серверные и даже распределенные приложения.
Компоненты
Компоненты (иногда их называют просто bean) - это основной элемент технологии JavaBeans. Для взаимодействия компонентов между собой используется механизм самопредставления классов. Этот механизм позволяет другим Java-классам определять имена методов, реализованных в компоненте. Таким образом, другие компоненты JavaBeans могут динамически настроить себя на взаимодействие с новым компонентом. JavaBeans предусматривает следующие три механизма динамической настройки компонентов:
В принципе возможность самопредставления была изначально заложена в технологию Java, поскольку даже в откомпилированных программах содержатся имена методов, переменных и их типов. В JavaBeans этот механизм активно используется на этапе сборки приложения, так как он позволяет соединить в единое целое компоненты, которые были разработаны отдельно друг от друга. В результате появляется возможность создавать средства разработки, которые позволяют использовать при составлении программы компоненты, разработанные в любом другом средстве разработки.
Разработать новый компонент JavaBeans просто - для этого достаточно следовать при создании Java-класса определенным концептуальным правилам. Например, методы для получения свойства компонента должны описываться по определенным шаблонам. Для имени метода, опрашивающего определенное свойство компонента, используется следующий шаблон:
public типСвойства getИмяСвойства();
Единообразие названий методов упрощает реализацию компонентов. Например, динамически опросить свойство myProperty можно с помощью следующего метода:
public myProperty getmyProperty();
Набор правил, которые позволяют по имени компонента, контейнера или свойства составлять названия соответствующих методов, называется рефлексией.
В качестве основных средств настройки компонента под нужды конкретного приложения могут рассматриваться его свойства. Свойства компонента, как правило, реализуются с помощью определенных полей объекта. Так, например, можно описать цвет, размер, пиктограмму и другие характеристики компонента. Однако, поскольку механизмы работы со свойствами вызывают определенные методы, а не опрашивают поля объектов, есть возможность реализовать и более сложные свойства. Выше уже был приведен шаблон для определения имени метода, опрашивающего состояние свойства. Более полный список шаблонов для работы со свойствами можно найти во врезке "Свойства компонентов"
Свойства компонентов
Для определения имен методов работы со скалярными, индексными и булевыми свойствами компонентов используются следующие шаблоны:
Между свойствами различных компонентов можно установить определенные связи. Так, если при изменении одного свойства необходимо изменить и некоторые другие, то свойства называют связанными. Спецификация JavaBeans предусматривает при изменении связанных свойств инициализацию события propertyChange. Компоненты, которые отслеживают изменение данного свойства, должны подписаться на это событие и реализовать соответствующий интерфейс. Как это сделать, будет рассмотрено ниже. Есть свойства, которые могут иметь запрещенные значения, поэтому их изменение необходимо блокировать. Такие свойства называются ограниченными. Перед изменением ограниченного свойства инициализируется событие vetoableChange, которое позволяет подписчикам блокировать изменение свойства с помощью исключительной ситуации PropertyVetoException.
В процессе разработки приложения, которое использует какой-либо компонент JavaBeans, необходимо настроить работу компонента с помощью редактирования его свойств. Для этого используются специальные редакторы свойств, обычно выполняющиеся в виде формы, в которой можно установить значения каждого конкретного свойства. Если же для настройки компонента недостаточно стандартного представления его свойств в виде формы, то разработчики могут создать свой редактор свойств, который должен реализовать интерфейс PropertyEditor.
Свойства компонентов дают разработчику возможность наиболее точно настроить каждый компонент на выполнение необходимых ему функций. Это позволяет при создании новой программы использовать ранее разработанные и проверенные компоненты.
События
Второй важный элемент технологии JavaBeans - это механизм событий. Он позволяет организовать взаимодействие нескольких компонентов между собой. Выше уже были приведены примеры использования событий для определения реакции на изменение свойств какого-либо компонента. Так же можно обрабатывать нажатие клавиши, перемещение мыши и другие события, происходящие в системе.
У события есть источник, который его инициализирует, и подписчик, обрабатывающий событие тем или иным образом. Источник события должен сделать следующее:
Интерфейс события - это интерфейс, который представляет собой расширение java.util.EventListener. Он описывает все методы, вызываемые при инициализации события. Имя интерфейса событий выбирается по следующему шаблону:
interface имяСобытияListener extends java.util.EventListener
При инициализации события подписчикам передается объект, который содержит информацию о событии. Этот объект должен быть наследником класса java.util.EventObject. Шаблоны, используемые при работе с событиями, приведены во врезке "События".
События
public void removeИнтерфейсСобытия(ИнтерфейсСобытия inter); - аннулирование подписки.
Пример реализации источника события приведен во врезке "Нажми на кнопку...".
Нажми на кнопку...
Приведем пример того, как можно реализовать реакцию на нажатие кнопки. Вначале необходимо определить интерфейс события, который задает метод, вызываемый в подписчиках при распространении события:
interface KeyPressedListener extends java.util.EventListener { void KeyPressed(KeyPressedEvent kpe); }
Следующий шаг - описание объекта, который будет передаваться подписчикам при инициализации события:
public class KeyPressedEvent extends java.util.EventObject { protected transient int KeyCode; KeyPressedEvent(java.awt.Component source, int Key) { super(source); KeyCode = Key; } public int getKeyPressed() { return KeyCode; } }
И наконец, реализация собственно источника события:
public abstract class KeyPressedEventSource { private Vector listeners = new Vector(); /** Массив для хранения подписчиков */ public synchronized void addKeyPressedListener(KeyPressedListener kpl) { /** Метод для регистрации подписчиков */ listeners.addElement(kpl); } public synchronized void removeKeyPressedListener(KeyPressedListener kpl) { /** Метод для отказа от подписки */ listener.removeElement(kpl); } protected fireKeyPressed(int Key) { /** Метод возбуждения события */ Vector l; KeyPressedEvent kpe = new KeyPressedEvent(this. Key); synchronized (this) { l = (Vector)listeners.clone(); // Локальная версия подписчиков для исключения конфликтных // ситуаций в многопоточном режиме } for(int i=0; i < l.size(); i++) { ((KeyPressedListener)l.elementAt(i)).KeyPressed(kpe); } } }
Подписчик события должен выполнить следующие действия:
Фактически это означает, что класс-подписчик, для которого нужно реализовать опрос клавиатуры, должен содержать функцию обработки нажатия на кнопку (в нашем примере это метод KeyPressed), но кроме этого он должен зарегистрироваться у драйвера клавиатуры с помощью вызова метода регистрации (в нашем примере это метод addKeyPressedListener ).
Следует отметить, что в новой версии комплекта средств разработки компании JavaSoft - JDK 1.1 - механизм событий используется для обработки нажатий на клавиши, перемещения курсора и нажатия на кнопки мыши, перехода из одного окна в другое и т. д. Для этого пришлось даже изменить стандартный пакет AWT, который сейчас соответствует спецификации JavaBeans. Механизм событий позволяет обеспечить взаимодействие различных компонентов друг с другом, используя при этом открытые стандарты Java.
Таким образом, технологии Java и JavaBeans все сильнее переплетаются друг с другом. Поэтому без знания последней уже невозможно написать простейшую программу, в которой необходимо использовать реакцию на нажатие кнопки клавиатуры или мыши.
Контейнеры
Компоненты и события позволяют из ранее разработанных элементов создавать единую систему. Однако для того, чтобы это стало возможным, необходим третий элемент технологии JavaBeans - контейнеры, которые позволяют объединять несколько компонентов в один. Причем полученный элемент также может быть компонентом в еще более крупном контейнере. Таким образом, контейнеры в JavaBeans выполняют следующие функции:
Для взаимодействия контейнера с компонентами существуют два интерфейса: java.util.Collection, который описывает основные методы для работы с наборами компонентов, и java.beans. BeanContextChild, определяющий методы для взаимодействия компонентов и контейнеров.
Связь контейнера с компонентами выполняется на основе описанного выше механизма событий. В этом случае компонент должен подписываться на определенные события, инициализируемые контейнером, что позволяет централизованно обрабатывать события, а не подписываться каждому компоненту в отдельности. Разработчику компонента уже не нужно точно знать источник события - достаточно просто зарегистрироваться на определенное событие контейнера. Таким образом, контейнер выполняет роль посредника между компонентом и окружающей его средой, что упрощает разработку и настройку компонента.
Компонент поддерживает связь со своим контейнером с помощью механизма свойств. Фактически контейнер, к которому принадлежит компонент, представляет собой его свойство. Следует отметить, что свойство это ограниченное, так как контейнер может отказаться выполнять некоторые изменения. Естественно, что изменение компонента инициализирует соответствующее событие, которое передается контейнеру и далее его подписчикам. Таким образом обеспечивается взаимодействие компонента и объемлющего его контейнера.
Следует отметить, что спецификация контейнеров была добавлена позднее и поэтому не вошла в первую версию комплекта JDK 1.1. Полная версия этой спецификации есть в новой версии JavaBeans под кодовым названием Glasgow.
Дополнительные возможности
Технология JavaBeans постоянно совершенствуется, и в нее добавляются новые элементы. Среди таких дополнений технология InfoBus, предложенная компанией Lotus Development в рамках ее проекта Kona. Эта технология позволяет компонентам обмениваться данными между собой. Основная идея InfoBus заключается в том, чтобы выделить один компонент, который будет принимать данные от поставщика, оповещать о них потребителей и по мере необходимости передавать им информацию. Такая схема передачи данных позволяет потребителям получать информацию не в тот момент, когда она была передана (что характерно для обработки событий), а когда она требуется.
Описанные выше контейнеры не единственный способ групповой работы с компонентами JavaBeans. Есть другая концепция объединения компонентов - так называемая модель делегирования. Основой этой концепции является понятие агрегата - объекта, наследующего свое поведение от нескольких различных классов и интерфейсов, а также класс-координатор. Именно этот класс выдает потребителю нужного представителя, который может выполнить необходимые потребителю функции.
Кроме того, есть спецификация на подсистему Drag-and-Drop, упрощающая разработку пользовательских интерфейсов. Эта спецификация ориентируется на стандартные классы AWT и JFC и предусматривает поддержку таких платформ, как OLE (Win32), CDE/Motif и Mac OS. С помощью этой подсистемы можно будет легко управлять передачей данных от одного компонента к другому с помощью специального буфера (clipboard). Таким образом, модель JavaBeans упрощает разработку всех элементов программного обеспечения - от многопоточных приложений до графического интерфейса пользователя.