Spring Framework Event

doxxx·2024년 2월 4일
1

Spring Framework

목록 보기
3/3

최근 스프링 이벤트에 대한 관심이 생겨서 스프링 이벤트에 대해 정리해보려고 합니다.

Spring-Context의 클래스들과 내부의 event 패키지의 클래스들을 뜯어보고 데모 프로젝트를 통해 알아보았습니다.

1. 이벤트 정의 및 발행 (Event Definition and Publishing)

어플리케이션에서 발생하는 특정 사건을 나타내는 이벤트 클래스를 정의합니다. 이 클래스는 ApplicationEvent를 상속받습니다.
발생한 이벤트는 ApplicationEventPublisher를 사용하여 ApplicationContext에 등록된 리스너에게 발행됩니다.

/**
 * 사용자 등록 이벤트 클래스
 */
public class UserRegisteredEvent extends ApplicationEvent {
    private final String username;

    public UserRegisteredEvent(Object source, String username) {
        super(source);
        this.username = username;
    }

    public String getUsername() {
        return username;
    }
}
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;

public class UserService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher eventPublisher;

    // 다른 메서드들...
    // @Async, @Transactional, @Cacheable 등의 어노테이션을 사용할 수 있습니다.
    public void registerUser(String username) {
        // 사용자 등록 로직...

        // 사용자 등록 이벤트 발생
        UserRegisteredEvent userRegisteredEvent = new UserRegisteredEvent(this, username);
        eventPublisher.publishEvent(userRegisteredEvent);
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }
}

2. 이벤트 리스너 등록 (Listener Registration)

어플리케이션에서는 특정 이벤트에 대한 처리를 수행하는 리스너를 구현합니다.
이 리스너는 ApplicationListener를 구현하거나 @EventListener 어노테이션을 사용하여 메서드를 정의할 수 있습니다.

방법 1. ApplicationListener 인터페이스 구현:

메서드 시그니처에서 이벤트 타입을 지정해야 하지만, 외부에서 해당 리스너를 직접 호출할 수 있습니다.

import org.springframework.context.ApplicationListener;

public class UserRegisteredEventListener implements ApplicationListener<UserRegisteredEvent> {

    @Override
    public void onApplicationEvent(UserRegisteredEvent event) {
        // 사용자 등록 이벤트 처리 로직
        String username = event.getUsername();
        System.out.println("User registered: " + username);
    }
}

위의 예제에서 UserRegisteredEventListener 클래스는 ApplicationListener 인터페이스를 구현하고, 제네릭으로 사용자 등록 이벤트 (UserRegisteredEvent)를
지정합니다.
그리고 onApplicationEvent 메서드에서 해당 이벤트가 발생했을 때 수행할 로직을 구현합니다.

방법 2. @EventListener 어노테이션 사용:

@EventListener 어노테이션은 빈으로 등록된 클래스 내부의 메서드에만 적용 가능하며, 메서드의 시그니처에 이벤트 타입을 명시적으로 지정할 필요가 없습니다.

이는 편리하지만, 클래스 외부에서 해당 메서드를 호출할 수 없다는 제약이 있습니다.

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class AnotherUserRegisteredEventListener {

    @EventListener
    public void handleUserRegisteredEvent(UserRegisteredEvent event) {
        // 다른 사용자 등록 이벤트 처리 로직
        String username = event.getUsername();
        System.out.println("Another user registered: " + username);
    }
}

위의 예제에서 AnotherUserRegisteredEventListener 클래스는 @Component 어노테이션을 통해 빈으로 등록되며,

@EventListener 어노테이션을 사용하여 특정 이벤트에 대한 핸들러 메서드를 정의합니다. 메서드 이름은 자유롭게 정할 수 있습니다.

리스너 등록시 우선순위 지정

위와 같이 동일한 이벤트에 대해 여러 리스너가 등록되어 있을 때, @Order 어노테이션을 사용하여 리스너의 우선순위를 지정할 수 있습니다.

@Order(1)@EventListener 어노테이션 위에 추가하여 리스너의 우선순위를 지정할 수 있습니다.

다양한 리스너 메서드 시그니처 활용

@EventListener({UserRegisteredEvent.class, OtherEvent.class}) 와 같이 여러 이벤트 타입을 하나의 이벤트 리스너 메서드에 등록할 수 있습니다.

3. ApplicationContext 초기화 및 이벤트 핸들링 (ApplicationContext Initialization and Event Handling)

어플리케이션 컨텍스트가 초기화되면, 등록된 리스너들이 자동으로 등록된 이벤트에 대해 수신 대기 상태가 됩니다.
ApplicationContext가 특정 상태에 도달하거나 특정 이벤트가 발생하면 등록된 리스너들이 해당 이벤트를 처리합니다.

ApplicationEventMulticaster의 구현체를 명시적으로 지정하지 않으면, Spring은 기본적으로 단일 스레드에서 동작하는 SimpleApplicationEventMulticaster
사용합니다.

ApplicationContext 라이프사이클 이벤트

  1. ContextRefreshedEvent:ApplicationContext가 초기화되고, 모든 빈들이 생성된 후에 발생하는 이벤트입니다. 주로 초기화 작업이나 리소스 로딩과 관련된 작업을 수행하는 리스너들이
    등록됩니다.
  2. ContextStartedEvent:ApplicationContext가 시작될 때 발생하는 이벤트입니다. 주로 백그라운드 작업이나 스케줄러를 시작하는 데 활용됩니다.
  3. ContextStoppedEvent:ApplicationContext가 정지될 때 발생하는 이벤트입니다. 주로 백그라운드 작업이나 스케줄러를 중지하는 데 활용됩니다.
  4. ContextClosedEvent: ApplicationContext가 종료되고 빈들이 소멸되기 직전에 발생하는 이벤트입니다. 주로 리소스 정리나 마무리 작업을 수행하는 데 활용됩니다.

