예전에 조금 포스팅하다가 임시글로 저장만 하고 까먹은 글이 있었다. 그게 지금 말할려는 ApplicationEvent이다.
Spring에는 ApplicationContext를 활용해서 Event를 발생 시킬수 있다. ApplicationEvent를 구현하기 위해서는 세가지만 알면 된다.
1. 이벤트 발신자 : 말그대로 이벤트를 발생시킨다. ApplicationEventPublisher 인터페이스 혹은 ApplicatonContext의 publishEvent를 활용해서 이벤트를 발생시킨다. (ApplicationContext는 ApplicationEventPublisher 상속받고 있다.)
2. 이벤트 수신자 : 이거 또한 이벤트를 수신한다. ApplicationListener 인터페이스를 통해 이벤트를 수신할 수 있다.
3. 이벤트 : 전달하려고자 하는 이벤트이다. ApplicationEvent를 상속받아 구현하면 된다.

이렇게 세가지만 알면 끝난다. Spring reference에 있는 예제에 있는 black 리스트에게 메일을 전달하지 않고 관리자에게 이메일로 알려주는 그런 예제이다.

public class EmailService implements ApplicationEventPublisherAware {

  private List<String> blackList;
  private ApplicationEventPublisher publisher;

  public void setBlackList(List<String> blackList) {
    this.blackList = blackList;
  }

  public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
    this.publisher = publisher;
  }

  public void sendEmail(String address, String text) {
    if (blackList.contains(address)) {
      BlackListEvent event = new BlackListEvent(this, address, text);
      publisher.publishEvent(event);
      return;
    }
    // 이메일을 보내자
  }
}

위의 코드는 이벤트를 발신하는 코드이다. blackList에는 현재 black 당한 이메일이 저장되있다. 그리고 만약 email을 보낼시에 black email에 존재하면 email을 보내지않고 이벤트를 발신한다. 이벤트 발신은 ApplicationEventPublisher.publishEvent(event)를 통해 발생시킨다.
다음은 이벤트 수신자다.

@Slf4j
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

  private String notificationAddress;

  public void setNotificationAddress(String notificationAddress) {
    this.notificationAddress = notificationAddress;
  }

  @Override
  public void onApplicationEvent(BlackListEvent event) {
    log.info("이벤트 발생 시간 : {}", event.getTimestamp());
    log.info("관리자에게 알리자 : {} " ,notificationAddress);
  }
}

ApplicationListener를 구현하고 있다. BlackListEvent이벤트가 발생하였을 경우에만 이벤트가 수신된다. 여기에서는 black된 이메일을 보낼시에 관리자에게 이메일로 전달해주는 그런 코드가 들어갈 것이다.
다음코드는 이벤트이다.

public class BlackListEvent extends ApplicationEvent {

  private final String address;
  private final String text;

  public BlackListEvent(Object source, String address, String text) {
    super(source);
    this.address = address;
    this.text = text;
  }
  //other method
}

ApplicationEvent를 상속받아 구현해주면 된다. 중요한건 super(source) 를 해줘야한다. 어차피 ApplicationEvent 생성자가 한개 밖에 없어서 안넣을시 컴파일 에러가 난다.
Test를 해보자. 일단 저기 발신자와 수신자는 빈으로 등록을 해야된다.

@Bean
public EmailService emailService(){
  EmailService emailService = new EmailService();
  emailService.setBlackList(Arrays.asList("test@test.com", "wonwoo@test.com", "5151@test.com"));
  return emailService;
}

@Bean
public BlackListNotifier blackListNotifier(){
  BlackListNotifier blackListNotifier = new BlackListNotifier();
  blackListNotifier.setNotificationAddress("admin@test.com");
  return blackListNotifier;
}

blackList에는 세개의 이메일이 존재한다. 그리고 수신자의 속성에는 관리자의 이메일을 작성한다.

ApplicationContext run = SpringApplication.run(SpringStudyApplication.class, args);
EmailService bean = run.getBean(EmailService.class);
bean.sendEmail("wonwoo@test.com", "hi wonwoo");

