Статьи

Как использовать события в Spring 3.x

Существует множество концепций и методов для создания слабосвязанных приложений, одним из которых является Event. События могут устранить многие зависимости в вашем коде. Иногда без событий SRP * очень сложно реализовать. Наблюдаемый интерфейс в Java может помочь нам реализовать события (через Observer Pattern).

Но подождите, цель этого поста — быстрый урок о Spring Event. Spring имеет несколько хороших возможностей для создания событийно-управляемых приложений. Вы можете вызвать конкретное событие в бине и прослушать его в другом бине.

Представьте себе простое приложение с этими требованиями:

  • Есть несколько заказов, которые могут иметь другой статус
  • когда заказ находится в состоянии ДОСТАВЛЕНО или ОТЛОЖЕН, нам нужно отправить электронное письмо клиенту

Первое (но не лучшее) решение для удовлетворения требований — отправка электронной почты в нашей модели заказа, но есть некоторые недостатки:

  • Заказ не отвечает за отправку электронной почты.
  • Если вы следуете принципу доменного управления, Order — это объект домена, но Email Sender, возможно, является службой (отличной от Domain Service), поэтому вы не можете использовать ее в своей модели.

Другое решение — вызвать некоторые события в нашей модели Order после изменения ее статуса. Меня не волнует порядок, что будет после поднятия этого события. В нашем примере нам нужно прослушать определенный вид событий, проанализировать их и заняться бизнесом (отправка электронной почты).

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
@Configurable
 public class Order implements ApplicationEventPublisherAware {
 private final String orderId;
 private final Date createDate;
 private final CustomerInfo customerInfo;
 private ApplicationEventPublisher eventPublisher;
 private Date lastUpdateDate;
 private Status status;
  
public Order(String orderId, CustomerInfo customerInfo) {
 this.orderId = orderId;
 this.customerInfo = customerInfo;
 status = Status.MODIFIABLE;
 this.createDate = new Date();
 this.lastUpdateDate = this.createDate;
 }
  
public String getOrderId() {
 return orderId;
 }
  
public void checkOut() {
 if (status == Status.DELIVERED) {
 throw new IllegalStateException(String.format("Order is already delivered"));
 }
 this.status = Status.CHECKED_OUT;
 this.lastUpdateDate = new Date();
 }
  
public void deliver() {
 if (this.status != Status.CHECKED_OUT && this.status != Status.POSTPONED) {
 throw new IllegalStateException(String.format("Order status should be CHECKED OUT for delivery to be called. but is : %s", status));
 }
  
this.status = Status.DELIVERED;
 this.lastUpdateDate = new Date();
 this.eventPublisher.publishEvent(new OnOrderDelivered(this));
 }
  
public void postponeDelivery() {
 if (status != Status.CHECKED_OUT && status != Status.POSTPONED) {
 throw new IllegalStateException(String.format("Can not postpone delivery in this state: %s", status));
 }
 this.status = Status.POSTPONED;
 this.lastUpdateDate = new Date();
 this.eventPublisher.publishEvent(new OnOrderPostponed(this));
 }
  
public Status getStatus() {
 return status;
 }
  
public CustomerInfo getCustomerInfo() {
 return customerInfo;
 }
  
public Date getLastUpdateDate() {
 return lastUpdateDate;
 }
  
public Date getCreateDate() {
 return createDate;
 }
  
@Override
 public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
 this.eventPublisher = applicationEventPublisher;
 }
  
public static enum Status {
 MODIFIABLE,
 CHECKED_OUT,
 POSTPONED,
 DELIVERED,
 CANCELED;
 }
 }

Как вы видите, Order — это настраиваемый класс, если вы не работали с этой концепцией раньше, не огорчайтесь. Для этого поста вам просто нужно знать, что настраиваемые классы можно создавать везде с новым ключевым словом, но они управляются весной, поэтому вы можете добавлять в них другие bean-компоненты или использовать с ними большинство средств Spring. Обещаю опубликовать статью об этом как можно скорее.

