이번에는 이벤트 리스너를 등록해보자. 이벤트 리스너란, 이벤트가 발생했을 때 원하는 후처리 작업을 할 수 있는 방법이다.
JavaScript의 이벤트 리스너랑 완전 똑같은 것이라고 보면 된다.
참고로, 이 포스팅은 공식 문서에서 제공하는 방식과 살짝 다르다. 스프링에서 InitializingBean, DisposableBean 인터페이스를 구현하여 빈으로 등록해서, 스프링 컨텍스트(컨테이너)가 최초로 띄워질때와 마지막에 종료될 때 호출될 메서드와 사용할 이벤트 리스너를 등록해 보았다. 왜 그러냐면, 이 플러그인 관련 포스팅을 Part.1에서 쭉 보다보면 스프링의 기술이 들어가 있는것을 알 수가 있는데 스프링의 기술을 사용중이니까 스프링과 잘 호환되는 기술을 사용해보고자 이런 방식을 구현했다
그리고 스프링 기술을 이용했기 때문에 Add-on Descriptor(atlassian-plugin.xml)에 어떠한 작업도 필요 없고 그래서 더 간결하다는 것을 캐치해서 유심히 봐보자!
IssueCreatedResolvedListener
package kr.osci.kapproval.com.jira.eventlistener;
import com.atlassian.event.api.EventListener;
import com.atlassian.event.api.EventPublisher;
import com.atlassian.jira.event.issue.IssueEvent;
import com.atlassian.jira.event.type.EventType;
import com.atlassian.jira.issue.Issue;
import com.atlassian.plugin.spring.scanner.annotation.imports.JiraImport;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class IssueCreatedResolvedListener implements InitializingBean, DisposableBean {
@JiraImport
private final EventPublisher eventPublisher;
/**
* Called when the plugin has been enabled.
*/
@Override
public void afterPropertiesSet() {
log.info("Enabling plugin");
eventPublisher.register(this);
}
/**
* Called when the plugin is being disabled or removed.
*/
@Override
public void destroy() {
log.info("Disabling plugin");
eventPublisher.unregister(this);
}
@EventListener
public void onIssueEvent(IssueEvent issueEvent) {
Long eventTypeId = issueEvent.getEventTypeId();
Issue issue = issueEvent.getIssue();
if (eventTypeId.equals(EventType.ISSUE_CREATED_ID)) {
log.info("Issue {} has been created at {}.", issue.getKey(), issue.getCreated());
// 이슈 Created 이벤트가 발생했을 때 실행되는 부분
} else if (eventTypeId.equals(EventType.ISSUE_RESOLVED_ID)) {
log.info("Issue {} has been resolved at {}.", issue.getKey(), issue.getResolutionDate());
// 이슈 Resolved 이벤트가 발생했을 때 실행되는 부분
} else if (eventTypeId.equals(EventType.ISSUE_CLOSED_ID)) {
log.info("Issue {} has been closed at {}.", issue.getKey(), issue.getUpdated());
// 이슈 Closed 이벤트가 발생했을 때 실행되는 부분
}
}
}
우선, InitializingBean을 구현하려면 재정의 할 메서드가 있다.
afterPropertiesSet()
이 메서드는 스프링 컨텍스트가 완전히 띄워졌을 때, 호출되는 메서드이다. 그러니까 스프링이 진짜 이제 실행될 준비가 됐을 때 자동으로 호출되는 메서드이다. 여기서 무엇을 해야 하냐면 내가 이벤트 퍼블리셔를 등록하겠다고 선언해줘야 한다. 그래야 어떤 이벤트가 발생했을 때 이벤트를 캐치할 수 있게 된다.
그래서 이 메서드안에 다음 코드 한 줄이 있다.
eventPublisher.register(this);
그 다음, DisposableBean을 구현하려면 또 한가지 재정의 할 메서드가 있다.
destroy()
이 메서드는 스프링 컨텍스트가 내려가기 바로 전에 호출되는 메서드이다. 그러니까, 스프링이 내려가기 전 마지막으로 정리할 자원들을 정리하는 메서드라고 생각하면 된다. 그래서 등록한 이벤트 퍼블리셔를 다시 등록 해제하면 된다.
eventPublisher.unregister(this);
그리고, 실제 이벤트가 발생했을 때마다 호출될 메서드가 있다. 바로 다음 메서드.
@EventListener
public void onIssueEvent(IssueEvent issueEvent) {
Long eventTypeId = issueEvent.getEventTypeId();
Issue issue = issueEvent.getIssue();
if (eventTypeId.equals(EventType.ISSUE_CREATED_ID)) {
log.info("Issue {} has been created at {}.", issue.getKey(), issue.getCreated());
// 이슈 Created 이벤트가 발생했을 때 실행되는 부분
} else if (eventTypeId.equals(EventType.ISSUE_RESOLVED_ID)) {
log.info("Issue {} has been resolved at {}.", issue.getKey(), issue.getResolutionDate());
// 이슈 Resolved 이벤트가 발생했을 때 실행되는 부분
} else if (eventTypeId.equals(EventType.ISSUE_CLOSED_ID)) {
log.info("Issue {} has been closed at {}.", issue.getKey(), issue.getUpdated());
// 이슈 Closed 이벤트가 발생했을 때 실행되는 부분
}
}
주의 깊게 볼 건 @EventListener 애노테이션이다. 이 애노테이션은 어떠한 public 메서드라도 상관없이 달 수 있는데 이 애노테이션이 달린 메서드의 파라미터 이벤트가 발생할 때마다 이 메서드가 호출된다. 여기서는, IssueEvent라는 이슈 관련 이벤트를 파라미터로 받는다. 생성, 수정, 삭제 등등의 이벤트가 다 잡히게 될 것이다.
그래서 실제로 원하는 이벤트의 후처리 코드는 이 @EventListener 애노테이션이 달린 메서드에서 작업하면 된다.
이렇게 스프링과 JIRA가 제공하는 @EventListener 애노테이션을 사용해서 스프링의 라이프 사이클을 이용해 스프링 컨테이너가 완전히 올라왔을 때(플러그인이 띄워질 때)와 스프링 컨테이너가 완전히 내려가기 바로 직전에(플러그인이 내려가기 직전에) 딱 한 번씩만 이벤트 퍼블리셔를 등록할 수 있고, 이벤트 리스너 메서드를 만들 수 있다.
공식 문서도 한번 참고해보면 좋을 것 같다.
보너스. 또다른 이벤트 리스너 예시 코드 (RemoteIssueLinkEvent)
RemoteIssueLinkListener
package kr.osci.aijql.eventlistener;
import com.atlassian.event.api.EventListener;
import com.atlassian.event.api.EventPublisher;
import com.atlassian.jira.event.issue.link.RemoteIssueLinkCreateEvent;
import com.atlassian.jira.event.issue.link.RemoteIssueLinkUICreateEvent;
import com.atlassian.jira.issue.link.RemoteIssueLinkManager;
import com.atlassian.plugin.spring.scanner.annotation.imports.JiraImport;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class RemoteIssueLinkListener implements InitializingBean, DisposableBean {
@JiraImport
private final EventPublisher eventPublisher;
@JiraImport
private final RemoteIssueLinkManager remoteIssueLinkManager;
/**
* Called when the plugin has been enabled.
*/
@Override
public void afterPropertiesSet() {
log.debug("[afterPropertiesSet]: RemoteIssueLinkListener initialized.");
eventPublisher.register(this);
}
/**
* REST API 또는 애플리케이션에서 직접 Remote Issue Link 추가하는 경우 호출
* @param remoteIssueLinkCreateEvent remoteIssueLinkCreateEvent
*/
@EventListener
public void onCreateRemoteIssueLinkEvent(RemoteIssueLinkCreateEvent remoteIssueLinkCreateEvent) {
log.info("[onCreateRemoteIssueLinkEvent] called");
log.info("[onCreateRemoteIssueLinkEvent] remote issue link id = {}", remoteIssueLinkCreateEvent.getRemoteIssueLinkId());
log.info("[onCreateRemoteIssueLinkEvent] global id = {}", remoteIssueLinkCreateEvent.getGlobalId());
}
/**
* 오직 애플리케이션에서 사용자가 Remote Issue Link 추가하는 경우 호출
* @param remoteIssueLinkUiCreateEvent remoteIssueLinkUiCreateEvent
*/
@EventListener
public void onCreateUiRemoteIssueLinkEvent(RemoteIssueLinkUICreateEvent remoteIssueLinkUiCreateEvent) {
log.info("[onCreateUiRemoteIssueLinkEvent] called");
log.info("[onCreateUiRemoteIssueLinkEvent] remote issue link id = {}", remoteIssueLinkUiCreateEvent.getRemoteIssueLinkId());
log.info("[onCreateUiRemoteIssueLinkEvent] global id = {}", remoteIssueLinkUiCreateEvent.getGlobalId());
}
/**
* Called when the plugin is being disabled or removed.
*/
@Override
public void destroy() {
log.info("[destroy]: RemoteIssueLinkListener destroyed.");
eventPublisher.unregister(this);
}
}
'Jira & ScriptRunner' 카테고리의 다른 글
Jira DC 플러그인 개발 Part.11 - 예외 공통처리 하기 (0) | 2024.08.13 |
---|---|
Jira DC 플러그인 개발 Part.10 - 로그 설정 (0) | 2024.08.13 |
Jira DC 플러그인 개발 Part.7 서블릿 필터 만들기 (Java에서 하던것과 똑같다) (0) | 2024.07.23 |
Jira DC 플러그인 개발 Part.6 - JQL Function 만들기 (0) | 2024.07.16 |
Jira DC 플러그인 개발 Part.5 - 상단 네비게이션 바에 앱 링크 노출하기 (0) | 2024.07.16 |