Gymterview
middle

Как работать с Event Listener в плагине?

Event Listener позволяет реагировать на события в Jira — создание, обновление, удаление задач, комментариев, проектов и других сущностей — через подписку с аннотацией @EventListener.

Реализация через EventPublisher

Код IssueEventListener
@Named
public class IssueEventListener implements InitializingBean, DisposableBean {

    private static final Logger log = LoggerFactory.getLogger(IssueEventListener.class);

    private final EventPublisher eventPublisher;
    private final MailService mailService;

    @Inject
    public IssueEventListener(@ComponentImport EventPublisher eventPublisher,
                              MailService mailService) {
        this.eventPublisher = eventPublisher;
        this.mailService = mailService;
    }

    @Override
    public void afterPropertiesSet() {
        eventPublisher.register(this);
    }

    @Override
    public void destroy() {
        eventPublisher.unregister(this);
    }

    @EventListener
    public void onIssueCreated(IssueEvent event) {
        if (event.getEventTypeId().equals(EventType.ISSUE_CREATED_ID)) {
            Issue issue = event.getIssue();
            log.info("Создана задача: {} в проекте {}",
                    issue.getKey(), issue.getProjectObject().getKey());

            if ("Bug".equals(issue.getIssueType().getName())
                    && "Critical".equals(issue.getPriority().getName())) {
                mailService.notifyTeamLead(issue);
            }
        }
    }

    @EventListener
    public void onIssueUpdated(IssueEvent event) {
        if (event.getEventTypeId().equals(EventType.ISSUE_UPDATED_ID)) {
            Issue issue = event.getIssue();
            ChangeLog changeLog = event.getChangeLog();

            if (changeLog != null) {
                for (ChangeItemBean change : changeLog.getChangeItemBeans()) {
                    if ("status".equals(change.getField())) {
                        log.info("Задача {} сменила статус: {} → {}",
                                issue.getKey(), change.getFromString(),
                                change.getToString());
                    }
                }
            }
        }
    }
}

Кастомные события

Пример
// Определение
public class TaskProcessedEvent {
    private final String issueKey;
    private final String result;
    private final Instant processedAt;

    public TaskProcessedEvent(String issueKey, String result) {
        this.issueKey = issueKey;
        this.result = result;
        this.processedAt = Instant.now();
    }
    // getters
}

// Публикация
@Named
public class TaskProcessor {

    private final EventPublisher eventPublisher;

    @Inject
    public TaskProcessor(@ComponentImport EventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    public void process(String issueKey) {
        // ... обработка ...
        eventPublisher.publish(new TaskProcessedEvent(issueKey, "SUCCESS"));
    }
}

// Подписка
@EventListener
public void onTaskProcessed(TaskProcessedEvent event) {
    log.info("Задача {} обработана: {}", event.getIssueKey(), event.getResult());
}

Частые ошибки

  • Не дерегистрировать слушатель в destroy() — при перезагрузке плагина старый слушатель остаётся активным и вызывается дважды
  • Синхронная тяжёлая обработка — замедляет UI Jira для пользователя
  • Изменение issue внутри слушателя без IssueManager.updateIssue() — изменения теряются или вызывают рекурсивные события
  • Не обрабатывать исключения — необработанное исключение в listener может сломать операцию пользователя

Как используется в 2026

  • Event Listener — стандартный механизм реакции на события в DC-плагинах
  • В Cloud аналог — Forge Triggers (avi:jira:created:issue)
  • Для сложных event-driven сценариев применяют паттерн: Jira Event -> Plugin Queue -> Async Processor

На собеседовании: обязательно упомяните register/unregister в afterPropertiesSet/destroy — иначе утечка памяти. Listener вызывается синхронно в том же потоке — тяжёлую логику выносите в отдельный поток. Сравните с Event Listener и не забудьте: IssueEvent.getEventTypeId() — числовой ID, сравнивайте с константами EventType.