위와 같이 테스트를 해보면 아래와 같이 로그가 남겨진다.

2016-07-25 21:30:12.394  INFO 925 --- [           main] me.wonwoo.event.BlackListNotifier        :  이벤트 발생 시간 : 1469449810609
2016-07-25 21:30:12.395  INFO 925 --- [           main] me.wonwoo.event.BlackListNotifier        : 관리자에게 알리자 : admin@test.com 

어떠한 특정한 이벤트를 발생시키려면 위와 같이 ApplicationEvent를 사용하면 된다. 여기서 한가지 더 알아야 될 것이 있는데 ApplicationEvent는 모든 이벤트리스너 에게 전달한다. 예를들어보자. 위의 코드는 관리자에게 메일을 보내는 것이라면 다른 이벤트리스너는 로그서버에 전송을 한다거나 단순 로그를 찍는다고 가정하자.
그럼 이벤트 리스너를 한개더 구현하면 된다.

@Slf4j
public class BlackLogNotifier implements ApplicationListener<BlackListEvent> {

  @Override
  public void onApplicationEvent(BlackListEvent event) {
    log.info("black log");
  }
}

만약 ApplicationListener의 제네릭 타입에 ApplicationEvent를 넣을 경우에는 모든 이벤트 발생시 리스너에게 날라온다. 모든 이벤트시에 받을 경우라면 상관없는데 그럴경우는 드물다. 만약 ApplicationListener의 ApplicationEvent 제네릭타입에 사용할 경우에는 구현체에 형타입을 체크해야할 것이다.

@Bean
public BlackLogNotifier blackLogNotifier(){
  return new BlackLogNotifier();
}

위의 BlackLogNotifier를 빈으로 등록하고 다시한번 테스트를 해보자. 그럼 아래와 같이 로그가 출력될 것이다.

2016-07-25 21:52:47.648  INFO 992 --- [           main] me.wonwoo.event.BlackLogNotifier         : black log
2016-07-25 21:52:48.099  INFO 992 --- [           main] me.wonwoo.event.BlackListNotifier        : 이벤트 발생 시간 : 1469451166692
2016-07-25 21:52:48.100  INFO 992 --- [           main] me.wonwoo.event.BlackListNotifier        : 관리자에게 알리자 : admin@test.com 

우리가 필요한 두개의 리스너에게 제대로 이벤트를 전달하였다.

Spring 4.2 부터는 어노테이션으로 간단하게 할 수 있다. 스프링은 너무 어노테이션을 좋아한다. 이러다 어노테이션만으로 개발이 가능 하겠는데..

@Slf4j
public class AnnotationListener {

  private String notificationAddress;

  public void setNotificationAddress(String notificationAddress) {
    this.notificationAddress = notificationAddress;
  }

  @EventListener
  public void processBlackListEvent(BlackListEvent event) {
    log.info("이벤트 발생 시간 : {}", event.getTimestamp());
    log.info("관리자에게 알리자 : {} " ,notificationAddress);
  }
}

위와 같이 @EventListener 만 선언해 주면 끝이다. 그럼 굳이 인터페이스를 구현하지 않아도 된다.
또한 EventListener에 속성중 condition을 통해 SpEL 태그로 필터링을 할 수 있다.

@EventListener(condition = "#event.text == 'foo'")
public void processBlackListEvent(BlackListEvent event) {
  // something
}

event안에 text가 foo인 것만 리스너를 호출한다.
또한 비동기 이벤트 리스너도 가능하다.

@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
  // something
}

이벤트 리스너의 순서도 정할 수 있으며

@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
    // something
}

이벤트의 제네릭 타입도 지원한다.

@EventListener
public void processBlackListEvent(BlackListEvent<Account> event) {
    // something
}

이것으로 Spring의 ApplicationEvent에 대해 알아봤다. 그런데 이걸 어디다 써먹을까? 딱히 떠오르지 않아.. 알려주세요