Статьи

JSF Событийное общение: олдскульный подход

Веб-приложения, написанные на JSF, состоят из компонентов, которые взаимодействуют друг с другом. Связь между компонентами является одним из основных шаблонов проектирования при разработке веб-приложения. Иногда одному компоненту нужно отправлять события другим компонентам, чтобы информировать их о некоторых изменениях или о чем-либо еще. Обычно мы можем внедрить управляемый или Spring bean-компонент в свойство другого bean-компонента, чтобы другой bean-компонент мог напрямую уведомить о введенном bean-компоненте. Инъекция это хорошо, но она не была введена с целью общения. Это далеко от динамически слабосвязанной системы, где каждый компонент не знает о других компонентах. В слабо связанной системе нам нужен хороший механизм связи, основанный на событиях. В этом посте будут рассмотрены два шаблона проектирования: Observer / Event Listener и шаблон Mediator. Эти шаблоны в настоящее время широко используются во многих веб-приложениях, но у них есть недостатки. Система не очень слабо связана с ними. Есть гораздо лучшие и современные подходы. Поэтому я написал «олдскульный подход» в названии поста. Новые школьные подходы будут раскрыты в следующем посте.

Наблюдатель / Слушатель событий
 

Мы начнем с шаблона Observer (также называемого Event Listener). Объект, называемый субъектом или наблюдаемым объектом, поддерживает список своих зависимых, называемых наблюдателями, и автоматически уведомляет их о любых изменениях состояния. В Java есть классы java.util.Observer и java.util.Observable, которые помогают реализовать этот шаблон. Другими связанными конструкциями для связи на основе событий с помощью этого шаблона являются класс java.util.EventObject и интерфейс java.util.EventListener. Давайте начнем кодировать. Предположим, у нас есть веб-приложение I18N, и пользователь может выбрать язык (Locale) где-то в настройках пользователя. Предположим, у нас есть компонент с именем UserSettingsForm, который отвечает за пользовательские настройки. Некоторые bean-объекты области видимости могут сохранять текст / сообщения I18N, поэтому при изменении пользователем текущих языков требуется сброс предыдущих текстов / сообщений на последнем выбранном языке. Во-первых, нам нужен LocaleChangeEvent.
01
02
03
04
05
06
07
08
09
10
11
12
13
public class LocaleChangeEvent extends EventObject {
     
    Locale locale;
 
    public LocaleChangeEvent(Object source, Locale locale) {
        super(source);
        this.locale = locale;
    }
 
    public Locale getLocale() {
        return locale;
    }
}

Во-вторых, нам нужен интерфейс LocaleChangeListener.

1
2
3
4
public interface LocaleChangeListener extends EventListener {
     
    void processLocaleChange(LocaleChangeEvent event);
}
Наш UserSettingsForm теперь может управлять экземплярами типа LocaleChangeListener, регистрируя и уведомляя их.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
@ManagedBean
@SessionScoped
public class UserSettingsForm implements Serializable {
 
    private Locale selectedLocale;
    private List<SelectItem> locales;
    private List<LocaleChangeListener> localeChangeListeners = new ArrayList<LocaleChangeListener>();
 
    public void addLocaleChangeListener(LocaleChangeListener listener) {
        localeChangeListeners.add(listener);
    }
 
    public void localChangeListener(ValueChangeEvent e) {
        ...
        // notify listeners
        LocaleChangeEvent lce = new LocaleChangeEvent(this, this.selectedLocale);
        for (LocaleChangeListener lcl : localeChangeListeners) {
            lcl.processLocaleChange(lce);
        }
    }
    ...
}
Метод localChangeListener () является JSF ValueChangeListener и может применяться, например, в h: selectOneMenu. Каждый компонент, который реализует LocaleChangeListener, должен быть зарегистрирован UserSettingsForm, чтобы получать уведомления об изменениях локали.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@ManagedBean
@SessionScoped
public MyBean implements LocaleChangeListener, Serializable {
 
    // UserSettingsForm can be injected e.g. via @ManagedProperty annotation or via Spring facility
    private UserSettingsForm userSettingsForm;
 
    @PostConstruct
    public void initialize() {
        userSettingsForm.addLocaleChangeListener(this);
    }
 
    public void processLocaleChange(LocaleChangeEvent event) {
        // reset something related to I18N data
        ...
    }
}

С точки зрения шаблона Observer UserSettingsForm является Observable, а экземпляры LocaleChangeListener (например, MyBean) являются Observers. Обсуждаемый шаблон сопровождается некоторыми важными вопросами, о которых вам необходимо знать. Бобы тесно связаны. Есть много ручной работы, чтобы обработать бобы. Бины должны реализовывать определенные интерфейсы. Если ваш бин информирован о 100 различных семантических изменениях, он должен реализовать 100 интерфейсов. Невозможно уведомить подмножество зарегистрированных слушателей — всегда все слушатели получают уведомление, даже если они не нуждаются в уведомлении. Последнее, но не менее важное — проблема управления памятью . Мартин Фаулер написал: «Предположим, у нас есть несколько экранов, наблюдающих некоторые доменные объекты. Как только мы закрываем экран, мы хотим, чтобы он был удален, но доменные объекты на самом деле несут ссылку на экран через отношения наблюдателя. В среде с управлением памятью долгоживущие доменные объекты могут удерживать множество экранов зомби, что приводит к значительной утечке памяти ».

