728x90
반응형
SMALL

이번에는 이벤트 리스너를 등록해보자. 이벤트 리스너란, 이벤트가 발생했을 때 원하는 후처리 작업을 할 수 있는 방법이다.

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 애노테이션을 사용해서 스프링의 라이프 사이클을 이용해 스프링 컨테이너가 완전히 올라왔을 때(플러그인이 띄워질 때)와 스프링 컨테이너가 완전히 내려가기 바로 직전에(플러그인이 내려가기 직전에) 딱 한 번씩만 이벤트 퍼블리셔를 등록할 수 있고, 이벤트 리스너 메서드를 만들 수 있다.

 

공식 문서도 한번 참고해보면 좋을 것 같다.

 

Writing Jira event listeners with the atlassian-event library

Writing Jira event listeners with the atlassian-event library Applicable:Jira 7.1.0 and later.Level of experience:Intermediate. You should have completed at least one beginner tutorial before working through this tutorial. See the list of developer tutoria

developer.atlassian.com

 

보너스. 또다른 이벤트 리스너 예시 코드 (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);
    }
}
728x90
반응형
LIST

+ Recent posts