Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

События CDI

Когда в блоге появляется запись, нужно сделать несколько дел: уведомить подписчиков, обновить поисковый индекс, отправить письма. Если вызывать всё это из сервиса публикации, он будет знать про каждую службу и разрастётся. События CDI решают задачу иначе: сервис только объявляет «запись создана», а все, кому это интересно, подписываются сами.

Событие

Событие — обычный объект с данными. Удобно описать его записью:

// src/main/java/com/example/blog/PostCreated.java
package com.example.blog;
 
public record PostCreated(long id, String title) {
}

Отправка

Сервис получает объект Event<PostCreated> и вызывает fire. Обновите src/main/java/com/example/blog/PostService.java:

// src/main/java/com/example/blog/PostService.java
package com.example.blog;
 
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Event;
import jakarta.inject.Inject;
 
@ApplicationScoped
public class PostService {
 
    @Inject
    PostStore store;
 
    @Inject
    Event<PostCreated> postCreated;
 
    public Post publish(NewPost data) {
        Post post = store.add(data);
        postCreated.fire(new PostCreated(post.id(), post.title()));
        return post;
    }
}

Здесь зависимости внедрены в поля — так короче, и конструктор без параметров не нужен.

Наблюдатели

Наблюдатель — метод бина, у которого параметр помечен @Observes:

// src/main/java/com/example/blog/NotificationService.java
package com.example.blog;
 
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import java.util.logging.Logger;
 
@ApplicationScoped
public class NotificationService {
 
    private static final Logger LOG = Logger.getLogger(NotificationService.class.getName());
 
    void onPostCreated(@Observes PostCreated event) {
        LOG.info("Уведомляем подписчиков о записи: " + event.title());
    }
}

Второй наблюдатель того же события ничего не знает о первом:

// src/main/java/com/example/blog/SearchIndex.java
package com.example.blog;
 
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import java.util.logging.Logger;
 
@ApplicationScoped
public class SearchIndex {
 
    private static final Logger LOG = Logger.getLogger(SearchIndex.class.getName());
 
    void onPostCreated(@Observes PostCreated event) {
        LOG.info("Добавляем запись " + event.id() + " в поисковый индекс");
    }
}

Создадим запись:

curl -H "Content-Type: application/json" -d '{"title":"События CDI","body":"Отправить и поймать."}' http://localhost:8080/blog/api/posts

В target/liberty/wlp/usr/servers/defaultServer/logs/messages.log появятся строки (дата сокращена):

com.example.blog.NotificationService  I Уведомляем подписчиков о записи: События CDI
com.example.blog.SearchIndex          I Добавляем запись 3 в поисковый индекс

fire вызывает наблюдателей сразу, в том же потоке: ответ клиенту уйдёт, когда все они закончат.

Асинхронные события

Долгую работу, например рассылку писем, лучше не делать во время запроса. Метод fireAsync отправляет событие в другой поток, а ловят его методы с @ObservesAsync:

// src/main/java/com/example/blog/EmailSender.java
package com.example.blog;
 
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.ObservesAsync;
import java.util.logging.Logger;
 
@ApplicationScoped
public class EmailSender {
 
    private static final Logger LOG = Logger.getLogger(EmailSender.class.getName());
 
    void onPostCreated(@ObservesAsync PostCreated event) {
        LOG.info("Рассылаем письма о записи " + event.id() + " в потоке "
                + Thread.currentThread().getName());
    }
}

В PostService после fire добавьте postCreated.fireAsync(event), сохранив событие в переменную event. В логе появится строка из другого потока:

com.example.blog.EmailSender  I Рассылаем письма о записи 3 в потоке Default Executor-thread-101
ОтправкаНаблюдательПоток
fire(event)@ObservesТот же, ответ ждёт
fireAsync(event)@ObservesAsyncДругой, ответ не ждёт

Частая ошибка

Отправить событие через fireAsync, а наблюдателя пометить @Observes, или наоборот. Ошибки не будет — метод просто ни разу не вызовется. Синхронные и асинхронные наблюдатели не смешиваются: если событие должно дойти до обоих, вызовите и fire, и fireAsync, как в примере выше.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.