медиатор
 

Шаблон Mediator улучшает взаимодействие на основе событий по сравнению с шаблоном Observer / Event Listener. При использовании шаблона-посредника связь между объектами инкапсулируется с объектом-посредником. Объекты больше не общаются напрямую друг с другом, а вместо этого общаются через посредника. Это уменьшает зависимости между взаимодействующими объектами. Мы увидим, как это работает для bean-компонентов JSF-Spring (в приведенных выше примерах были стандартные управляемые bean-компоненты). Мы реализуем класс Mediator для управления связью между bean-объектами. Важно понимать, что только bean-компонент может уведомлять другие bean-объекты, имеющие более широкий охват. Bean-объект области видимости может уведомлять bean-объекты области видимости, сеанса и приложения, но не запрашивать bean-объекты области действия с более узкой областью действия. Следуйте этому правилу, чтобы избежать неприятностей. Это природа bean-объекта bean-объекта — вы можете помнить, что вы всегда можете внедрить bean-компонент более широкого диапазона в bean-компонент более узкого диапазона, но не наоборот. Для начала работы с Mediator представим два интерфейса MediatorEvent, MediatorListener и центрический класс Mediator.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public interface MediatorEvent {
    ...
}
 
public interface MediatorListener {
 
    public void listenToEvent(MediatorEvent event);
}
 
public class Mediator implements Serializable {
 
    private Collection<MediatorListener> collaborators = new HashSet<MediatorListener>();
 
    public static Mediator getCurrentInstance() {
        // access Mediator bean by JSF-Spring facility
        return ContextLoader.getCurrentWebApplicationContext().getBean("mediator");
    }
 
    public void fireEvent(MediatorEvent event) {
        for (MediatorListener mediatorListener : collaborators) {
            mediatorListener.listenToEvent(event);
        }
    }
 
    public void addCollaborator(MediatorListener collaborator) {
        collaborators.add(collaborator);
    }
 
    public void removeCollaborator(MediatorListener collaborator) {
        collaborators.remove(collaborator);
    }
}
Посредник — это bean-объект с заданной областью действия, который может регистрировать и уведомлять сотрудников. Соавторы регистрируют себя посредником. В Spring бин может реализовывать интерфейс InitializingBean, так что метод afterPropertiesSet () будет вызываться автоматически после создания экземпляра бина. Это похоже на @PostConstruct. afterPropertiesSet () — это подходящее место для регистрации такого бина посредником. Бин также должен реализовывать MediatorListener для того, чтобы получать уведомления (см. ListenToEvent ()).
01
02
03
04
05
06
07
08
09
10
11
12
13
14
public MyBean implements MediatorListener, InitializingBean, Serializable {
 
    public void afterPropertiesSet() throws Exception {
        ...
        Mediator.getCurrentInstance().addCollaborator(this);
    }
 
    @Override
    public void listenToEvent(MediatorEvent event) {
        if (event instanceof LocaleChangeEvent) {
            // do something
        }
    }
}
Мы будем использовать тот же сценарий с UserSettingsForm и изменением локали. Бобы, зарегистрированные посредником, будут уведомлены fireEvent ().
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class LocaleChangeEvent implements MediatorEvent {
    ...
}
 
public class UserSettingsForm implements Serializable {
 
    private Locale selectedLocale;
    private List<SelectItem> locales;
 
    public void localChangeListener(ValueChangeEvent e) {
        ...
        // notify listeners
        Mediator.getCurrentInstance().fireEvent(new LocaleChangeEvent(this, this.selectedLocale));
    }
    ...
}
Модель посредника обеспечивает лучшую связь между бобами, но они все еще связаны с посредником. Дополнительные недостатки: по-прежнему необходимо регистрировать компоненты вручную — см. Дополнительный код Mediator.getCurrentInstance (). AddCollaborator (this). Каждый бин должен по-прежнему реализовывать хотя бы один MediatorListener, и это приводит к другому ограничению — listenToEvent (). Каждый компонент должен реализовывать этот метод интерфейса! Вероятно, самый большой недостаток паттерна-посредника в JSF заключается в том, что это bean-объект с заданной областью действия. Посредник в области видимости будет работать плавно только с фасолями в области видимости. Бины с областью видимости автоматически удаляются при уничтожении посредника с областью видимости. Другие сценарии могут вызвать утечки памяти или несколько проблем. Например, запрашиваемые bean-компоненты в области видимости, зарегистрированные посредником в области представления, должны быть удалены вручную путем вызова removeCollaborator () (легко забываемого). Сессионные компоненты должны регистрироваться посредником сессионной области, в противном случае они не будут уведомлены после уничтожения посредника с областью представления. И т. Д.
На самом деле, шаблон Mediator всего на один шаг лучше, чем обычная концепция Observer / Event Listener. Существуют более гибкие подходы, в которых * любой метод * может перехватывать выброшенное событие, а не только фиксировать указанное, например, listenToEvent (). В следующем посте мы увидим простые и ненавязчивые способы, как перехватывать события, умноженные только одним методом и другими советами.

Ссылка: Событийное общение в JSF. Олдскульный подход от нашего партнера JCG Олега Вараксина в блоге Мысли о разработке программного обеспечения .