Суть шаблона наблюдателя заключается в том, чтобы «определить зависимость один-ко-многим между объектами, чтобы при изменении состояния одного объекта все его иждивенцы уведомлялись и обновлялись автоматически». GoF. Шаблон наблюдателя — это подмножество шаблона публикации / подписки, которое позволяет ряду объектов-наблюдателей видеть событие.
Этот шаблон можно использовать в разных ситуациях, но в целом можно сказать, что шаблон Observer можно применять, когда объект должен иметь возможность уведомлять сообщения другим объектам, и вы не хотите, чтобы эти объекты были тесно связаны. В моем случае я использовал этот шаблон, когда асинхронное событие должно быть сообщено одному или нескольким графическим компонентам.
Этот шаблон может быть реализован с использованием решения adhoc или классов java.util.Observer/Observable . Но мои проекты всегда разрабатываются с помощью Spring, будь то веб- приложения или приложения для настольных компьютеров . Итак, в этом посте я объясню, как реализовать шаблон Observer с помощью Spring .
РУКИ ВВЕРХ
Обработка событий в Spring ApplicationContext обеспечивается через класс ApplicationEvent и интерфейс ApplicationListener . Если компонент, реализующий интерфейс ApplicationListener, развертывается в контексте, каждый раз, когда ApplicationEvent публикуется в контейнере, ApplicationListener получает его.
Spring поставляется со встроенными событиями, такими как ContextStartedEvent , ContextStoppedEvent , но вы также можете создавать свои собственные пользовательские события.
Для разработки ваших собственных событий требуются три класса: роль наблюдателя , наблюдаемая роль и событие . Наблюдатели — это те, кто получает события и должен реализовать класс ApplicationListener . Наблюдаемые классы отвечают за публикацию событий и должны реализовывать ApplicationEventPublisherAware . Наконец, класс события должен расширять ApplicationEvent .
КОДИРОВАНИЕ
То, что я собираюсь реализовать, это пример википедии шаблона Observer ( http://en.wikipedia.org/wiki/Observer_pattern#Example ), но с использованием Spring Events вместо Java- классов Observer / Observable . Этот пример является базовым примером публикации / подписки, где одно сообщение String отправляется из одного модуля в другой.
Давайте создадим MessageEvent . Это событие содержит строку, которая представляет сообщение, которое мы хотим отправить. Это простой класс, который выходит из ApplicationEvent .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class MessageEvent extends ApplicationEvent { ** * * private static final long serialVersionUID = 5743058377815147529L; private String message; public MessageEvent(Object source, String message) { super (source); this .message = message; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append( 'MessageEvent [message=' ).append(message).append( ']' ); return builder.toString(); } } |
Следующий класс — наблюдаемый класс. Этот класс должен реализовывать ApplicationEventPublisherAware . Этот интерфейс определяет метод установки с ApplicationEventPublisher в качестве параметра. Этот параметр используется для публикации событий.
В текущей реализации видим, что также реализует интерфейс Runnable, так что пользователь может создавать с консоли ввода,
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class EventSource implements Runnable, ApplicationEventPublisherAware { private ApplicationEventPublisher applicationEventPublisher = null ; public void setApplicationEventPublisher( ApplicationEventPublisher applicationEventPublisher) { this .applicationEventPublisher = applicationEventPublisher; } public void run() { final InputStreamReader isr = new InputStreamReader(System.in); final BufferedReader br = new BufferedReader(isr); while ( true ) { try { String response = br.readLine(); System.out.println(Thread.currentThread().getName()); this .applicationEventPublisher.publishEvent( new MessageEvent( this , response)); } catch (IOException e) { e.printStackTrace(); } } } } |
Класс Observer еще проще. Реализует интерфейс ApplicationListener . Метод onApplicationEvent вызывается при публикации события. Обратите внимание, что это универсальный интерфейс, так что приведение не требуется. Это отличается от класса java.util.Observer .
1
2
3
4
5
6
7
8
9
|
public class ResponseHandler implements ApplicationListener<MessageEvent> { public void onApplicationEvent(MessageEvent messageEvent) { System.out.println(Thread.currentThread().getName()); System.out.println(messageEvent); } } |
В файле контекста приложения вы регистрируете компоненты ApplicationListener и ApplicationEventPublisherAware .
И, наконец, основной класс для тестирования системы. Поток создается для выполнения нескольких асинхронных событий.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
public class MyApp { public static void main(String args[]) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext( 'classpath:META-INFspringapp-context.xml' ); EventSource eventSource = applicationContext.getBean( 'eventSource' , EventSource. class ); Thread thread = new Thread(eventSource); thread.start(); } } |
Итак, запустите программу и запишите что-нибудь в консоль. Вы увидите что-то вроде:
Привет Тема-0 Тема-0 MessageEvent [message = hello]
Я ввел приветственное сообщение, и печатается название темы издателя событий . Затем отправляется событие и выводится имя потока обработчика . Наконец полученное событие показывается. Есть одна вещь, которая должна привлечь ваше внимание. И отправитель ( наблюдаемый ), и получатель ( наблюдатель ) выполняются в одном потоке; по умолчанию слушатели событий получают события синхронно. Это означает, что метод publishEvent () блокирует, пока все слушатели не закончили обработку события. Этот подход имеет много преимуществ (например, повторное использование контекста транзакции,…), но в некоторых случаях вы предпочитаете, чтобы каждое событие выполнялось в новом потоке, Spring также поддерживает эту стратегию.
Весной класс, отвечающий за управление событиями, — SimpleApplicationEventMulticaster . Этот класс осуществляет многоадресную передачу всех событий всем зарегистрированным слушателям, предоставляя слушателям возможность игнорировать события, в которых они не заинтересованы. По умолчанию все слушатели вызываются в вызывающем потоке.
Теперь я собираюсь объяснить, как инициализируется Spring Event Architecture и как вы можете его изменить. По умолчанию, когда ApplicationContext запустил, он вызывает метод initApplicationEventMulticaster . Этот метод проверяет, существует ли компонент с идентификатором applicationEventMulticaster типа ApplicationEventMulticaster . Если это так, то используется ApplicationEventMulticaster , если нет, то создается новый SimpleApplicationEventMulticaster с конфигурацией по умолчанию.
SimpleApplicationEventMulticaster имеет setTaskExecutor, который можно использовать для указания того, какой java.util.concurrent.Executor будет выполнять события. Поэтому, если вы хотите, чтобы каждое событие выполнялось в другом потоке, хорошим подходом было бы использование ThreadPoolExecutor . Как объяснялось в предыдущем абзаце, теперь мы должны явно определить SimpleApplicationEventMulticaster вместо используя по умолчанию. Давайте реализуем:
1
2
3
4
5
6
7
8
|
<beans xmlns= 'http:www.springframework.orgschemabeans' xmlns:xsi= 'http:www.w3.org2001XMLSchema-instance' xmlns:context= 'http:www.springframework.orgschemacontext' xmlns:task= 'http:www.springframework.orgschematask' xsi:schemaLocation= 'http:www.springframework.orgschematask http:www.springframework.orgschemataskspring-task-3.0.xsd http:www.springframework.orgschemabeans http:www.springframework.orgschemabeansspring-beans-3.0.xsd http:www.springframework.orgschemacontext http:www.springframework.orgschemacontextspring-context-3.0.xsd' > <bean id= 'eventSource' class = 'org.asotobu.oo.EventSource' > <bean id= 'responseHandler' class = 'org.asotobu.oo.ResponseHandler' > <task:executor id= 'pool' pool-size= '10' > <bean id= 'applicationEventMulticaster' class = 'org.springframework.context.event.SimpleApplicationEventMulticaster' > <property name= 'taskExecutor' ref= 'pool' > <bean> <beans> |
Прежде всего SimpleApplicationEventMulticaster должен быть определен как компонент с идентификатором applicationEventMulticaster . Затем устанавливается пул задач, и мы снова запускаем наш основной класс. И вывод будет:
привет поток-1 пул-1 MessageEvent [сообщение = привет]
Обратите внимание, что теперь поток отправителя и получателя отличается.
И, конечно, вы можете создать свой собственный ApplicationEventMulticaster для более сложных операций. Вам просто нужно реализовать ApplicationEventMulticaster и определить его с помощью имени компонента applicationEventMulticaster , и события будут выполняться в зависимости от вашей собственной стратегии.
Надеюсь, что теперь ваши настольные приложения Spring могут в полной мере использовать события Spring для разделения модулей.
Ссылка: шаблон наблюдателя с весенними событиями от нашего партнера по JCG Алекса Сото в блоге One Jar To Rule All .