Класс заказа реализует интерфейс org.springframework.context.ApplicationEventPublisherAware . Этот интерфейс имеет метод setter с именем setApplicationEventPublisher который представляет ApplicationEventPublisher для использования в вашем классе. Как вы видите в методе поставки, мы использовали этот объект для публикации события ( this.eventPublisher.publishEvent(new OnOrderDelivered(this)) ). Вы можете опубликовать каждое событие, которое расширяет org.springframework.context.ApplicationEvent . Мы вызвали другое событие OnOrderPostponed когда заказ становится отложенным.

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
public abstract class OnOrderStatusChanged extends ApplicationEvent {
 private final Order order;
  
public OnOrderStatusChanged(Order source) {
 super(source);
 this.order = source;
 System.out.println(String.format("Order:%s status is changed to %s", source.getOrderId(), source.getStatus()));
 }
  
public Order getOrder() {
 return order;
 }
 }
  
public class OnOrderDelivered extends OnOrderStatusChanged {
 public OnOrderDelivered(Order order) {
 super(order);
 }
 }
  
public class OnOrderPostponed extends OnOrderStatusChanged {
 public OnOrderPostponed(Order order) {
 super(order);
 }
 }

OnOrderStatusChanged — это абстрактный класс, который расширяет его OnOrderDelivered и OnOrderPostponed. до сих пор мы могли создавать наши события и поднимать их. Теперь, если вы создадите весенний тест и вызовете метод доставки заказа, вы увидите «Заказ: статус Х изменен на ПОСТАВЛЕН». Последний шаг — что-то делать, когда эти события публикуются. мы хотим отправить электронное письмо клиенту, когда эти методы подняты. Кроме того, для клиента важно разместить товар, когда его заказ находится в состоянии доставки. Слушатели — это простые Бины, которые реализуют универсальный интерфейс ApplicationListener. Тип параметра в этом интерфейсе — это тип события, которое вы хотите прослушать. можно определить тип параметра как родительский и прослушать все его дочерние элементы. например, в нашей модели, если мы используем OnOrderStatusChanged наш слушатель будет перехватывать все события из OnOrderDelivered и OnOrderPostponed

Это может быть подходящим для отправки электронной почты в нашем сценарии. но мы не используем эту модель и создаем для них два разных слушателя.

Как вы видите ниже, их код очень прост

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service
 public class OrderDeliveredEmailSender implements ApplicationListener,Ordered {
  
@Override
 public void onApplicationEvent(OnOrderDelivered event) {
 System.out.println(String.format("Message sent for delivered order to:%s ORDER-ID:%s",event.getOrder().getCustomerInfo().getEmail(),event.getOrder().getOrderId()));
 }
  
@Override
 public int getOrder() {
 return 100;
 }
 }
  
@Service
 public class OrderPostponedEmailSender implements ApplicationListener {
  
@Override
 public void onApplicationEvent(OnOrderPostponed event) {
 System.out.println(String.format("Message sent for postponed order to:%s ORDER-ID:%s", event.getOrder().getCustomerInfo().getEmail(), event.getOrder().getOrderId()));
 }
 }

Эти два bean-компонента будут запускать onApplicationEvent при возникновении соответствующего события. Для публикации продукта клиенту нам нужно создать еще одно событие Listener для OnOrderDelivered.

01
02
03
04
05
06
07
08
09
10
11
12
@Service
 public class OnOrderDeliveredPost implements ApplicationListener,Ordered {
 @Override
 public void onApplicationEvent(OnOrderDelivered onOrderDelivered) {
 System.out.println(String.format("Order:%s is posting for customer.",onOrderDelivered.getOrder().getOrderId()));
 }
  
@Override
 public int getOrder() {
 return 1000;
 }
 }

Как вы видите, этот слушатель отправит продукт клиенту, когда его состояние будет доставлено. Но ждать там, что такое заказанный интерфейс? Если вы не использовали интерфейс org.springframework.core.Ordered , важно знать, что с помощью этого интерфейса вы можете определить порядок между компонентами в коллекции. в нашем сценарии клиент хотел бы получить электронное письмо до того, как мы отправим ему продукт. для этого эти два класса реализуют интерфейс Ordered, не забывайте, что самый низкий порядок имеет самый низкий приоритет.

* Принцип единой ответственности

Ссылка: Как использовать Events в Spring 3.x от нашего партнера по JCG Сороша Сарабадани в блоге Just Another Java .