Технология JavaBeans.
Что такое JavaBeans
В исходной документации по JavaBeans от Sun определено: "Целью технологии JavaBeans является определение модели программных компонент такой, что фирмы-разработчики ( third party firms ) могут создавать и устанавливать Java-компоненты, которые могут быть скомпонованы конечными пользователями в законченные приложения".
Т. е. здесь речь идет о компонентном программировании и JavaBean — это технология создания и использования программных компонент (обычно визуальных, хотя не обязательно). В JavaBean программные компоненты, которые являются как бы кирпичиками программы, называются Beans (в переводе — бобы). Мы будем далее их именовать бинами .
В компонентном программировании подразумевается наличие не только самих компонент, но и некоторой визуальной среды разработки, позволяющей в диалоге строить программу из этих компонент. Причем, результат процесса сразу виден на экране. Технология JavaBean также неявно подразумевает наличие такой среды, но никоим образом не определяет ее. Соответствующие диалоговые среды разработки есть и, наверное, еще будут созданы новые. Эти среды отличаются друг от друга, иногда значительно, но все опираются на JavaBean, который является в этом смысле некоторым стандартом.
По адресу http://jsp2.java. /products/javabeans/software/bdk_download. html можно загрузить Bean Development Kit ( BDK ), основу которого составляет BeanBox , но это скорее демонстрационное средство, а не инструмент для программирования.
В процессе компонентного программирования можно выделить три группы действующих лиц, или три роли. Во-первых, это конечный пользователь, т. е. прикладной программист, который в визуальной среде собирает программу из отдельных компонент. Во-вторых, это разработчик готовых компонент. И, в-третьих, это разработчик визуальных сред компоновки программ.
Соответственно, тут возможны варианты. Можно, например, создать среду и технологию построения компонент под эту среду. Именно так поступила MicroSoft, создав Visual Basic и технологии OLE и ActiveX. А можно сделать универсальную технологию, которая позволяла бы не только создавать компоненты, но и визуальные среды, использующие эти компоненты. JavaBean создана в расчете именно на этот вариант.
Кроме того, нужно учитывать, что универсальная технология, претендующая на стандарт, должна учитывать интересы всех указанных групп действующих лиц. Она должна иметь средства, позволяющие разработчикам визуальных сред подключать различные компоненты в палитру доступных компонент, что вызывает необходимость наличия средств анализа готовых компонент. Она должна иметь правила разработки компонент, с тем, чтобы разработанная компонента могла быть интегрирована в визуальную среду. И, наконец, она должна определять средства связи компонент, которые прикладной программист использует для объединения готовых компонент в законченное приложение.
Три указанные роли являются, конечно, некоторым идеалом и в реальности эти роли зачастую пересекаются. Так, фирмы разработчики визуальных сред (таких как, JBuilder, Semantec Cafe, VisualJ и др.) включают в состав своих продуктов разработанные ими библиотеки, содержащие бины; разработчики прикладного ПО в процессе разработки не только используют существующие бины, но и создают свои.
Что такое Bean
В документации от Sun бин определяется так: "A Java Bean is a reusable software component that can be manipulated visually in a builder tool." ("Java Bean это многократно используемый программный компонент, которым можно манипулировать визуально в (визуальных) средах разработки").
В простейшем случае бин — это отдельный класс, представляющий определенную компоненту. В более сложных случаях — это набор взаимосвязанных классов, каждый из которых играет определенную роль. Так многие классы стандартной библиотеки Java являются бинами, например, JLabel, JTextField и др.
Основной класс бина должен удовлетворять одному требованию — он должен иметь конструктор по умолчанию (default constructor). Это требование естественно. Предполагается, что визуальная среда будет создавать экземпляры бинов и использовать для этого конструкторы по умолчанию. Есть и другие требования к бинам. Мы их рассмотрим далее.
Бины могут быть совершенно разными как по размерам, сложности, так и по области применения. Каждый конкретный бин может поддерживать ту или иную степень функциональности, но типичные универсальные возможности, которые обеспечивает бин следующие.
· Поддерживает "интроспекцию" (introspection), что позволяет средам разработки анализировать из чего состоит и как работает данный бин.
· Обеспечивает настраиваемость (customization), т. е. возможность изменять внешний вид (положение, размеры и т. п.) и поведение данного бина.
· Обеспечивает поддержку "событий" (events) как средства связи данного бина с программой и другими бинами.
· Обеспечивает поддержку свойств или атрибутов (properties), которые используются, в частности, для настройки (например, ширина, высота, количество каких-либо составных подкомпонент и т. п.).
· Поддерживает "сохраняемость" (persistence). Это необходимо для того, чтобы после настройки конкретного бина в некоторой визуальной среде разработки была возможность сохранить параметры настройки, а потом их восстановить.
Рассмотрим эти возможности подробнее. И начнем с конца, с persistence ("сохраняемости"), как с самого простого.
Это свойство обеспечивается выполнением следующего требования. Каждый бин в заголовке описания класса должен содержать " implements java. io. Serializable ", т. е. бины должны быть сериализуемыми. Предполагается, что визуальная среда при сохранении скомпонованного приложения дополнительно сохраняет настройки компонент, сделанные пользователем в процессе разработки приложения и делает она это путем сериализации бина, например, в некоторый файл. При повторном входе в среду разработки и загрузке приложения эти настройки восстанавливаются. Для этого среда разработки просто десериализует бины из файла.
Свойства бинов (Bean properties)
Обычно каждый бин имеет свойства, которые определяют, как он будет работать и/или как он будет выглядеть. Эти свойства являются private или protected полями класса бина, которые доступны для выборки и/или модификации через специальные public методы. Другими словами бин обеспечивает доступ к своим свойствам через public методы 'get...' и 'set...'. Эти методы называют аксессорами (accessor) или, жаргонно, getters и setters и имеют определенные правила построения. Так утверждение "данный бин имеет свойство name типа String " означает, что у этого бина
· есть поле
· private String name;
·
· есть get-метод
· public String getName() {
· return name;
· }
·
· есть set-метод
· public void setString(String name) {
· this. name = name;
· }
·
Такой подход соответствует общим принципам объектно-ориентированного программирования, когда внутренние поля класса недоступны непосредственно и могут быть извлечены/изменены только посредством вызова методов класса.
Рассмотрим, к примеру, JLabel по документации. Во-первых, JLabel удовлетворяет интерфейсу Serializable, во-вторых, имеет конструктор по умолчанию
public JLabel()
и, в-третьих, имеет ряд методов аксессоров, например,
public String getText()
public void setText(String text)
Исходя из этого, можно сделать вывод, что JLabel является бином, имеющим атрибут (свойство) text. Кроме того, JLabel имеет и другие пары get-/set-методов, т. е. имеет и другие атрибуты.
Атрибуты бинов могут быть как элементарных типов ( int, long и т. п.), так и стандартных типов Java (например, String ), а также пользовательских типов (например, MyType , где MyType — класс, определенный пользователем).
· Для свойств типа boolean вместо get-метода используется is-метод. Например, JLabel имеет boolean-свойство enabled, унаследованное от класса Component . Для доступа к этому свойству имеются методы
· public boolean isEnabled()
·
· public void setEnabled(boolean b)
·
Правила построение методов доступа к атрибутам (аксессоров)
Аксессоры строятся по следующим правилам
· public void set < Property_name> (< Property_type> value);
· public < Property_type> get < Property_name>();
· public boolean is < Property_name>();
Эти правила относятся к простым свойствам. Кроме того, свойства могут быть индексированными или, другими словами, атрибут бина может быть массивом.
Для индексированных свойств выработаны следующие правила.
Они должны быть описаны как поля-массивы, например,
private String[] messages;
и должны быть такие методы
· public < Property_type> get < Property_name>(int index);
· public void set < Property_name> (int index, < Property_type> value);
· public < Property_type>[] get < Property_name>();
· public void set < Property_name> (< Property_type>[] value);
Так, для приведенного выше примера должны быть методы
public String getMessages(int index);
public void setMessages(int index, String message);
public String[] getMessages();
public void setMessages(String[] messages);
Bean-методы
Кроме аксессоров, бин может иметь любое количество других методов, как обычный класс Java.
Интроспекция бинов при помощи reflection API
Описанных выше правил достаточно для осуществления простейшей интроспекции бинов с использованием reflection API. Вспомним возможности интроспекции, рассмотренные нами ранее.
Для использования бина визуальная среда должна знать полное имя класса бина. По полному имени класса можно статическим методом forName класса Class получить объект класса Class для данного бина. И далее, используя возможности класса Class, получить всю необходимую информацию по данному методу.
В частности, можно получить список всех public -методов данного класса. Исследуя их имена можно выделить из них аксессоры и определить какие атрибуты (свойства) есть у данного бина и какого они типа. Все остальные методы, не распознанные как аксессоры являются bean-методами.
В результате соответствующая визуальная среда разработки может построить диалог, в котором будет предоставлена возможность задавать значения этих атрибутов. Наличие конструктора по умолчанию позволяет построить объект bean-класса, set-методы позволят установить в этом объекте значения атрибутов, введенные пользователем, а благодаря сериализации объект с заданными атрибутами можно сохранить в файле и восстановить значение объекта при следующем сеансе работы с данной визуальной средой. Более того, можно изобразить на экране внешний вид бина (если это визуализируемый бин) в процессе разработки и менять этот вид в соответствии с задаваемыми пользователем значениями атрибутов.
Связанные свойства (bound properties) и события
Еще одним важным аспектом технологии JavaBeans является возможность бинов взаимодействовать с другими объектами, в частности, с другими бинами. JavaBeans реализует такое взаимодействие путем генерации (firing) событий и прослушивания (listening) событий.
В приложении к бинам взаимодействие объектов с бином через событийную модель выглядит так. Объект, который интересуется тем, что может произойти во внешнем, по отношению к нему, бине, может зарегистрировать себя как слушателя (listener) этого бина. В результате, при возникновении соответствующего события в бине будет вызван определенный метод данного объекта, которому в качестве параметра будет передан объект-событие (event). Причем, если зарегистрировалось несколько слушателей, то эти методы будут последовательно вызваны для каждого слушателя.
Такой механизм взаимодействия является очень гибким, поскольку два объекта - бин и его слушатель, связаны только посредством данного метода и параметра-события. Модификации в структуре и алгоритмах работы двух этих объектов очень редко влияют на эту связь.
Одним из способов экспорта событий является использование связанных свойств. Когда значение связанного свойства меняется, генерируется событие и передается всем зарегистрированным слушателям посредством вызова метода propertyChange .
Приведем реальный пример. Рассмотрим текстовый редактор, позволяющий редактировать один файл. Такой пример мы уже рассматривали (Dlg5.java, занятие 17) и реализовали текущий редактируемый файл просто как поле типа File с именем currentFile. Изначально переменная currentFile не установлена. Она устанавливается при сохранении или чтении файла.
Теперь, если мы захотим добавить в наше приложение вывод какой-то информации по данному файлу, например, вывод имени файла где-нибудь внизу или вверху экрана, нам придется внимательно отследить, где в программе может изменятся это поле и вставить в этих точках обновление имени файла на экране. В данном приложении это вполне приемлемо, так как оно является небольшим и обозримым, но в более сложных случаях это может вызвать проблемы.
Альтернативный вариант состоит в построении бина со связанным свойством currentFile . Тогда при любой модификации этого свойства будет генерироваться событие. После этого мы можем создавать любое количество объектов, интересующихся значением свойства currentFile. Нам не придется при этом вносить какие-либо изменения в разработанный уже бин. Просто каждый новый такой объект должен зарегистрировать себя как слушателя данного бина. Для этого он должен иметь метод propertyChange, а в нем должен быть код, реагирующий на изменение свойства currentFile .
Создание и использование связанного свойства
Разберемся практически, как создавать и использовать связанные свойства.
Начнем с события, которое должно быть сгенерировано при изменении связанного свойства. Это событие класса java. beans. PropertyChangeEvent (см. документацию).
Далее можно действовать по следующей инструкции.
· 1. Для регистрации/дерегистрации слушателя необходимо в бине реализовать два метода:
· public void addPropertyChangeListener(PropertyChangeListener pcl)
·
и
public void removePropertyChangeListener(PropertyChangeListener pcl)
· 2. Чтобы не реализовывать их вручную лучше воспользоваться существующим классом java. beans. PropertyChangeSupport (см. документацию).
· 3. В set-методе связанного свойства необходимо добавить вызов метода класса java. beans. PropertyChangeSupport — firePropertyChange.
· 4. В классе-слушателе необходимо реализовать интерфейс PropertyChangeListener, т. е. в заголовке класса записать " implements PropertyChangeListener ", а в теле класса реализовать метод
· public void propertyChange(PropertyChangeEvent evt)
·
· 5. Создать объект-слушатель и зарегистрировать его как слушателя нашего бина при помощи метода addPropertyChangeListener , который был нами реализован в п.1. Лучше всего это сделать сразу после порождения объекта-слушателя, например,
· MyLitener obj = new MyListener();
· myBean. addPropertyChangeListener(obj);
·
где myBean — наш бин (имеется в виду объект, а не класс).
Пункт 4-й должен быть реализован для каждого класса-слушателя, а п.5 — для каждого порожденного объекта-слушателя.
Разберемся подробнее с пунктами 2 и 3.
С событиями мы знакомились в рамках 20-го занятия. Но там мы рассматривали подключение слушателей к объектам источникам событий. Сейчас же нам нужно реализовать генерацию событий. Наш бин должен генерировать событие PropertyChangeEvent при изменении связанного свойства (п.3). Кроме того, согласно правилам событийной модели Java он должен обеспечивать регистрацию/дерегистрацию слушателей при помощи соответствующих методов add...Listener / remove...Listener (п.2).
Т. е. нам нужно обеспечить наличие в бине некоторого списка слушателей, а также методы addPropertyChangeListener и removePropertyChangeListener.
К счастью, нам не требуется программировать все это. Соответствующий инструментарий уже подготовлен в пакете java. beans — это класс java. beans. PropertyChangeSupport. Он обеспечивает регистрацию слушателей и методы firePropertyChange , которые можно использовать в тех местах, где требуется сгенерировать событие, т. е. в set-методах, которые изменяют значение связанных атрибутов.
Разберем это на примере.
Пусть мы имеем некоторый бин SomeBean с одним свойством someProperty :
public class SomeBean {
private String someProperty = null;
public SomeBean() {
}
public String getSomeProperty() {
return someProperty;
}
public void setSomeProperty(String value) {
someProperty = value;
}
}
Переделаем его так, чтобы сойство someProperty стало связанным:
import java. beans.*;
public class SomeBean {
private String someProperty = null;
private PropertyChangeSupport pcs;
public SomeBean() {
pcs = new PropertyChangeSupport(this);
}
public void addPropertyChangeListener(PropertyChangeListener pcl) {
pcs. addPropertyChangeListener(pcl);
}
public void removePropertyChangeListener(PropertyChangeListener pcl) {
pcs. removePropertyChangeListener(pcl);
}
public String getSomeProperty() {
return someProperty;
}
public void setSomeProperty(String value) {
pcs. firePropertyChange("someProperty", someProperty, value);
someProperty = value;
}
}
Здесь мы реализовали пункты 1, 2 и 3 приведенной инструкции. Остальные пункты относятся к использованию связанного свойства и для их демонстрации потребуется более реальный пример.
Для обеспечения механизма генерации событий в классе SomeBean создан объект класса PropertyChangeSupport (поле pcs ). И все действия по регистрации/дерегистрции слушателей, по собственно генерации событий "переадресуются" этому объекту, который за нас выполняет всю эту рутинную работу.
Так, например, метод
public void addPropertyChangeListener(PropertyChangeListener pcl)
нашего класса просто обращается к одноименному методу класса PropertyChangeSupport .
В методе setSomeProperty перед собственно изменением значения свойства someProperty генерируется событие PropertyChangeEvent. Для этого вызывается метод firePropertyChange, который обеспечивает все необходимые для такой генерации действия.
Как видно из кода примера, результат не очень громоздкий, несмотря на то, что наш бин реализует достаточно сложное поведение.
Практическая работа
Добавим в наш пример редактирования файла (Dlg5.java) метку в нижнюю область экрана. Будем выводить в нее информацию: имя файла и количество строк файла. Для этого реализуем наш основной класс как бин со связанным свойством currentFile. А для метки построим класс-слушатель на основе JLabel, создадим объект этого класса, добавим его на экран и зарегистрируем как слушателя основного класса.
Проделайте это сами. Если не получится, решение можно загрузить отсюда: Dlg5.java .
Ограниченные свойства (constrained properties)
Кроме понятия связанных свойств в JavaBeans есть понятие ограниченных свойств (constrained properties). Ограниченные свойства введены для того, чтобы была возможность запретить изменение свойства бина, если это необходимо. Т. е. бин будет как-бы спрашивать разрешение у зарегистрированных слушателей на изменение данного свойства. В случае, если слушатель не разрешает ему менять свойство, он генерирует исключение PropertyVetoException. Соответственно set-метод для ограниченного свойства должен иметь в своем описании throws PropertyVetoException. Это заставляет перехватывать это исключение в точке вызова этого set-метода. В результате прикладная программа, использующая этот бин, будет извещена, что ограниченное свойство не было изменено.
В остальном ограниченные свойства очень похожи на связанные свойства. Как и все свойства они имеют get - и set-методы. Но для них set-методы могут генерировать исключение PropertyVetoException. Т. е. они имеют вид
public void set < Property_name> (< Property_type> param) throws PropertyVetoException
Второе отличие заключается в именах методов для регистрации/ дерегистрации слушателей. Вместо методов addPropertyChangeListener и removePropertyChangeListener для ограниченных свойств применяются методы
addVetoableChangeListener(VetoableChangeListener v)
и
removeVetoableChangeListener(VetoableChangeListener v)
Здесь VetoableChangeListener — интерфейс с единственным методом
public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException
По аналогии со вспомогательным классом PropertyChangeSupport , который используется при реализации связанных свойств, для ограниченных свойств в пакете java. beans есть вспомогательный класс VetoableChangeSupport. В нем реализованы алгоритмы, необходимые для поддержки событий ограниченных свойств.
Рассмотрим это на примере.
Вспомним класс SomeBean, рассмотренный ранее. Его свойство someProperty мы реализовали как связанное. Переделаем пример и реализуем его как ограниченное.
import java. beans.*;
public class SomeBean {
private String someProperty = null;
private VetoableChangeSupport vcs;
public SomeBean() {
vcs = new VetoableChangeSupport (this);
}
public void addVetableChangeListener(VetoableChangeListener pcl) {
vcs. addVetoableChangeListener(pcl);
}
public void removeVetoableChangeListener(VetoableChangeListener pcl) {
vcs. removeVetoableChangeListener(pcl);
}
public String getSomeProperty() {
return someProperty;
}
public void setSomeProperty(String value) throws PropertyVetoException {
vcs. fireVetoableChange("someProperty", someProperty, value);
someProperty = value;
}
}
Как видим, принципиально ничего не изменилось. Только вместо PropertyChangeSupport использован VetoableChangeSupport и в описании set-метода добавлено throws PropertyVetoException. Теперь someProperty является ограниченным свойством и зарегистрировавшийся слушатель может запретить его изменение.
Рассмотренные возможности организации связи бина с другими компонентами не являются единственно возможными. Бин, как и любой класс, может быть источником событий и/или слушателем. И эти события могут быть не связаны с изменением свойств бина.
В таких случаях обычно используют существующие события, типа ActionEvent, хотя можно построить и свои события. Лучше пользоваться существующими событиями, поскольку предполагается, что бин будет использоваться совместно с другими классами и бинами при разработке приложений и наличие в нем каких-то уникальных возможностей может помешать его интеграции в законченное приложение.