사용 예시

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

/**
 * ApplicationContext가 초기화 및 리프레시될 때 발생하는 이벤트를 처리하는 리스너입니다.
 * 주로 초기화 작업이나 리소스 로딩과 관련된 작업을 수행합니다.
 */
@Component
public class ContextRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // ContextRefreshedEvent에 대한 처리 로직
        System.out.println("ApplicationContext is refreshed.");
    }
}

@Listener 어노테이션을 사용하여서도 구현이 가능합니다.

@Component
public class ContextRefreshedEventListener {

    @EventListener
    public void handleContextRefreshedEvent(ContextRefreshedEvent event) {
        // ContextRefreshedEvent에 대한 처리 로직
        System.out.println("ApplicationContext is refreshed.");
    }
}

ContextRefreshedEventListenerContextRefreshedEvent를 처리하는 리스너로, @Component 어노테이션을 통해 빈으로 등록되었습니다. 이 리스너는
ApplicationContext가 초기화 및 리프레시되면 해당 이벤트를 수신하여 처리합니다.

@Service
public class EventExampleService {

    @Autowired
    private ApplicationContext applicationContext;

    public void performExample() {
        applicationContext.publishEvent(new ContextRefreshedEvent(applicationContext));

        // 다른 이벤트에 대한 발생 로직 추가 가능
    }
}

EventExampleService에서는 ApplicationContext를 주입받아 publishEvent 메서드를 사용하여 ContextRefreshedEvent를 발생시킵니다.

이와 유사하게 ContextStartedEvent, ContextStoppedEvent, ContextClosedEvent에 대한 리스너들도 구현할 수 있습니다.

4. 이벤트 다중 발행 및 수신 (Event Multicasting)

이벤트 발행은 ApplicationEventMulticaster 인터페이스의 구현체를 통해 이루어집니다.

위 인터페이스의 추상 구현체인 AbstractApplicationEventMulticaster를 사용하여 다양한 전파 방식을 제공합니다.

일반적으로 사용되는 구현체 중 하나는 SimpleApplicationEventMulticaster입니다.

SimpleApplicationEventMulticaster는 단일 스레드에서 동작하는 간단한 구현체로, 이벤트를 순차적으로 리스너에게 전달합니다.

import org.springframework.context.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.context.support.GenericApplicationContext;

public class EventPublisherExample {

    public static void main(String[] args) {
        // ApplicationContext 생성
        GenericApplicationContext context = new GenericApplicationContext();

        // SimpleApplicationEventMulticaster를 생성하고 ApplicationContext에 등록
        ApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
        context.addApplicationListener(new MyEventListener()); // 리스너 등록
        context.setApplicationEventMulticaster(eventMulticaster);

        // 이벤트 발행
        context.refresh(); // ContextRefreshedEvent 발생

        // ApplicationContext 종료
        context.close(); // ContextClosedEvent 발생
    }
}

SimpleApplicationEventMulticaster를 생성하고, 해당 이벤트 멀티캐스터를 GenericApplicationContext에 등록하였습니다.

그 후 context.refresh()를 호출하여 ContextRefreshedEvent를 발생시키면, 등록된 리스너인 MyEventListener에서 해당 이벤트를 처리합니다.

마찬가지로 context.close()를 호출하여 ContextClosedEvent를 발생시키면, 해당 이벤트를 처리하는 리스너도 호출됩니다.

5. 이벤트 처리 및 조건부 처리 (Event Processing and Conditional Handling)

@EventListener 어노테이션을 사용하여 이벤트 리스너를 정의할 때, condition 속성을 사용하여 특정 조건을 만족하는 경우에만 해당 이벤트를 처리할 수 있습니다.

import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class ConditionalEventListener {

    @EventListener(condition = "#root.event instanceof T(me.doxxx.springeventdemo.event.UserRegisteredEvent)")
    public void handleConditionalEvent(ApplicationEvent event) {
        String eventName = event.getClass().getSimpleName();
        String expression = "#this.username.length() > 5";

        if (EventExpressionEvaluator.evaluateExpression(expression, event)) {
            System.out.println("Conditional event handled: " + eventName);
        } else {
            System.out.println("Conditional event not handled: " + eventName);
        }
    }
}

public class EventExpressionEvaluator {
    private EventExpressionEvaluator() {
    }

    public static boolean evaluateExpression(String expression, Object event) {
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext(event);
        return Boolean.TRUE.equals(parser.parseExpression(expression).getValue(context, Boolean.class));
    }
}

위의 예제에서 ConditionalEventListener@EventListener 어노테이션을 사용하여 UserRegisteredEvent 이벤트에 대한 리스너를 정의합니다.

그리고 EventExpressionEvaluator.evaluateExpression() 메서드를 사용하여 표현식을 평가하고 조건부로 이벤트를 처리합니다.

expression 변수에 설정된 표현식은 Spring EL 표현식으로, UserRegisteredEvent 클래스의 인스턴스이며, username 필드의 길이가 5보다 큰 경우에만 해당 이벤트를
처리합니다.

0개의 댓글